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/recordsAuthorization: Bearer {access_token}Idempotency-Key: 6f1c2e8a-...-batch-2026-06-29SDI-Contract-Version: 1.0Content-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 withproblems(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 whoserecordRefyou have seen before updates the existing student; returnoutcome: "updated". Return your own student id asexternalIdforcreated/updated. recordRefis the Consumer’s id, opaque to you — store it; it is how the Consumer addresses this student in future updates (e.g. anapplicant-updatecapability later).
6.1 Validating each record
Section titled “6.1 Validating each record”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.