Publish your field schema
GET {schema_endpoint} (requires sdi.enrollment.read) returns a JSON Schema (Draft 2020-12) describing
the fields you expect. This is what powers the Consumer’s field-mapping experience, so annotate it well:
{ "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://sms.example.edu/sdi/enrollment-sync/schema/1.0", "type": "object", "required": ["givenName", "familyName", "email", "programme", "intakeYear"], "properties": { "givenName": { "type": "string", "title": "First name" }, "familyName": { "type": "string", "title": "Last name" }, "email": { "type": "string", "format": "email", "title": "Email" }, "phone": { "type": "string", "title": "Phone" }, "dateOfBirth":{ "type": "string", "format": "date", "title": "Date of birth" }, "programme": { "type": "string", "title": "Programme", "enum": ["BSc Computer Science", "BSc Economics", "LLB Law"] }, "intakeYear": { "type": "integer", "title": "Intake year", "minimum": 2024 }, "localIds": { "type": "array", "title": "Local identifiers", "items": { "type": "object", "required": ["scheme", "value"], "properties": { "scheme": { "type": "string" }, "value": { "type": "string" } } } }, "extensions": { "type": "object", "title": "Additional data" } }}Guidance:
- Use the SDI canonical field names (§9.3) where a concept matches (
givenName,email,dateOfBirth, …). This makes a Consumer’s mapping predictable and lets administrators map most fields in one click. Declare your own field names only where you genuinely diverge. - Add
title/descriptionto every field — Consumers display them in the mapping UI. - Use
enum,format,pattern,minimum/maxLengthto express real constraints; Consumers surface them so administrators map correct sources. - Tag the response with its
contract_versionvia theSDI-Contract-Versionheader and/or the schema$id. Keep older advertised versions resolvable for the deprecation window (§11).
5.1 Localizing field labels (if you support more than one language)
Section titled “5.1 Localizing field labels (if you support more than one language)”Only the human-readable parts of the schema are localized — title, description, and enum option
labels. Property names and enum values stay the same in every language (the Consumer maps and validates
against them). Honour Accept-Language, echo Content-Language, set Vary: Accept-Language, and fall back
to your default_language when you can’t satisfy the request (§4.1). With Hono:
const SUPPORTED = ["en-GB", "fr-FR", "sw-KE"] as constconst DEFAULT = "en-GB"
// Simplest possible negotiation: exact tag, then primary-language match, else default.function negotiate(header: string | undefined): (typeof SUPPORTED)[number] { const wanted = (header ?? "").split(",").map((p) => p.split(";")[0].trim().toLowerCase()) for (const tag of wanted) { const base = tag.split("-")[0] const hit = SUPPORTED.find((s) => s.toLowerCase() === tag || s.toLowerCase().startsWith(base + "-")) if (hit) return hit } return DEFAULT}
app.get("/sdi/enrollment-sync/schema", (c) => { const lang = negotiate(c.req.header("accept-language")) c.header("Content-Language", lang) c.header("Vary", "Accept-Language") c.header("SDI-Contract-Version", "1.0") return c.json(enrollmentSchema(lang)) // titles/descriptions/enum labels in `lang`; field names unchanged})Give enumerated fields stable values plus localized labels with the oneOf + const/title pattern — the
const never changes, only the title does per language:
"sex": { "type": "string", "title": "Sex", "oneOf": [ { "const": "male", "title": "Male" }, { "const": "female", "title": "Female" } ]}Apply the same to your error responses (§6): localize a problem’s title/detail, but keep its code
stable across languages so Consumers can key their own translations off it.