API Contracts
:::info Source
Sourced from services/progress-service/API_CONTRACTS.md in the documentation repo.
:::
1. Surfaces
- Internal REST:
/api/v1/…(authenticated, tenant-scoped). - xAPI:
/xapi/statementsand related (xAPI 1.0.3 compliant). - Inter-service: NATS subscription to delivery-service + assessment-service events.
2. Versioning
/api/v1/…for internal;/xapi/is spec-defined (xAPI 1.0.3, frozen).- Minor changes via
X-API-Versionresponse header.
3. Authentication
Authorization: Bearer <jwt>(internal).X-API-Keyfor xAPI integrations (per-tenant keys).X-Tenant-Idheader required; must match JWTtid.
4. Endpoints
4.1 xAPI (LRS)
POST /xapi/statements
Body: Statement | Statement[]
Returns: 200 OK with { statementIds: [...] }
GET /xapi/statements/{id}
Returns: Statement
GET /xapi/statements?agent=...&verb=...&activity=...&since=...&until=...&limit=100&ascending=true
Returns: { statements: [...], more: "/xapi/statements?cursor=..." }
HEAD /xapi/statements?... (conformance)
PUT /xapi/activities/state (state API)
GET /xapi/activities/state
DELETE /xapi/activities/state
PUT /xapi/activities/profile (profile API)
GET /xapi/activities/profile
GET /xapi/about (returns supported versions)
4.2 Internal
GET /api/v1/attempts/{id}
GET /api/v1/attempts?filter[enrollmentId]=...&filter[outcome]=passed&page[size]=50
POST /api/v1/attempts/{id}/close (admin-only; emergency close)
GET /api/v1/enrollments/{eid}/progress
Returns: {
enrollmentId, courseId, courseVersionId, userId,
attempts: [ { id, number, state, outcome, score, startedAt, endedAt } ],
latestAttempt: { ... },
overallState: 'not_started' | 'in_progress' | 'completed' | 'failed',
completion: null | { completedAt, score, certificateId? }
}
GET /api/v1/enrollments/{eid}/transcript?format=json|pdf
Returns: full transcript including interactions, scores, completion proof.
GET /api/v1/users/{uid}/transcripts
GET /api/v1/users/{uid}/statements (paginated)
5. Request/Response Schemas
Statement (xAPI 1.0.3)
{
"id": "01H...",
"actor": { "account": { "homePage": "https://id.ghasi.io", "name": "user_ulid" } },
"verb": { "id": "http://adlnet.gov/expapi/verbs/passed", "display": { "en-US": "passed" } },
"object": { "id": "urn:ghasi:course_version:cv_01H...", "definition": { "name": { "en-US": "Course Title" } } },
"result": { "score": { "scaled": 0.87 }, "success": true, "completion": true },
"context": { "registration": "reg_ulid", "contextActivities": {...} },
"timestamp": "2026-04-15T09:23:00Z",
"stored": "2026-04-15T09:23:02Z",
"authority": { "account": { "homePage": "https://id.ghasi.io", "name": "delivery-service" } }
}
Progress Response
Provided in §4.2 above.
6. Error Model (problem+json)
Common codes:
validation.statement.invalid— fails xAPI schema (422)validation.enrollment.not_found— statement has no matching enrollment (422)validation.tenant.mismatch— actor/tenant mismatch (403)resource.not_found— attempt / statement / enrollment absent (404)cursor.stale— cursor filters changed (410)rate.limited— ingestion rate exceeded (429)
7. Pagination
limit(xAPI) capped at 100; internal cursor paginated (page[size] ≤ 200).- Cursor payload includes
storedBefore,storedAfter,filtersfingerprint.
8. Rate Limits
| Endpoint | Limit | Per |
|---|---|---|
POST /xapi/statements | 10k/sec | tenant |
POST /xapi/statements (batch) | 100 MB/min | tenant |
GET /xapi/statements | 100/min | user |
GET /api/v1/enrollments/{eid}/progress | 600/min | user |
9. Security
- xAPI endpoints require
xapi:read/xapi:writescope. - Tenant isolation:
agentfilter narrowed to caller's tenant. - API keys logged by prefix only.
- Statements containing PII outside
actor.account.nameredacted on log.
10. Idempotency
- xAPI statements:
statementIdPK; duplicates silently accepted (spec requirement). - Internal writes use
Idempotency-Key.
11. Performance Targets
- Ingestion: 10k statements/sec/region sustained.
- Query p95 < 300ms.
- Transcript render p95 < 2s.
12. Monorepo contract hub (@ghasi/contracts)
- OpenAPI (aggregated):
Ghasi-edTech/packages/contracts/openapi/progress-service.openapi.json— keep in sync withservices/progress-service/openapi.jsonviapnpm --filter @ghasi/contracts sync:openapiin the implementation monorepo. - AsyncAPI (machine-readable events):
Ghasi-edTech/packages/contracts/asyncapi/progress-service.asyncapi.yaml— runpnpm --filter @ghasi/contracts validatein CI. This document remains the normative source for review, versioning, and outbox policy; extend the AsyncAPI channel list when you add published subjects.