Each resource has its own monotonic integer version, incremented each time the underlying YAML changes. The public identifier is the integer; the underlying git SHA is kept in metadata for transparency.
Three ways to read a state
GET /api/v1/compliance/states/MO # current (live)
GET /api/v1/compliance/states/MO?version=4 # pin to version 4
GET /api/v1/compliance/states/MO?as_of=2026-03-01 # what it looked like on that date
All three return the same envelope shape. The meta block tells you which version you got:
{
"data": { "abbreviation": "MO", "name": "Missouri", ... },
"meta": {
"api_version": "v1",
"version": 4,
"released_at": "2026-04-15",
"git_sha": "abc12345"
}
}
Why integers and not SHAs?
SHAs are great for internal audit but bad for customer-facing URLs:
- SHAs aren't chronologically orderable at a glance
- They aren't human-meaningful
- They couple your code to our internal commit history
Integers solve all three. The SHA stays in meta.git_sha for anyone who wants to verify provenance.
Version history
GET /api/v1/compliance/states/MO/versions
Returns the full version list newest-first, each carrying the version number, release date, short SHA, commit author, commit message (as change_summary), provenance (source: human | llm_drafted | automated_sync), and a diff block listing which top-level keys changed vs the previous version.
GET /api/v1/compliance/states/MO/versions/4
Returns the historical payload at version 4 — exactly what a live GET /states/MO would have returned the day version 4 was published.
Caching
Every state response includes HTTP caching headers:
ETag: "MO-v4"Last-Modified: <committed_at as HTTP date>
Honor them. Send If-None-Match: "MO-v4" and you'll get a 304 Not Modified with no body when the version hasn't changed — saves bandwidth on polling integrators.
How versions are minted
When the YAML file at data/compliance/states/<abbr>.yml changes (whether via a human PR, a future LLM-drafted PR, or an automated sync), a deploy-time compliance:reindex_history rake task walks git log for that file and writes one new compliance_snapshots row, with a monotonic version_number. There is no "version 0" — initial seeding creates version 1.
Schema evolution
The YAML schema is locked at data/compliance/state.schema.json. Renames bump the schema major version (a new file: state.schema.v2.json); every snapshot carries the payload_schema_version it was written under so historical reads stay correct.