Versioning
How the formats grow without breaking the apps that read them.
Every format is versioned by a single integer, and most changes don't need a new one. The rules are the same for themes, cards and the public page.
The rules
Schemas are versioned by integer
Documents that carry a version say so:
"schema": 1in themes and cards. The version is also in the schema's URL:theme-1.json,card-1.json,profile-1.json.Additive changes keep the version
New enum values (a new stat id, a new theme font), new optional fields and new endpoints ship under the same version. The served schema is updated in place.
Readers ignore what they don't know
Skip unknown fields. Skip a stat whose id you don't recognize (it still has a
labelandvalueif you want to show it generically). Treat an unknown theme enum value as that field's default: the valueterminaluses.Breaking changes bump the version
Removing or renaming a field or enum value, changing a type, or changing what a value means ships as a new file,
{name}-{n+1}.json. The old version stays served.Unknown themes fall back to terminal
A theme id that no longer exists renders as
terminal. A broken theme never breaks a profile.Retired stats disappear quietly
If a stat id is ever retired, cards that chose it drop it when read, topped up from the defaults so a card always shows at least 2.
Writes are strict, reads are lenient
PUT /profile/cardand theme submissions are checked against the current schema. Responses may contain values newer than your client.
What that means for your client
| Change | Version | What you do |
|---|---|---|
A new stat id, e.g. kudos | same | Skip it, or show its label and value |
| A new theme font or layout variant | same | Use terminal's value for that field |
| A new optional field on the page | same | Ignore it |
| A new endpoint | same | Nothing |
| A field renamed or removed | n+1 | Keep reading n; move when you're ready |
| A value's meaning changes | n+1 | Same |
Validating
- Validate against the schema served now (cached for at most a day), not a copy vendored into your app: a stale copy rejects values that were added since.
- A failed validation on read is a reason to render less, never a reason to render nothing.
Errors
Errors are JSON with a stable error code and the matching HTTP status:
| Status | error | When |
|---|---|---|
| 400 | invalid | Bad input. PUT /profile/card adds a human-readable message |
| 401 | unauthorized | Missing or expired token on an authenticated endpoint |
| 404 | not_found | Unknown profile, username or schema |
| 429 | rate_limited | Too many requests |
Some endpoints add a reason (see Handle). Don't rely on any other field in an error.