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.

Amounts
// 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.

Formatting safely
/** 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 covers is refused with CURRENCY_NOT_COVERED 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.

EndpointParametersResponse
GET /v1/policies?page, ?pageSize{ policies, pagination }
GET /v1/webhooks/{id}/deliveries?limit{ items, hasMore }
GET /v1/agentsNone{ agents }
GET /v1/webhooksNone{ items }
MCP list_purchaseslimit (1–50), cursor{ purchases, nextCursor, hasMore }

Page and size, where it applies#

  • pageSize defaults 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.
  • pagination is always present, and totalPages is 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, which accepts no body at all.

SurfaceLimit
Purchases, policies, simulations64 KB
Webhook management16 KB

The purchase limit is a real bound rather than a formality: the body is stored verbatim as evidence, so an unbounded one would be an unbounded write into permanent records.

Versioning#

Three version vocabularies, and they mean different things.

VersionWhereWhat it versions
/v1The URL pathThe REST contract.
2026-09-01apiVersion in a webhook envelopeThe 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.0engineVersion in a decisionWhich 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-secret accepts 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 issue code, not the path.

The full contract, including these, is in the API reference.