Skip to content

Implement ingest

POST {ingest_endpoint} (requires sdi.enrollment.write) accepts an envelope of records and returns one result per record.

Request:

POST /sdi/enrollment-sync/records
Authorization: Bearer {access_token}
Idempotency-Key: 6f1c2e8a-...-batch-2026-06-29
SDI-Contract-Version: 1.0
Content-Type: application/json
{
"sdi_version": "1.0",
"capability": "enrollment-sync",
"contract_version": "1.0",
"records": [
{
"recordRef": "app_01HZX...",
"data": {
"givenName": "Amara", "familyName": "Okeke", "email": "amara@example.com",
"programme": "BSc Computer Science", "intakeYear": 2026,
"localIds": [{ "scheme": "ng.jamb", "value": "12345678AB" }]
},
"documents": [
{ "id": "doc_88a1", "type": "transcript", "resolveUrl": "https://admissions.example/sdi/documents/resolve",
"filename": "transcript.pdf", "contentType": "application/pdf", "size": 482113,
"sha256": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08" }
]
}
]
}

Response (200):

{
"results": [
{ "recordRef": "app_01HZX...", "outcome": "created", "externalId": "stu_55021", "problems": [] }
]
}

Rules to implement:

  • Validate each record against the schema you published for contract_version. On a per-field failure, reject that record with problems (JSON Pointers into the record), but do not fail the whole batch — valid records in the same batch must still be applied.
  • Idempotency: dedupe on Idempotency-Key + recordRef. A repeat must return the original result, not a duplicate student. Keep results for at least 24 hours (7 days recommended).
  • Upsert by recordRef: a record whose recordRef you have seen before updates the existing student; return outcome: "updated". Return your own student id as externalId for created/updated.
  • recordRef is the Consumer’s id, opaque to you — store it; it is how the Consumer addresses this student in future updates (e.g. an applicant-update capability later).

Validate record.data against the JSON Schema you published in §5. Ajv is the most popular validator and supports Draft 2020-12 via its 2020 build; compile the schema once and reuse it:

import Ajv2020 from "ajv/dist/2020"
import addFormats from "ajv-formats"
const ajv = addFormats(new Ajv2020({ allErrors: true }))
const validate = ajv.compile(publishedEnrollmentSchema) // the schema from your schema_endpoint
function validateRecord(data: unknown) {
if (validate(data)) return { ok: true as const }
return {
ok: false as const,
problems: (validate.errors ?? []).map((e) => ({
pointer: e.instancePath || "/", // JSON Pointer into the record
code: e.keyword, // e.g. "required", "format", "type"
detail: e.message ?? "invalid",
})),
}
}

Map a failed result straight into the record’s problems array (§6) and reject only that record.

Lighter-weight options: @cfworker/json-schema is a small, dependency-free Draft 2020-12 validator that runs well on edge/Workers runtimes. If your codebase is already Zod-first, author the schema in Zod and derive the published JSON Schema with Zod’s toJSONSchema (so the wire schema and your validator share one source of truth), then validate incoming records with the same Zod schema rather than a separate JSON-Schema validator.