---
title: "Latest contract check run"
method: GET
path: "/v1/contract"
tags: ["Content"]
---

# Latest contract check run

`GET /v1/contract`

## Request

- Base URL: `https://api.gunspec.io`
- URL: `https://api.gunspec.io/v1/contract`
- Auth: one of:
  - none
  - API key in header `X-API-Key`
  - HTTP bearer

Every documented operation is called against this API on a schedule and held against this document: the status, the media type, the schema, the envelope, the fields promised against the fields sent, the cache contract, and the plan gates. This returns the most recent run, with the verdict for each operation, the checks that failed, and what happened when each gate was exercised with a key above it, at it and below it.

This is not uptime. Whether the API is answering is `GET /health` and the status page; this is whether what it answers is still what the reference promises, which a perfectly available deploy of last week's code gets wrong.

No API key required.

## Query parameters

- `env` 'production' | 'staging' | 'local'
- `surface` 'api' | 'mcp' | 'fitment' | 'catalogue' | 'integrity' | 'plausibility' | 'anomalies' | 'cartridges' | 'materials' | 'corroboration' | 'sources' | 'manufacturers' | 'links'

## Response `200`

The latest run for that environment. `run` is null when nothing has been checked there yet, which is deliberately different from a run where everything failed.

- object
  - `success` boolean, required: Always true on a successful response. Every error carries `false` and an `error` object instead.
  - `data` object, required: The response payload.
    - `run` ApiContractRun: One run of the contract suite: every documented operation called against this API and held against this document. Produced by `scripts/smoke/` in CI. Uptime is a separate question and a separate system - this says whether what the API answers is still what the reference promises.
      - `surface` 'api' | 'mcp' | 'fitment' | 'catalogue' | 'integrity' | 'plausibility' | 'anomalies' | 'cartridges' | 'materials' | 'corroboration' | 'sources' | 'manufacturers' | 'links': What the run measured: `api` for this REST API, `mcp` for the hosted MCP server, `fitment` for the attachment compatibility suite, `catalogue` for the catalogue record checks: every file against its schema, no company under two ids, and no firearm whose fields cannot all be true, `integrity` for the integrity checks: figures, prose and feature lists copied between unrelated guns, citations that are not evidence, timelines, weight against size, and confidence against evidence, `plausibility` for the plausibility checks: a loaded weight against the empty one and its magazine, a gun older than its cartridge, figures in the wrong unit, energy against velocity, variants that disagree with their family, and the same gun filed twice, `anomalies` for the statistical anomaly checks: a figure unusually far from its family's or its cohort's median, and a barrel an unusual share of its gun's length; each a figure to verify, not one shown wrong, `cartridges` for the cartridge checks: energy against bullet and velocity, pressure in MPa against psi, dimensions no case can have, a bullet against the calibre its name states, a cartridge older than its parent, and each load against its cartridge, `materials` for the materials registry: every material's density quoted from a page that still states it, within the range it declares, and every material value the catalogue uses covered, so the mass checks read sourced densities, `corroboration` for the corroboration checks: firearm figures compared with a second, independent source (Wikidata), `sources` for the source checks: how many records cite their own maker or another authority, and which heavily cited sites the source hierarchy cannot yet name, `manufacturers` for the manufacturer checks: every manufacturer record held against Wikidata for its website, founding year, country and whether it still trades, `links` for the link checks: every manufacturer website and every cited source asked whether it still answers and is still the page cited.
      - `env` 'production' | 'staging' | 'local', required: Which environment was measured: `production`, `staging` or `local`. A staging run is never mixed into production, because a report read as evidence about the wrong environment is worse than no report.
      - `specVersion` string, nullable: The version of this document the run was measured against, so a result names the contract it was true of.
      - `commitSha` string, nullable: The commit of our repository the run started from, or null where CI did not report one.
      - `runUrl` string, nullable: The CI run, where it is reachable.
      - `ranAt` string, required: When the run finished, ISO 8601.
      - `durationMs` integer: Wall-clock time the whole sweep took, in milliseconds.
      - `operations` integer, required: How many documented operations the run covered.
      - `passed` integer, required: Operations where every check that ran passed.
      - `failed` integer, required: Operations where at least one check failed. One endpoint failing three ways is one unhealthy endpoint, not three.
      - `skipped` integer, required: Operations nothing could be asked about: no key for that gate, a write the suite will not send against a live host, or a fixture it could not resolve. Never folded into `passed`.
      - `tiers` string[], required: The plans the run held a key for. The tier checks can only cover a gate the run could call, so this is what separates "every gate was proven" from "we had one key".
      - `results` object[], required: One entry per documented operation.
        - `key` string, required: The operation, as method and path: `get /v1/firearms/{id}`.
        - `method` string, required: HTTP method, lower case, as the operation key spells it.
        - `path` string, required: The documented path, with its parameters still in braces.
        - `tag` string, nullable: The OpenAPI tag the operation belongs to, which is how the report groups.
        - `tier` string, nullable: The plan the document declares for it, which is what the access verdicts are judged against.
        - `status` integer, nullable: Production status, one of `in_production`, `discontinued`, `out_of_production`, `in_service`, `limited_production`, `prototype`. Discontinued records are kept deliberately: a rifle built in 2004 wears parts nobody sells today.
        - `ms` integer: How long that operation took, in milliseconds.
        - `verdict` 'pass' | 'fail' | 'skip', required: `pass`, `fail` or `skip` for the operation as a whole.
        - `checks` object, required: How many checks passed, failed and were skipped for this operation.
          - `passed` integer: Operations where every check that ran passed.
          - `failed` integer: Operations where at least one check failed. One endpoint failing three ways is one unhealthy endpoint, not three.
          - `skipped` integer: Operations nothing could be asked about: no key for that gate, a write the suite will not send against a live host, or a fixture it could not resolve. Never folded into `passed`.
        - `describes` string, nullable: What the operation asks, when its key does not say. A fitment scenario states its question and the checks behind it; an API operation carries none, its path being its name.
        - `failures` object[], required: Only the checks that failed. A run is about 1,500 checks, and the passing ones are the counts above.
          - `check` string: Which check this entry is about, e.g. `schema`, `tier-refuses`.
          - `detail` string: What the check found, in the words a person needs to act on it.
        - `access` object[], required: What happened when the plan gates were exercised: a keyless call, the documented plan, and the plan immediately below it.
          - `check` 'gate' | 'tier-admits' | 'tier-refuses' | 'tier-shape': Which check this entry is about, e.g. `schema`, `tier-refuses`.
          - `tier` string, nullable: The plan the document declares for it, which is what the access verdicts are judged against.
          - `verdict` 'pass' | 'fail' | 'skip': `pass`, `fail` or `skip` for the operation as a whole.
          - `detail` string: What the check found, in the words a person needs to act on it.
      - `systemChecks` object[], required: The same run reduced to questions about the API as a whole rather than about one endpoint: whether any response carries JSON inside a string, whether every gate still refuses a keyless call, whether every ETag revalidates. A rollup of the results above, so the two cannot disagree; `found` counts the operations that failed the check and `sample` names a few of them.
        - `id` string, required: The system check, as a stable kebab-case id: `json-not-stringified`, `allowance-matches-the-plan`. Never renamed, because the docs name it in prose and a reader may have linked to it.
        - `describes` string, required: What passing means, in a sentence.
        - `status` 'pass' | 'fail' | 'skip', required: `skip` where the run never gathered the evidence, which is not the same as passing.
        - `found` integer, required: How many operations failed the check.
        - `checked` integer, required: How many the run could ask it of. A pass over two operations and a pass over 127 both report zero failures, so this is what separates them.
        - `sample` string[], required: A few of the operations that failed it.

## Other responses

- `400`: Validation error
- `429`: A limit was reached. `RATE_LIMITED` is the per-minute limit: wait `Retry-After` seconds (a minute) and retry. `DAILY_CAP_EXCEEDED` is the plan's daily limit, per key: nothing will be served until the counters return to zero at midnight UTC. `MONTHLY_CAP_EXCEEDED` is the plan's monthly allowance, for the whole account: nothing will be served until midnight UTC on the first of next month, or until the plan changes. For both, `Retry-After` is the seconds until then and `error.resetsAt` says when as an instant. Calls made after a refusal are refused too and do not spend the month, and a free key that keeps calling is paused, so stop at the first one and resume at the reset.
- `500`: Internal server error

---

[API](https://skmtc.dev/gunspec/apis/gunspec-io-api.md) · [All operations](https://skmtc.dev/gunspec/apis/gunspec-io-api/llms.txt) · [OpenAPI document](https://skmtc.dev/gunspec/apis/gunspec-io-api/revisions/f2e974d8b05c?raw)
