Skip to content

← Back to Overview

Internal Endpoints

These endpoints back the website's own admin/submission-form UI rather than being a general public API. They're documented here for completeness, not as a recommendation to build new integrations against them — they have looser error handling than the endpoints in Software Catalog API and Submission API, and none of their behavior is covered by the test suite.

None of these go through the camelCase renderer; keys below are shown exactly as the server sends/expects them.

Contents


Structure

Why are these generic, not one-per-model?

Every model in this codebase — OperatingSystem, ProgrammingLanguage, Keyword, SoftwareEditQueue, and dozens more — inherits from one HssiModel base class (website/models/base.py). That shared base is what lets five endpoints cover every model in the app instead of needing one bespoke endpoint per model:

  • Every model gets a UUID primary key (id) the same way.
  • Every model implements get_choice(), returning a uniform (id, name, keywords, tooltip, identifier) tuple no matter what the model actually represents.
  • Every model declares an access: AccessLevel class attribute, so "can this caller read/write this model" is answerable generically too.

Because every model honors that same contract, a single view can take a model_name from the URL, resolve it with Django's apps.get_model(app_label, model_name), and operate on it the same way regardless of which specific table it is — the same trick Django's own admin site uses to generate a change-list and edit form for any registered model without per-model code.

That's the shape behind each endpoint below:

Endpoint Mirrors
structures/ Introspecting a model's fields (like Model._meta.get_fields())
{model_name}/choices/ A queryset of choices for a dropdown (like populating a ModelChoiceField)
{model_name}/form/ A generic add/change form (like Django admin's auto-generated form)
{model_name}/rows/all/ A generic list view (Model.objects.all(), exposed over HTTP)
{model_name}/rows/{uid}/ A generic detail view (Model.objects.get(pk=uid))

This genericness is also why the error handling below is looser than the public API's: {model_name} is a live lookup against any HssiModel subclass in the app, so there's a much larger space of "what could go wrong" than a hand-written endpoint built around one specific resource.

GET /api/models/structures/

No parameters. Returns the schema the submission form renders itself from:

{"data": [{"...": "registered form-field structure entries"}], "fieldMap": {"...": "..."}}

GET /api/models/{model_name}/choices/

Returns the available choices for a controlled-vocabulary model (e.g. OperatingSystem, ProgrammingLanguage) — the dropdown options shown in the submission form.

Query parameters: any query parameter is treated as a Django filter keyword, and its value is parsed with Python's ast.literal_eval — so values must be valid Python literals (e.g. a quoted string), not bare text. An unquoted value will raise an error server-side rather than a clean 400.

curl "https://hssi.hsdcloud.org/api/models/ProgrammingLanguage/choices/"
{"data": [{"id": "...", "name": "Python"}, {"id": "...", "name": "Rust"}]}

Errors: 400 "Target model must inherit from HssiModel" if model_name isn't a recognized controlled-vocabulary model.

GET /api/models/{model_name}/form/

Renders an HTML form fragment for the given model, not JSON. Out of scope for programmatic use.

GET /api/models/{model_name}/rows/all/

Lists rows of an HssiModel subclass, gated by an internal role-based access-level system.

Query parameters

Param Description
ids Comma-separated list of primary keys to restrict to.
offset, limit Pagination (default 0/25 if not present). Non-integer values return 400.
columns Comma-separated list of fields to include in each row.
recursive "true"/"false" — whether related objects are serialized recursively.
sort VerifiedSoftware only. Orders the whole list before pagination, as the homepage's sort buttons do: date puts the most recently modified metadata first (see metadata_modified_date below), create puts the newest Publication Date first, and name sorts A–Z ignoring case. Rows without a date come last, and equal dates fall back to name order. Without sort, or with any other value, rows come back in no particular order.
curl "https://hssi.hsdcloud.org/api/models/Keyword/rows/all/?limit=5&columns=id,name"
{"data": [{"id": "...", "name": "magnetosphere"}], "total": 42}

Each VerifiedSoftware row also includes metadata_modified_date: an ISO 8601 timestamp of the software's last metadata change, from Software.date_modified or, failing that, its newest submission date, and null when neither exists. It's the same date the JSON-LD reports as subjectOf.dateModified, and the one the date sort uses.

Insufficient access on this particular endpoint surfaces as an unhandled server error rather than a clean 403 — a known inconsistency versus the single-row endpoint below.

GET /api/models/{model_name}/rows/{uid}/

Same columns/recursive parameters as above, for a single row.

  • {model_name} of SoftwareEditQueue (or any subclass) is explicitly blocked: 403.
  • Insufficient access: 403 "Unauthorized".
  • If {uid} doesn't parse as a UUID, this silently falls back to returning the first row of the model rather than a 404 — worth knowing if you're debugging an "invalid id" case that unexpectedly returns data.
curl "https://hssi.hsdcloud.org/api/models/Keyword/rows/9e1a2b3c-.../"
{"id": "9e1a2b3c-...", "name": "magnetosphere"}