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
- GET /api/models/structures/
- GET /api/models/{model_name}/choices/
- GET /api/models/{model_name}/form/
- GET /api/models/{model_name}/rows/all/
- GET /api/models/{model_name}/rows/{uid}/
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: AccessLevelclass 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}ofSoftwareEditQueue(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 a404— 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"}