Skip to content

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/description to every field — Consumers display them in the mapping UI.
  • Use enum, format, pattern, minimum/maxLength to express real constraints; Consumers surface them so administrators map correct sources.
  • Tag the response with its contract_version via the SDI-Contract-Version header 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 const
const 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.