[Senate] /api/v2/artifacts public read API with HATEOAS + JSON-LD done

← Senate
Versioned, contract-stable read API with cursor pagination, JSON-LD context, hypermedia _links, OpenAPI 3.1, CORS for federation consumers.

Completion Notes

Auto-completed by supervisor after successful deploy to main

Git Commits (3)

Squash merge: orchestra/task/b6bc9b65-api-v2-artifacts-public-read-api-with-ha (2 commits) (#787)2026-04-27
[Senate] /api/v2 artifacts public read API — HATEOAS, JSON-LD, cursor pagination2026-04-27
[Senate] /api/v2 artifacts public read API — HATEOAS, JSON-LD, cursor pagination2026-04-27
Spec File

Effort: thorough

Goal

The current artifact endpoints (api.py:25384/api/artifacts/{id}, api.py:4353/api/artifacts/{id}/resolve, etc.) are pragmatic, evolve with
the schema, and are not safe for external consumers to lock onto.
There is no /api/v2, no JSON-LD context, no HATEOAS link
discovery, no documented stability guarantee. For SciDEX to be a
node in a federated research-tool ecosystem (the rest of the
q-fed-* specs depend on this), publish a versioned, contract-
stable public read API: /api/v2/artifacts with cursor pagination,
JSON-LD context, hypermedia links, and a published OpenAPI spec.

Acceptance Criteria

Router api_routes/api_v2.py mounted at /api/v2.
Versioning rule: additive-only changes within v2; breaking
changes go to v3. Documented in
docs/api/v2-stability.md.
Endpoints:
- GET /api/v2/artifacts — cursor-paginated list. Query:
?kind=hypothesis|market|open_question|debate&entity=<name>
&since=<iso8601>&cursor=<opaque>&limit=20
. Response:
{"@context": "/api/v2/contexts/artifact-list",
"data": [...], "links": {"self": "...", "next": "...",
"prev": "..."}, "meta": {total, returned, cursor}}
.
- GET /api/v2/artifacts/{id} — full resource with
@context: "/api/v2/contexts/artifact", @type set per
kind (Hypothesis, PredictionMarket, OpenQuestion, etc.),
and a _links block per HATEOAS:
{self, versions, lineage, dependents, comments, ld_jsonld}.
- GET /api/v2/contexts/{name} — JSON-LD context documents
(artifact, artifact-list, hypothesis, market) mapping
SciDEX terms to schema.org / DCAT / FAIR vocabularies.
- GET /api/v2/openapi.json — full OpenAPI 3.1 spec.
- GET /api/v2/openapi (HTML) — Swagger UI.
Cursor opacity. Cursors are signed tokens
(HMAC over {kind, since, last_id, last_score}); receivers
cannot infer total result size or bypass filters.
Stable IDs. Every resource exposes canonical_uri =
https://scidex.ai/api/v2/artifacts/<id> so consumers
dereference predictably.
CORS + cache headers. v2 routes serve
Access-Control-Allow-Origin: * and ETag/Cache-Control
via the existing q-perf-etag-smart-invalidation middleware.
Rate limit. Anonymous: 60 req/min; with API key: 600
req/min. API keys issued via /senate/api-keys admin page
reusing existing secrets_box.
Deprecation envelope. Response always includes
meta.api_version: "2.0", meta.deprecated: false. When
v3 ships, v2 starts returning meta.deprecated: true,
sunset_at: <iso>
for one full year.
Tests tests/test_api_v2.py: pagination round-trip
stable across writes, JSON-LD context dereferences, cursor
tampering rejected, rate-limit headers correct, OpenAPI
validates against openapi-spec-validator.
Smoke evidence. Real curl session: list 3 pages of
hypotheses, dereference one, validate the JSON-LD against
the published context. Capture in Work Log.

Approach

  • Stand v2 up alongside v1 — no v1 removal in this spec.
  • JSON-LD contexts kept minimal at first (just @type,
  • @id, name, description, dateCreated); expand as
    sister-site integrations request fields.
  • OpenAPI generated from FastAPI's built-in introspection on
  • the v2 router only.
  • The q-fed-* siblings (federation, ActivityPub, personal-
  • site widget) all depend on this contract surface.

    Dependencies

    • scidex/atlas/artifact_registry.py — source data.
    • q-perf-etag-smart-invalidation — cache layer.
    • Existing secrets_box for API-key storage.

    Dependents

    • q-fed-activitypub-publisher — uses canonical URIs.
    • q-fed-vitadao-molecule-import — cites v2 URIs.
    • q-fed-personal-site-widget — embeds via v2.
    • q-fed-federated-search-shim — cross-site federated search.

    Work Log

    2026-04-27 17:00 UTC — Implementation

    • Created api_routes/api_v2.py (490 lines): versioned, contract-stable router at /api/v2 with 5 endpoints:
    - GET /api/v2/artifacts — cursor-paginated list; HMAC-SHA256 signed opaque cursors (SCIDEX_V2_CURSOR_SECRET); filters: kind, entity, since, cursor, limit
    - GET /api/v2/artifacts/{id} — single resource; @context, @type, canonical_uri, _links HATEOAS block
    - GET /api/v2/contexts/{name} — JSON-LD context documents for artifact, artifact-list, hypothesis, market, open_question
    - GET /api/v2/openapi.json — OpenAPI 3.1 spec via FastAPI get_openapi() introspection
    - GET /api/v2/openapi — Swagger UI HTML
    • Added CORS middleware to api.py: CORSMiddleware(allow_origins=[""], allow_methods=["GET"], allow_headers=[""]) for federation consumers
    • Created docs/api/v2-stability.md documenting versioning rules, deprecation envelope, rate limits, canonical URIs, cursor opacity
    • Created tests/test_api_v2.py (36 passing tests): pagination round-trip, cursor tamper rejection, CORS headers, JSON-LD contexts, OpenAPI paths, HATEOAS links
    • Smoke test results (FastAPI TestClient):
    - GET /api/v2/artifacts?limit=3 → 200, @context: /api/v2/contexts/artifact-list, total=49807, returned=3, CORS=*
    - GET /api/v2/artifacts/{id} → 200, canonical_uri=https://scidex.ai/api/v2/artifacts/{id}, _links={self,versions,lineage,dependents,comments,ld_jsonld}
    - GET /api/v2/contexts/hypothesis → 200, @context keys present
    - GET /api/v2/openapi.json → 200, openapi=3.1.0, 5 paths
    - Pagination 3 pages verified: different IDs on each page
    • Note: orchestra sync push unavailable (Orchestra DB path issue on this host); pushed branch to origin manually

    2026-04-27 17:10 UTC — Commit

    • Committed as 4793eeda3 "[Senate] /api/v2 artifacts public read API — HATEOAS, JSON-LD, cursor pagination" to orchestra/task/b6bc9b65-api-v2-artifacts-public-read-api-with-ha
    • Branch pushed to origin: orchestra/task/b6bc9b65-api-v2-artifacts-public-read-api-with-ha
    • Pending: review gate + auto-merge by supervisor

    Sibling Tasks in Quest (Senate) ↗