Conventions
The conventions every Veriticity endpoint follows: minor-unit money as strings, ISO 8601 timestamps, UUID identifiers, pagination, idempotency, body limits and versioning.
Rules that hold across every endpoint. Reading this once saves discovering them one surprise at a time.
Money#
Every monetary amount is a decimal string of ISO 4217 minor units, paired with an alpha-3 currency. Pence for GBP, cents for USD.
// Correct. A string, in minor units.
{ "amountMinor": "7500", "currency": "GBP" } // GBP 75.00
// Also accepted on input, and returned as a string anyway.
{ "amountMinor": 7500, "currency": "GBP" }
// Wrong. There is no major-unit field, and no float anywhere.
{ "amount": 75.00, "currency": "GBP" }Amounts are accepted as a JSON number or a string on input, and always returned as a string — including small ones.
/** Minor units to a display string. Never use a float in between. */
function formatMinor(amountMinor: string, currency: string): string {
// BigInt, not Number: above 2^53 a Number is no longer an exact integer,
// and an invoice can get there.
const minor = BigInt(amountMinor);
return new Intl.NumberFormat("en-GB", {
style: "currency",
currency,
}).format(Number(minor) / 100);
}Veriticity performs no currency conversion. A purchase in a currency no applicable policy sets limits in is refused with CURRENCY_NOT_LISTED rather than converted at a rate nobody agreed to.
Time#
Every timestamp is ISO 8601, UTC, with milliseconds: 2026-09-12T14:03:21.114Z.
Fields are null when the thing has not happened rather than absent, so a client’s shape is stable and no key needs probing for.
Identifiers#
Bare UUIDs, with no type prefix. Identifiers that need to sort by creation time — purchase requests, decisions, webhook events — are UUIDv7, so they sort lexicographically by when they were created.
Credentials are the exception and carry a namespace: cmp_live_, cmp_oat_, cmp_ort_, cmp_oac_, cmp_whs_. Those prefixes are permanent.
Enumerations#
Shouted constants: APPROVED, REQUIRES_APPROVAL, ACTIVE, DISABLED, ARCHIVED, REVOKED.
New values may be added. Handle an unrecognised enum value rather than crashing on it — a switch with no default is the thing that breaks when a reason code is added.
Collections and pagination#
There are three pagination models on the API. This is a real inconsistency rather than a subtlety, and it is documented as it is rather than smoothed over.
| Endpoint | Parameters | Response |
|---|---|---|
GET /v1/policies | ?page, ?pageSize | { policies, pagination } |
GET /v1/webhooks/{id}/deliveries | ?limit (default 25, clamped to 100) | { items, hasMore } |
GET /v1/agents | None | { agents } |
GET /v1/webhooks | None | { items } |
MCP list_purchases | limit (1–50), cursor | { purchases, nextCursor, hasMore } |
Page and size, where it applies#
pageSizedefaults to 25 and is clamped to 100 rather than refused — asking for everything gets you the maximum.- A malformed value is refused, because it means your client believes something untrue.
- A page beyond the end returns an empty page, not a
404. pagecan be at most 10000. A higher page is refused withinvalid_request. Narrow the results with a filter instead.paginationis always present, andtotalPagesis at least 1 even when nothing matched.
Request bodies#
Content-Type: application/json on every write, or you get 415. The one exception is POST /v1/webhooks/{id}/rotate-secret, whose body is optional.
| Surface | Limit |
|---|---|
| Purchases, policies, simulations | 64 KB |
| Webhook management | 16 KB |
A body over the limit is refused with 413 payload_too_large and nothing is done. A Content-Length over the limit is refused before the body is sent in full. Limits are in bytes, so multi-byte characters count for more than one.
Versioning#
Three version vocabularies, and they mean different things.
| Version | Where | What it versions |
|---|---|---|
/v1 | The URL path | The REST contract. |
2026-09-01 | apiVersion in a webhook envelope | The event contract, pinned per endpoint. A date, because dates sort and do not invite semver reasoning that does not apply. |
veriticity-trust-engine@0.1.0 | engineVersion in a decision | Which evaluator produced this decision. Evidence, not a contract to branch on. |
Additive changes are not breaking. Fields may be added to responses, and webhook event types may be added, at any time. Ignore what you do not recognise.
Known inconsistencies#
These are documented as they are rather than quietly normalised. Changing any of them would break working integrations, so they are recorded here so you can plan around them.
- Collection envelopes differ.
{ policies, pagination },{ agents },{ items },{ items, hasMore }and{ purchase }all appear. There is no single convention. - Three pagination models, plus two unpaginated collections. See the table above.
- There is no
GET /v1/purchase-requests. An MCP connection can enumerate its own purchases; the REST API reads one at a time by id. - Idempotency exists on one operation only. Webhook creation, rotation and test-send accept no idempotency key.
- Body limits differ between 64 KB and 16 KB.
rotate-secretaccepts an absent body where every other write requires a JSON one.- Whole-body validation issues use two spellings of
path—"body"on purchases, an empty string on webhooks. Match on the issuecode, not the path.
The full contract, including these, is in the API reference.

