Skip to content

API Conventions

Everything on this page applies across the whole API — learn it once, use it everywhere.

Response Format (JSON-LD / Hydra)

Responses use JSON-LD with the Hydra vocabulary. Single resources carry @context, @id, and @type alongside their data fields; collections wrap items in an envelope:

{
  "@context": "/api/partner/v1/contexts/Course",
  "@id": "/api/partner/v1/courses",
  "@type": "hydra:Collection",
  "hydra:member": [ { "…": "resource objects" } ],
  "hydra:totalItems": 109,
  "hydra:view": {
    "@id": "/api/partner/v1/courses?page=1",
    "hydra:first": "/api/partner/v1/courses?page=1",
    "hydra:last": "/api/partner/v1/courses?page=22",
    "hydra:next": "/api/partner/v1/courses?page=2"
  }
}

Treat the @-prefixed and hydra: keys as metadata: read your data from hydra:member (collections) or the top-level fields (single resources).

Field Naming

Resource fields are camelCase: firstName, organizationId, passingScore, createdAt, updatedAt.

One exception

The two content-delivery endpoints (/modules/{id}/content and /modules/{id}/refresh-url) return snake_case fields (content_url, expires_at, module_id). Match each endpoint's documented example rather than assuming one convention globally.

Request Content Types

Operation Content-Type
POST with a body application/json
PATCH application/merge-patch+jsonrequired; a PATCH with application/json is rejected
GET / DELETE (no body)

Merge-patch semantics: send only the fields you want to change; set a field to null to clear it.

The X-User-ID Header

Catalog endpoints are partner-scoped: the JWT alone identifies you. User-scoped endpoints additionally need to know which learner the call is about — pass their UUID:

X-User-ID: a1b2c3d4-5678-9abc-def0-123456789abc
Requires X-User-ID Doesn't
GET /modules/{id}/content GET /ping
GET /modules/{id}/refresh-url POST /users/register
POST /modules/{id}/complete GET /users/{uuid} (UUID is in the path)
GET /course-progresses (+/{id}) GET /organizations, /categories, /courses, /modules (+ details)
GET /transcripts (+/{id})
GET /transcript/pdf, GET /transcripts/{id}/certificate/pdf

The UUID must belong to a learner you registered. A UUID you don't own behaves exactly like one that doesn't exist (404) — partner isolation is absolute.

Pagination

Collections are paginated with query parameters:

GET /api/partner/v1/courses?page=2&itemsPerPage=25
  • page — 1-based page number
  • itemsPerPage — page size
  • Walk hydra:view.hydra:next until it's absent to iterate a full collection.

Delta Sync (updatedAt Filters)

Five collections — /categories, /courses, /modules, /course-progresses, /transcripts — support filtering on updatedAt, so you fetch only what changed since your last sync instead of re-pulling everything:

GET /api/partner/v1/courses?updatedAt[after]=2026-07-01T00:00:00Z
Filter Matches records where updatedAt is…
updatedAt[after] ≥ the given ISO 8601 timestamp
updatedAt[before] ≤ the timestamp
updatedAt[strictly_after] > the timestamp
updatedAt[strictly_before] < the timestamp

Recommended pattern: store the timestamp you started each sync at, and use it as updatedAt[after] next time.

Note

Domain-event fields (lastActivityAt on progress, completionDate on transcripts) are not the sync cursor — always cursor on updatedAt.