API reference
The complete Veriticity REST API: every endpoint, its credential requirements, scopes, parameters, request and response shapes, and the errors it can return.
30 operations across 6 groups. Everything on this page is generated from the Veriticity OpenAPI document, which you can download and feed to a client generator or an HTTP client:
openapi.json — OpenAPI 3.1, 30 operations, including the outbound webhook contract.
Endpoints at a glance#
| Method | Path | Credential |
|---|---|---|
| GET | /v1/purchase-requests | API key bound to an agent · purchase:read |
| POST | /v1/purchase-requests | API key bound to an agent · purchase:request |
| GET | /v1/purchase-requests/{purchaseRequestId} | API key bound to an agent · purchase:read |
| POST | /v1/purchase-requests/{purchaseRequestId}/cancel | API key of either kind · purchase:request |
| POST | /v1/purchase-requests/{purchaseRequestId}/outcome | API key of either kind · purchase:request |
| GET | /v1/purchase-authorizations | Organisation-scoped API key |
| GET | /v1/purchase-authorizations/{authorizationId} | Organisation-scoped API key |
| POST | /v1/purchase-authorizations/{authorizationId}/execution-token | API key bound to an agent |
| POST | /v1/purchase-authorizations/{authorizationId}/use | API key of either kind |
| POST | /v1/purchase-authorizations/{authorizationId}/void | Organisation-scoped API key |
| POST | /v1/purchase-authorizations/{authorizationId}/withdraw | API key of either kind · purchase:request |
| POST | /v1/simulations/current-policies | API key of either kind · purchase:check |
| POST | /v1/simulations/draft-policy | Organisation-scoped API key |
| POST | /v1/simulations/policy-version | Organisation-scoped API key |
| GET | /v1/policies | Organisation-scoped API key |
| GET | /v1/policies/{policyId} | Organisation-scoped API key |
| GET | /v1/policies/{policyId}/versions/{versionId} | Organisation-scoped API key |
| POST | /v1/policies/{policyId}/versions/{versionId}/validate | Organisation-scoped API key |
| GET | /v1/agent | API key bound to an agent · purchase:check |
| GET | /v1/agents | Organisation-scoped API key |
| GET | /v1/budget-status | API key bound to an agent · purchase:check |
| GET | /v1/spending-rules | API key bound to an agent · purchase:check |
| GET | /v1/webhooks | Organisation-scoped API key |
| POST | /v1/webhooks | Organisation-scoped API key |
| GET | /v1/webhooks/{webhookId} | Organisation-scoped API key |
| PATCH | /v1/webhooks/{webhookId} | Organisation-scoped API key |
| DELETE | /v1/webhooks/{webhookId} | Organisation-scoped API key |
| GET | /v1/webhooks/{webhookId}/deliveries | Organisation-scoped API key |
| POST | /v1/webhooks/{webhookId}/rotate-secret | Organisation-scoped API key |
| POST | /v1/webhooks/{webhookId}/test | Organisation-scoped API key |
Purchases#
Submit a purchase for a decision, and find out what happened to it.
List the purchases you submitted#
GET/v1/purchase-requests
API key bound to an agentOAuth: purchase:read
The purchases the calling credential submitted, newest first.
Scoped to the credential, exactly as reading one purchase is. A key that replaced another does not see the earlier key's purchases; subscribe to webhooks for a record that survives rotation.
Credential: An API key bound to an agent. OAuth connections need `purchase:read`.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
status | query | Only purchases in this state. |
since | query | Only purchases requested at or after this instant. |
until | query | Only purchases requested before this instant. |
limit | query | How many to return. |
cursor | query | The `nextCursor` from the previous page. An unrecognised value returns the first page. |
Responses
| Status | Meaning |
|---|---|
200 | One page of purchases. |
400 | invalid_request |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_included |
403 | connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
hasMorerequired | boolean | |
nextCursorrequired | string | null | |
purchasesrequired | PurchaseReadback[] | |
agentrequired | object | |
idrequired | UUID | |
namerequired | string | |
statusrequired | ENABLED | DISABLED | ARCHIVED | |
amountMinorrequired | MinorAmount | |
approval | object | null | |
commentrequired | string | null | |
escalationReasonCodesrequired | ReasonCode[] | |
expiresAtrequired | Timestamp | |
isPendingrequired | boolean | |
resolvedAtrequired | Timestamp | null | |
resolvedByrequired | object | null | |
staterequired | PENDING | APPROVED | REJECTED | EXPIRED | CANCELLED | |
authorization | PurchaseAuthorizationSummary | null | |
expiresAtrequired | Timestamp | |
idrequired | UUID | |
statusrequired | active | used | expired | cancelled | `active` until `expiresAt`, unless it is used or cancelled first. An expired authorization is not renewed: request the purchase again. |
category | string | null | |
currencyrequired | Currency | |
decision | object | null | |
authorityrequired | WITHIN_POLICY | AUTHORIZED | null | |
engineVersionrequired | string | |
evaluatedAtrequired | Timestamp | |
explanationrequired | string | |
kindrequired | string | INITIAL, or APPROVAL_REEVALUATION once a person answered. |
outcomerequired | APPROVED | REJECTED | REQUIRES_APPROVAL | |
reasonCodesrequired | ReasonCode[] | |
idrequired | UUID | |
initiatedBy | object | null | |
namerequired | string | |
maximumMinorrequired | MinorAmount | null | The most the purchase can cost, with every `VARIABLE` charge at its maximum: what rules and budgets judged. `amountMinor` is the known amount. Null when no charge can still vary. |
merchantrequired | string | |
merchantDomain | string | null | |
origin | DASHBOARD | API | MCP | null | |
reasonrequired | string | |
requestedAtrequired | Timestamp | |
resolvedAt | Timestamp | null | |
statusrequired | EVALUATING | PENDING_APPROVAL | APPROVED | REJECTED | EXPIRED | CANCELLED | |
submittedVia | object | object | null |
Submit a purchase for a decision#
POST/v1/purchase-requests
API key bound to an agentOAuth: purchase:request
Asks the Trust Engine whether this agent may make this purchase, and records the answer.
All three outcomes — `APPROVED`, `REJECTED` and `REQUIRES_APPROVAL` — are successful calls. Branch on `decision.outcome`, never on the HTTP status code: the status describes whether the question could be answered, and a refusal is an answer.
`REQUIRES_APPROVAL` means a person has been asked and the budget is held until `approval.expiresAt`. Poll `GET /v1/purchase-requests/{purchaseRequestId}` for what they decided.
An `APPROVED` purchase carries a purchase authorization: an id to hand to whatever will make the purchase, and the time until which it can be used. Veriticity authorizes a purchase; it does not make one.
Requires an API key bound to an agent, or an OAuth connection granted `purchase:request`. An organization-scoped key names no identity to attribute a purchase to and is refused.
Credential: An API key bound to an agent. OAuth connections need `purchase:request`.
Rate limit: 120 per minute per credential; 600 per minute per organization.
Request body: at most 65536 bytes, or 655360 bytes when it states `items`.
Parameters
| Name | In | Description |
|---|---|---|
Idempotency-Keyrequired | header | Required. Makes a retry safe: the same key with the same body replays the original decision, reserves no further budget and returns the same purchase authorization, answering 200 rather than 201. The same key with a different body, or from a different credential, is refused with 409 `idempotency_key_reuse`. A request without one is refused with 400 `invalid_request`. This is the only operation that accepts one. |
Request body
| Field | Type | Description |
|---|---|---|
amountMinorrequired | string | integer | A whole number of ISO 4217 minor units. Send it as a string: JSON numbers lose precision above 2^53, and this is money. It is always returned as a string. |
category | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other | Required unless the body states `items`, and never with them. What kind of product this is: exactly one of Veriticity's categories. Choose by what the product is, not where it's sold or what it's for. `other` means you know what the product is and no category covers it; never use it for a product you can't identify. A purchase that holds different kinds of product, or a product that is also one of `medicines`, `gift-cards`, `alcohol`, `tobacco-nicotine`, `adult`, `gambling`, `crypto`, `recreational-drugs` besides the category you'd choose (a crypto casino deposit, an alcohol gift card), isn't described by one category: list its items instead, each with its own category and a risk tag for every other one of those it involves. Any other value is refused. Recorded as stated by the caller, not verified. `electronics`: Physical electronic devices, components and accessories: laptops, phones, chargers, hardware wallets. Not here: software (`software`), video games (`media-entertainment`). `software`: Software licences, apps, SaaS and cloud services, hosting, domains and productivity tools. Not here: music, film, streaming media and games (`media-entertainment`). `office-supplies`: Stationery, paper, printer supplies, and office furniture and equipment bought for work. Not here: personal electronics (`electronics`). `clothing`: Clothing, footwear, bags, jewellery and watches, for any age. Not here: sports equipment (`sports-outdoors`), dress-up toys (`kids-baby`). `home-garden`: Furniture, homeware, kitchenware, appliances, DIY tools and materials, garden products, and household cleaning and consumables such as washing-up liquid, bin bags and toilet roll. Not here: office furniture bought for an office (`office-supplies`). `food-drink`: Groceries, non-alcoholic drinks, meals, takeaway and food delivery, including baby food and formula. Not here: alcohol (`alcohol`), pet food (`pet-supplies`), vitamins and supplements (`health-beauty`). `health-beauty`: Toiletries, skincare, cosmetics, fragrance, hair and oral care, shaving, deodorant, sunscreen, sanitary and incontinence products, condoms and non-prescription contraception, vitamins and supplements, first-aid supplies, health devices and contact lenses. Not here: licensed medicines, including medicated creams (`medicines`), household cleaning (`home-garden`), nappies (`kids-baby`), CBD products (`recreational-drugs`). `kids-baby`: Toys, board games, puzzles, nursery equipment, nappies, and baby care that is neither food nor medicine. Not here: children's clothing (`clothing`), baby food (`food-drink`), video games (`media-entertainment`). `pet-supplies`: Pet food, accessories, bedding and non-prescription pet care. Not here: veterinary services (`services`), prescription pet medicines (`medicines`). `sports-outdoors`: Sports, fitness, camping and cycling equipment. Not here: sportswear (`clothing`), tickets to sports events (`media-entertainment`). `automotive`: Vehicle parts, accessories, tyres and maintenance products. Not here: fuel, parking and tolls (`travel`), repairs (`services`). `travel`: Flights, accommodation, rail, car hire, rides, parking, fuel and tolls. Not here: travel insurance (`services`). `media-entertainment`: Books and e-books, music, film, streaming media, video games and in-game items, and event tickets. Not here: betting stakes and casino credits (`gambling`). `services`: Professional, personal and booked services, repairs, insurance, education and veterinary services. Not here: software (`software`), event tickets (`media-entertainment`). `medicines`: Licensed medicines, prescription and over the counter, for people or pets: pain relief, cold and flu, allergy, medicated creams, pharmacy-only products and prescription refills. Not here: vitamins, supplements and unmedicated skincare (`health-beauty`), nicotine replacement products (`tobacco-nicotine`), CBD and other unlicensed psychoactive products (`recreational-drugs`). `gift-cards`: Gift cards, vouchers, prepaid cards, and top-ups of a merchant's or platform's wallet or account. Not here: deposits to crypto exchanges or wallets (`crypto`), goods paid for with a gift card (by what they are). `alcohol`: Alcoholic drinks of any strength, and alcohol gift sets. Not here: alcohol-free drinks (`food-drink`). `tobacco-nicotine`: Tobacco, cigarettes, cigars, e-cigarettes, vape devices and liquids, nicotine pouches and nicotine replacement products. Not here: cannabis vapes (`recreational-drugs`), lighters on their own (`home-garden`). `adult`: Sexual-wellness products, adult entertainment and age-restricted adult content. Not here: condoms and contraception (`health-beauty`). `gambling`: Lottery tickets, betting stakes and credits, casino credits including crypto casinos, and paid chance-based entries that can pay out money. Not here: in-game items with no cash value (`media-entertainment`). `crypto`: Buying cryptocurrency, stablecoins, tokens or NFTs, deposits to crypto exchanges or wallets, and crypto ATM purchases. Not here: hardware wallets (`electronics`), crypto casinos (`gambling`), crypto-themed merchandise (by what they are). `recreational-drugs`: Products containing cannabis or cannabinoids, including CBD, psilocybin, kratom and other psychoactive substances not licensed as medicine, and drug paraphernalia. Not here: licensed medicines (`medicines`), alcohol (`alcohol`), tobacco and nicotine (`tobacco-nicotine`). `other`: A product you can identify that no other category covers. Not for a product you can't identify. |
charges | object[] | Charges on the whole basket. `KNOWN` is exact: `TAX`, `DELIVERY`, `DUTY`, `FEE` or `DISCOUNT` (negative; a fee or discount says what it is). `UNEXPLAINED` is any part of the total you can't account for, and has no `kind`. `VARIABLE` is a `TAX`, `DELIVERY` or `DUTY` charge that can still change, stated with its `maximumMinor`: the purchase is decided at the known amount plus every maximum, and a use presents each one's final, which may not exceed it. |
amountMinor | string | |
classrequired | KNOWN | VARIABLE | UNEXPLAINED | |
description | string | At most 200 bytes. |
kind | TAX | DELIVERY | DUTY | FEE | DISCOUNT | |
maximumMinor | string | A variable charge's most. |
currency | string | null | Defaults to the organisation's own currency when omitted. |
items | object[] | The items, instead of `category`, for a purchase that holds different kinds of product or a product involving more than one restricted kind. `amountMinor` must then be exactly the item lines (each `quantity` × `unitAmountMinor` less `discountMinor`) plus the known and unexplained charges. Text limits are counted in UTF-8 bytes. |
category | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other | The item's category. Omit it only for an item whose category is uncertain. |
categoryUncertain | boolean | `true` when what kind of product this is can't be established. Send `uncertaintyReason` and no `category`; the item is then treated as not established in every category rule and budget. |
descriptionrequired | string | What the item is. At most 500 bytes. |
discountMinor | string | A discount on this line, in minor units. Defaults to 0. |
itemId | string | Your own label for the line, unique in the basket. At most 64 bytes. |
options | object[] | |
namerequired | string | |
valuerequired | string | |
possibleCategories | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other[] | For an uncertain item: up to three categories it might be. Shown to a person; never relied on. |
productReference | object | |
idrequired | string | |
providerrequired | string | |
productUrl | string | The product's https page, on `merchantDomain` or the same website with or without its `www` label. |
quantityrequired | integer | |
riskTags | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs[] | Every restricted kind the item involves besides its category: a crypto casino deposit is `gambling` tagged `crypto`. A rule on a tagged category applies to the item. |
uncertaintyReason | string | Why the category can't be established. At most 200 bytes. |
unitAmountMinorrequired | string | The price of one, in minor units. |
merchantrequired | string | Who the money would go to, written as a person would name them. A label: it is never looked up and does not identify the merchant. |
merchantDomain | string | null | The merchant's website: the host the purchase would be made on. A full URL is accepted and only its host is used. This is what identifies the merchant; without it no merchant is established, and a policy that restricts merchants refuses the purchase or holds it for a person. It identifies the merchant only where the organisation has that exact host on record for one; a host that is sent and not on record leaves the merchant unrecognised. `example.com` and `www.example.com` are the same host. Any other subdomain matches only where the organisation trusts that website's subdomains. |
merchantReference | CommerceReference | null | A commerce platform's id for the merchant. Like the domain it is a statement by the caller. It supports `merchantDomain` and never replaces it: sent without a website it establishes no merchant, and one that is not on record, or is on record for a different merchant than the website, leaves the merchant not established. |
idrequired | string | The id, exactly as the provider writes it. No spaces. |
providerrequired | string | The platform or scheme: a short lowercase name. |
metadata | object | null | Anything you want recorded alongside the purchase, as a JSON object of at most 16 KiB, nested at most 8 levels. No rule reads it. |
productReference | CommerceReference | null | An id for the product: a platform's, or a scheme such as `sku` or `gtin`. Resolved within the merchant, so it means nothing until the merchant is recognised. |
idrequired | string | The id, exactly as the provider writes it. No spaces. |
providerrequired | string | The platform or scheme: a short lowercase name. |
reasonrequired | string | Why the agent wants this purchase. Every purchase must say. |
taxIncluded | boolean | Required with `items`: whether the item prices already include tax. When `true`, tax is not stated again. |
Responses
| Status | Meaning |
|---|---|
200 | An idempotent replay. The stored decision, returned again; no new budget was reserved. `replayed` is true. |
201 | The purchase was evaluated and a decision recorded. |
400 | invalid_requestmalformed_json |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | purchase_allowance_exhaustedplan_requiredplan_limit_exceededfeature_not_included |
403 | connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scopebilling_suspended |
409 | idempotency_key_reuse |
413 | payload_too_large |
415 | unsupported_media_type |
429 | rate_limited |
500 | internal_error |
503 | temporarily_unavailable |
Response body
| Field | Type | Description |
|---|---|---|
approvalrequired | object | Whether a person still has to answer, and by when. The budget hold behind an escalation is released when the approval lapses. |
expiresAtrequired | Timestamp | null | |
requiredrequired | boolean | |
authorizationrequired | PurchaseAuthorizationSummary | null | |
expiresAtrequired | Timestamp | |
idrequired | UUID | |
statusrequired | active | used | expired | cancelled | `active` until `expiresAt`, unless it is used or cancelled first. An expired authorization is not renewed: request the purchase again. |
decisionrequired | Decision | |
authority | WITHIN_POLICY | AUTHORIZED | null | |
engineVersionrequired | string | |
evaluatedAtrequired | Timestamp | |
explanationrequired | string | The sentence a person reads. Not a stable contract. |
idrequired | UUID | |
outcomerequired | APPROVED | REJECTED | REQUIRES_APPROVAL | Branch on this, never on the HTTP status code. |
reasonCodesrequired | ReasonCode[] | |
findingsrequired | Finding[] | |
currency | Currency | null | |
headroomMinor | MinorAmount | null | |
limitMinor | MinorAmount | null | |
measured | MeasuredWindow | null | |
aggregaterequired | SUM_AMOUNT | COUNT | SUM_AMOUNT: a sum of money. COUNT: a number of purchases. |
scoperequired | string | Whose purchases the window holds: POLICY (the agents the policy binds), ORGANIZATION, EACH_AGENT, AGENT:<agent id>, EACH_PRINCIPAL or PRINCIPAL:<user id>. |
windowrequired | object | |
messagerequired | string | |
observedMinor | MinorAmount | null | |
outcomerequired | PASS | NOT_APPLICABLE | FLAG | REQUIRE_APPROVAL | BLOCK | AUTHORIZED | PASS: the purchase came within the rule. AUTHORIZED: the rule asked for approval and a person gave it. FLAG: recorded, and changes nothing. A rule that only flags is the organisation's own record and is not returned by this API. |
policyIdrequired | UUID | |
policyNamerequired | string | |
policyRuleId | UUID | null | |
policyVersionIdrequired | UUID | |
policyVersionNumberrequired | integer | |
reasonCoderequired | ReasonCode | |
rolerequired | BINDING | SATISFIED | NOT_APPLICABLE | How the finding stands against the decision. |
rulerequired | string | The rule's name, or its key when it has none. |
ruleKey | string | null | |
unit | MONEY | COUNT | null | |
policyVersionsrequired | object[] | |
applicabilityReasonrequired | string | |
policyVersionIdrequired | UUID | |
purchaseRequestrequired | PurchaseRequestSummary | |
amountMinorrequired | MinorAmount | |
currencyrequired | Currency | |
idrequired | UUID | |
maximumMinorrequired | MinorAmount | null | The most the purchase can cost, with every `VARIABLE` charge at its maximum: what rules and budgets judged. `amountMinor` is the known amount. Null when no charge can still vary. |
statusrequired | EVALUATING | PENDING_APPROVAL | APPROVED | REJECTED | EXPIRED | CANCELLED | |
replayedrequired | boolean | True when this is the stored answer to an earlier identical request. The decision is real and binding either way; what differs is that no new budget was reserved. |
Read back a purchase you submitted#
GET/v1/purchase-requests/{purchaseRequestId}
API key bound to an agentOAuth: purchase:read
What happened to a purchase, including the decision in force and the approval if one was opened. This is how you find out what a person decided after a `REQUIRES_APPROVAL`.
A purchase is readable by the credential that submitted it and by nothing else. Another agent's, another connection's, another tenant's and a malformed id are all the same 404.
Reads nothing into the audit trail and re-evaluates nothing.
Credential: An API key bound to an agent. OAuth connections need `purchase:read`.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
purchaseRequestIdrequired | path | The purchase request to read. |
Responses
| Status | Meaning |
|---|---|
200 | The purchase, as its submitter may see it. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_included |
403 | connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
purchaserequired | PurchaseReadback | |
agentrequired | object | |
idrequired | UUID | |
namerequired | string | |
statusrequired | ENABLED | DISABLED | ARCHIVED | |
amountMinorrequired | MinorAmount | |
approval | object | null | |
commentrequired | string | null | |
escalationReasonCodesrequired | ReasonCode[] | |
expiresAtrequired | Timestamp | |
isPendingrequired | boolean | |
resolvedAtrequired | Timestamp | null | |
resolvedByrequired | object | null | |
staterequired | PENDING | APPROVED | REJECTED | EXPIRED | CANCELLED | |
authorization | PurchaseAuthorizationSummary | null | |
expiresAtrequired | Timestamp | |
idrequired | UUID | |
statusrequired | active | used | expired | cancelled | `active` until `expiresAt`, unless it is used or cancelled first. An expired authorization is not renewed: request the purchase again. |
category | string | null | |
currencyrequired | Currency | |
decision | object | null | |
authorityrequired | WITHIN_POLICY | AUTHORIZED | null | |
engineVersionrequired | string | |
evaluatedAtrequired | Timestamp | |
explanationrequired | string | |
kindrequired | string | INITIAL, or APPROVAL_REEVALUATION once a person answered. |
outcomerequired | APPROVED | REJECTED | REQUIRES_APPROVAL | |
reasonCodesrequired | ReasonCode[] | |
idrequired | UUID | |
initiatedBy | object | null | |
namerequired | string | |
maximumMinorrequired | MinorAmount | null | The most the purchase can cost, with every `VARIABLE` charge at its maximum: what rules and budgets judged. `amountMinor` is the known amount. Null when no charge can still vary. |
merchantrequired | string | |
merchantDomain | string | null | |
origin | DASHBOARD | API | MCP | null | |
reasonrequired | string | |
requestedAtrequired | Timestamp | |
resolvedAt | Timestamp | null | |
statusrequired | EVALUATING | PENDING_APPROVAL | APPROVED | REJECTED | EXPIRED | CANCELLED | |
submittedVia | object | object | null |
Cancel a purchase that is waiting for approval#
POST/v1/purchase-requests/{purchaseRequestId}/cancel
API key of either kindOAuth: purchase:request
Takes back a request nobody has approved yet. The approval is no longer asked for, the budget it was holding is released, and the purchase becomes `CANCELLED`.
Only a request still waiting for approval can be cancelled. Anything else is refused with `purchase_authorization_conflict`. An approved purchase is ended by withdrawing its authorization, which an agent's credential cannot do.
The credential that submitted the request may cancel it, and so may an organization-scoped key. Another credential's request is a 404.
Credential: An API key of either kind. OAuth connections need `purchase:request`.
Rate limit: 120 per minute per credential.
Request body: at most 16384 bytes.
Parameters
| Name | In | Description |
|---|---|---|
purchaseRequestIdrequired | path | The purchase request to cancel. |
Request body (optional)
| Field | Type | Description |
|---|---|---|
reason | string | null | Shown to your organization's members in the audit trail. |
Responses
| Status | Meaning |
|---|---|
200 | The purchase, cancelled. |
400 | invalid_requestmalformed_json |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_included |
403 | connection_unauthorizedorganization_suspendedinsufficient_scope |
404 | not_found |
409 | purchase_authorization_conflict |
413 | payload_too_large |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
purchaseRequestrequired | object | |
idrequired | UUID | |
statusrequired | CANCELLED | |
replayedrequired | boolean |
Report what became of an approved purchase#
POST/v1/purchase-requests/{purchaseRequestId}/outcome
API key of either kindOAuth: purchase:request
Says what happened to a purchase this credential submitted and that was authorized: `COMPLETED` for `finalAmountMinor`, `COMPLETED_AMOUNT_UNKNOWN`, `PARTIAL` for the items in `itemPositions`, `FAILED`, or `REFUNDED` by `refundMinor`.
A report is the agent's word. It is recorded as reported by the assistant, never as verified. Under the organisation's default policy it is shown and changes nothing until a person confirms what was charged; an organisation that trusts assistant reports accepts it. It never approves a purchase or revives an authorization, and it makes, changes or cancels no order or payment.
An amount above what was approved is recorded and flagged for review. A final for each variable charge may be sent in `variableFinal`; `finalAmountMinor` is then the known amount plus every final.
Send a `reportId` with each report. The same report again returns `replayed: true` and records nothing; a different report under a used `reportId` is `report_id_reuse`. A later report with a new `reportId` replaces this credential's earlier one; a refund adds to it.
Only the credential that submitted the purchase may report on it. Anything else, including an organization-scoped key, is a 404. A purchase that was never authorized is `purchase_outcome_conflict`, and so is `REFUNDED` while the authorization can still be used: report a refund once it has been used or withdrawn.
Credential: An API key of either kind. OAuth connections need `purchase:request`.
Rate limit: 60 per minute per credential; 300 per minute per organization.
Request body: at most 16384 bytes.
Parameters
| Name | In | Description |
|---|---|---|
purchaseRequestIdrequired | path | The purchase request the report is about. |
Request body
| Field | Type | Description |
|---|---|---|
currencyrequired | string | The purchase's currency. |
finalAmountMinor | string | What was charged in total. COMPLETED and PARTIAL only, and required for both. |
itemPositions | integer[] | PARTIAL only: the bought items, by their zero-based position in the request's `items`. |
merchantOrderReference | string | At most 255 bytes in UTF-8 once trimmed, with no control characters. |
observedAt | string | When the outcome was seen. Not in the future. |
outcomerequired | COMPLETED | COMPLETED_AMOUNT_UNKNOWN | PARTIAL | FAILED | REFUNDED | |
refundMinor | string | REFUNDED only: the amount refunded. |
reportIdrequired | string | |
variableFinal | object | COMPLETED only: each variable charge's kind and its final. Exactly the purchase's variable charges. |
DELIVERY | string | |
DUTY | string | |
TAX | string |
Responses
| Status | Meaning |
|---|---|
200 | The report, recorded, and what the purchase is recorded at now. |
400 | invalid_requestmalformed_json |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_included |
403 | connection_unauthorizedorganization_suspendedinsufficient_scope |
404 | not_found |
409 | report_id_reusepurchase_outcome_conflict |
413 | payload_too_large |
415 | unsupported_media_type |
429 | rate_limited |
500 | internal_error |
503 | temporarily_unavailable |
Response body
| Field | Type | Description |
|---|---|---|
outcomeReportrequired | object | |
purchaseRequestIdrequired | UUID | |
recordedrequired | true | |
reportIdrequired | string | |
sourcerequired | AGENT_REPORT | |
purchaserequired | objectnull | The purchase the report was recorded against, to check it was the one meant. |
reconciliationrequired | object | |
countedMinorrequired | string | What it is recorded at: exposure until an accepted outcome says otherwise. |
currencyrequired | string | |
exposureMinorrequired | string | The most the purchase was approved for. |
outcomerequired | NOT_REPORTED | COMPLETED | COMPLETED_AMOUNT_UNKNOWN | PARTIAL | FAILED | REFUNDED | CONFLICTING | |
staterequired | UNRECONCILED | RECONCILED | RECONCILED_WITH_EXCEPTION | CORRECTED | |
replayedrequired | boolean |
Purchase authorizations#
What an approved purchase issues: permission for one purchase on exact terms, until a stated time. Veriticity authorizes a purchase and never makes one.
Simulations#
Ask what would happen, without anything happening. Simulations create no records and consume no budget.
Ask what would happen right now#
POST/v1/simulations/current-policies
API key of either kindOAuth: purchase:check
Runs the same evaluation `POST /v1/purchase-requests` would run, against the policies currently in force, and changes nothing: no purchase request, no decision, no budget reservation, no audit evidence, and no budget consumed.
This is how an agent finds out what it may spend before committing to asking.
Both API-key kinds reach it, and they behave differently: - an organization-scoped key must send `agentId` - an agent-bound key must not, and simulates as itself - an OAuth connection must not, and simulates as its agent
Credential: An API key of either kind. OAuth connections need `purchase:check`.
Rate limit: 120 per minute per credential.
Request body: at most 65536 bytes, or 655360 bytes when it states `items`.
Request body
| Field | Type | Description |
|---|---|---|
agentId | UUID | null | Required for an organization-scoped key. Must be omitted by an agent-bound key or an OAuth connection, which simulate as themselves — a credential choosing which agent it asks about would be choosing its own authority. |
amountMinorrequired | string | integer | A whole number of ISO 4217 minor units. Send it as a string: JSON numbers lose precision above 2^53, and this is money. It is always returned as a string. |
category | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other | Required unless the body states `items`, and never with them. What kind of product this is: exactly one of Veriticity's categories. Choose by what the product is, not where it's sold or what it's for. `other` means you know what the product is and no category covers it; never use it for a product you can't identify. A purchase that holds different kinds of product, or a product that is also one of `medicines`, `gift-cards`, `alcohol`, `tobacco-nicotine`, `adult`, `gambling`, `crypto`, `recreational-drugs` besides the category you'd choose (a crypto casino deposit, an alcohol gift card), isn't described by one category: list its items instead, each with its own category and a risk tag for every other one of those it involves. Any other value is refused. Recorded as stated by the caller, not verified. `electronics`: Physical electronic devices, components and accessories: laptops, phones, chargers, hardware wallets. Not here: software (`software`), video games (`media-entertainment`). `software`: Software licences, apps, SaaS and cloud services, hosting, domains and productivity tools. Not here: music, film, streaming media and games (`media-entertainment`). `office-supplies`: Stationery, paper, printer supplies, and office furniture and equipment bought for work. Not here: personal electronics (`electronics`). `clothing`: Clothing, footwear, bags, jewellery and watches, for any age. Not here: sports equipment (`sports-outdoors`), dress-up toys (`kids-baby`). `home-garden`: Furniture, homeware, kitchenware, appliances, DIY tools and materials, garden products, and household cleaning and consumables such as washing-up liquid, bin bags and toilet roll. Not here: office furniture bought for an office (`office-supplies`). `food-drink`: Groceries, non-alcoholic drinks, meals, takeaway and food delivery, including baby food and formula. Not here: alcohol (`alcohol`), pet food (`pet-supplies`), vitamins and supplements (`health-beauty`). `health-beauty`: Toiletries, skincare, cosmetics, fragrance, hair and oral care, shaving, deodorant, sunscreen, sanitary and incontinence products, condoms and non-prescription contraception, vitamins and supplements, first-aid supplies, health devices and contact lenses. Not here: licensed medicines, including medicated creams (`medicines`), household cleaning (`home-garden`), nappies (`kids-baby`), CBD products (`recreational-drugs`). `kids-baby`: Toys, board games, puzzles, nursery equipment, nappies, and baby care that is neither food nor medicine. Not here: children's clothing (`clothing`), baby food (`food-drink`), video games (`media-entertainment`). `pet-supplies`: Pet food, accessories, bedding and non-prescription pet care. Not here: veterinary services (`services`), prescription pet medicines (`medicines`). `sports-outdoors`: Sports, fitness, camping and cycling equipment. Not here: sportswear (`clothing`), tickets to sports events (`media-entertainment`). `automotive`: Vehicle parts, accessories, tyres and maintenance products. Not here: fuel, parking and tolls (`travel`), repairs (`services`). `travel`: Flights, accommodation, rail, car hire, rides, parking, fuel and tolls. Not here: travel insurance (`services`). `media-entertainment`: Books and e-books, music, film, streaming media, video games and in-game items, and event tickets. Not here: betting stakes and casino credits (`gambling`). `services`: Professional, personal and booked services, repairs, insurance, education and veterinary services. Not here: software (`software`), event tickets (`media-entertainment`). `medicines`: Licensed medicines, prescription and over the counter, for people or pets: pain relief, cold and flu, allergy, medicated creams, pharmacy-only products and prescription refills. Not here: vitamins, supplements and unmedicated skincare (`health-beauty`), nicotine replacement products (`tobacco-nicotine`), CBD and other unlicensed psychoactive products (`recreational-drugs`). `gift-cards`: Gift cards, vouchers, prepaid cards, and top-ups of a merchant's or platform's wallet or account. Not here: deposits to crypto exchanges or wallets (`crypto`), goods paid for with a gift card (by what they are). `alcohol`: Alcoholic drinks of any strength, and alcohol gift sets. Not here: alcohol-free drinks (`food-drink`). `tobacco-nicotine`: Tobacco, cigarettes, cigars, e-cigarettes, vape devices and liquids, nicotine pouches and nicotine replacement products. Not here: cannabis vapes (`recreational-drugs`), lighters on their own (`home-garden`). `adult`: Sexual-wellness products, adult entertainment and age-restricted adult content. Not here: condoms and contraception (`health-beauty`). `gambling`: Lottery tickets, betting stakes and credits, casino credits including crypto casinos, and paid chance-based entries that can pay out money. Not here: in-game items with no cash value (`media-entertainment`). `crypto`: Buying cryptocurrency, stablecoins, tokens or NFTs, deposits to crypto exchanges or wallets, and crypto ATM purchases. Not here: hardware wallets (`electronics`), crypto casinos (`gambling`), crypto-themed merchandise (by what they are). `recreational-drugs`: Products containing cannabis or cannabinoids, including CBD, psilocybin, kratom and other psychoactive substances not licensed as medicine, and drug paraphernalia. Not here: licensed medicines (`medicines`), alcohol (`alcohol`), tobacco and nicotine (`tobacco-nicotine`). `other`: A product you can identify that no other category covers. Not for a product you can't identify. |
charges | object[] | Charges on the whole basket. `KNOWN` is exact: `TAX`, `DELIVERY`, `DUTY`, `FEE` or `DISCOUNT` (negative; a fee or discount says what it is). `UNEXPLAINED` is any part of the total you can't account for, and has no `kind`. `VARIABLE` is a `TAX`, `DELIVERY` or `DUTY` charge that can still change, stated with its `maximumMinor`: the purchase is decided at the known amount plus every maximum, and a use presents each one's final, which may not exceed it. |
amountMinor | string | |
classrequired | KNOWN | VARIABLE | UNEXPLAINED | |
description | string | At most 200 bytes. |
kind | TAX | DELIVERY | DUTY | FEE | DISCOUNT | |
maximumMinor | string | A variable charge's most. |
currency | string | null | Defaults to the organisation's own currency when omitted. |
items | object[] | The items, instead of `category`, for a purchase that holds different kinds of product or a product involving more than one restricted kind. `amountMinor` must then be exactly the item lines (each `quantity` × `unitAmountMinor` less `discountMinor`) plus the known and unexplained charges. Text limits are counted in UTF-8 bytes. |
category | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other | The item's category. Omit it only for an item whose category is uncertain. |
categoryUncertain | boolean | `true` when what kind of product this is can't be established. Send `uncertaintyReason` and no `category`; the item is then treated as not established in every category rule and budget. |
descriptionrequired | string | What the item is. At most 500 bytes. |
discountMinor | string | A discount on this line, in minor units. Defaults to 0. |
itemId | string | Your own label for the line, unique in the basket. At most 64 bytes. |
options | object[] | |
namerequired | string | |
valuerequired | string | |
possibleCategories | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other[] | For an uncertain item: up to three categories it might be. Shown to a person; never relied on. |
productReference | object | |
idrequired | string | |
providerrequired | string | |
productUrl | string | The product's https page, on `merchantDomain` or the same website with or without its `www` label. |
quantityrequired | integer | |
riskTags | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs[] | Every restricted kind the item involves besides its category: a crypto casino deposit is `gambling` tagged `crypto`. A rule on a tagged category applies to the item. |
uncertaintyReason | string | Why the category can't be established. At most 200 bytes. |
unitAmountMinorrequired | string | The price of one, in minor units. |
merchantrequired | string | Who the money would go to, written as a person would name them. A label: it is never looked up and does not identify the merchant. |
merchantDomain | string | null | The merchant's website: the host the purchase would be made on. A full URL is accepted and only its host is used. This is what identifies the merchant; without it no merchant is established, and a policy that restricts merchants refuses the purchase or holds it for a person. It identifies the merchant only where the organisation has that exact host on record for one; a host that is sent and not on record leaves the merchant unrecognised. `example.com` and `www.example.com` are the same host. Any other subdomain matches only where the organisation trusts that website's subdomains. |
merchantReference | CommerceReference | null | A commerce platform's id for the merchant. Like the domain it is a statement by the caller. It supports `merchantDomain` and never replaces it: sent without a website it establishes no merchant, and one that is not on record, or is on record for a different merchant than the website, leaves the merchant not established. |
idrequired | string | The id, exactly as the provider writes it. No spaces. |
providerrequired | string | The platform or scheme: a short lowercase name. |
productReference | CommerceReference | null | An id for the product: a platform's, or a scheme such as `sku` or `gtin`. Resolved within the merchant, so it means nothing until the merchant is recognised. |
idrequired | string | The id, exactly as the provider writes it. No spaces. |
providerrequired | string | The platform or scheme: a short lowercase name. |
reasonrequired | string | Why the agent wants this purchase. Every purchase must say. |
taxIncluded | boolean | Required with `items`: whether the item prices already include tax. When `true`, tax is not stated again. |
Responses
| Status | Meaning |
|---|---|
200 | What would have happened. Nothing was changed. |
400 | invalid_requestmalformed_json |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_included |
403 | connection_unauthorizedorganization_suspendedinsufficient_scope |
404 | not_found |
413 | payload_too_large |
415 | unsupported_media_type |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
agentrequired | object | null | |
id | UUID | |
name | string | |
status | ENABLED | DISABLED | ARCHIVED | |
budgetsrequired | SimulatedBudget[] | |
aggregate | SUM_AMOUNT | COUNT | SUM_AMOUNT: the figures are minor units. COUNT: a number of purchases. |
currency | Currency | null | |
current | object | |
committedMinor | MinorAmount | |
heldMinor | MinorAmount | |
reservedMinor | MinorAmount | |
limits | object[] | |
effect | BLOCK | REQUIRE_APPROVAL | FLAG | |
limitMinor | MinorAmount | |
policyId | UUID | |
policyName | string | |
policyRuleId | UUID | null | |
policyVersionId | UUID | |
remainingAfterMinor | MinorAmount | Negative reads as "over by". |
remainingBeforeMinor | MinorAmount | |
ruleKey | string | null | |
wouldExceed | boolean | |
periodEnd | Timestamp | |
periodKey | string | |
periodKind | string | DAY, WEEK, MONTH, QUARTER or YEAR for a calendar window; ROLLING otherwise. |
periodStart | Timestamp | |
rollingSeconds | integer | null | |
scope | string | Whose purchases the window holds: ORGANIZATION, POLICY:<policy id>, AGENT:<agent id> or PRINCIPAL:<user:id | organization>. |
simulated | object | |
projectedMinor | MinorAmount | |
requestedMinor | MinorAmount | |
timeZone | string | null | |
wouldExceed | boolean | |
findingsrequired | Finding[] | |
currency | Currency | null | |
headroomMinor | MinorAmount | null | |
limitMinor | MinorAmount | null | |
measured | MeasuredWindow | null | |
aggregaterequired | SUM_AMOUNT | COUNT | SUM_AMOUNT: a sum of money. COUNT: a number of purchases. |
scoperequired | string | Whose purchases the window holds: POLICY (the agents the policy binds), ORGANIZATION, EACH_AGENT, AGENT:<agent id>, EACH_PRINCIPAL or PRINCIPAL:<user id>. |
windowrequired | object | |
messagerequired | string | |
observedMinor | MinorAmount | null | |
outcomerequired | PASS | NOT_APPLICABLE | FLAG | REQUIRE_APPROVAL | BLOCK | AUTHORIZED | PASS: the purchase came within the rule. AUTHORIZED: the rule asked for approval and a person gave it. FLAG: recorded, and changes nothing. A rule that only flags is the organisation's own record and is not returned by this API. |
policyIdrequired | UUID | |
policyNamerequired | string | |
policyRuleId | UUID | null | |
policyVersionIdrequired | UUID | |
policyVersionNumberrequired | integer | |
reasonCoderequired | ReasonCode | |
rolerequired | BINDING | SATISFIED | NOT_APPLICABLE | How the finding stands against the decision. |
rulerequired | string | The rule's name, or its key when it has none. |
ruleKey | string | null | |
unit | MONEY | COUNT | null | |
policiesConsideredrequired | object[] | Every candidate policy version. One whose participation is NOT_APPLICABLE governs the agent but is not about this purchase: it changed nothing, and omitting it would make a NO_APPLICABLE_POLICY answer impossible to act on. |
applicabilityReason | string | |
participation | PARTICIPATING | NOT_APPLICABLE | |
policyId | UUID | |
policyName | string | |
policyStatus | ACTIVE | DISABLED | ARCHIVED | |
policyVersionId | UUID | |
versionNumber | integer | |
versionStatus | DRAFT | ACTIVE | ARCHIVED | |
purchaserequired | object | |
amountMinor | MinorAmount | |
category | string | null | |
currency | Currency | |
currencyExponent | integer | |
maximumMinor | MinorAmount | null | The most the purchase can cost, with every `VARIABLE` charge at its maximum: what rules and budgets judged. `amountMinor` is the known amount. Null when no charge can still vary. |
merchant | string | |
merchantDomain | string | null | |
merchantKey | string | |
simulatedDecisionrequired | object | |
advancerequired | COMPLETE | PARTIAL | NOT_DECIDED | How much of the decision this answer is. COMPLETE: every rule was checked. PARTIAL: some rules are checked on request, and the others already refuse, so the refusal stands. NOT_DECIDED: some rules are checked on request and no outcome is given. |
authority | WITHIN_POLICY | AUTHORIZED | null | |
deferredRulesrequired | object[] | The rules checked when a purchase is requested, not in advance. Empty when advance is COMPLETE. |
policyIdrequired | string | |
policyNamerequired | string | |
rulerequired | string | |
ruleKeyrequired | string | |
explanationrequired | string | |
outcomerequired | APPROVED | REJECTED | REQUIRES_APPROVAL | NOT_DECIDED_IN_ADVANCE | What a purchase request would be decided as. NOT_DECIDED_IN_ADVANCE when some rules are checked only when a purchase is requested and the others do not already refuse: request the purchase to find out. |
reasonCodesrequired | ReasonCode[] | |
wouldRequireApprovalrequired | boolean | False when the outcome is NOT_DECIDED_IN_ADVANCE: it is not known. |
simulationrequired | object | |
disclaimerrequired | string | |
engineVersionrequired | string | |
isSimulationrequired | true | Always true. The field exists so its absence is a signal. |
moderequired | string | |
simulatedAtrequired | Timestamp |
Ask what a draft policy version would decide#
POST/v1/simulations/draft-policy
Organisation-scoped API keyNo OAuth caller admitted
Evaluates a purchase against one `DRAFT` policy version as it is currently written — the question you ask before activating it.
Organization-scoped API keys only. An agent may not read or simulate the guardrails being drafted for it, and no OAuth scope reaches this.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 120 per minute per credential.
Request body: at most 65536 bytes, or 655360 bytes when it states `items`.
Request body
| Field | Type | Description |
|---|---|---|
amountMinorrequired | string | integer | A whole number of ISO 4217 minor units. Send it as a string: JSON numbers lose precision above 2^53, and this is money. It is always returned as a string. |
category | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other | Required unless the body states `items`, and never with them. What kind of product this is: exactly one of Veriticity's categories. Choose by what the product is, not where it's sold or what it's for. `other` means you know what the product is and no category covers it; never use it for a product you can't identify. A purchase that holds different kinds of product, or a product that is also one of `medicines`, `gift-cards`, `alcohol`, `tobacco-nicotine`, `adult`, `gambling`, `crypto`, `recreational-drugs` besides the category you'd choose (a crypto casino deposit, an alcohol gift card), isn't described by one category: list its items instead, each with its own category and a risk tag for every other one of those it involves. Any other value is refused. Recorded as stated by the caller, not verified. `electronics`: Physical electronic devices, components and accessories: laptops, phones, chargers, hardware wallets. Not here: software (`software`), video games (`media-entertainment`). `software`: Software licences, apps, SaaS and cloud services, hosting, domains and productivity tools. Not here: music, film, streaming media and games (`media-entertainment`). `office-supplies`: Stationery, paper, printer supplies, and office furniture and equipment bought for work. Not here: personal electronics (`electronics`). `clothing`: Clothing, footwear, bags, jewellery and watches, for any age. Not here: sports equipment (`sports-outdoors`), dress-up toys (`kids-baby`). `home-garden`: Furniture, homeware, kitchenware, appliances, DIY tools and materials, garden products, and household cleaning and consumables such as washing-up liquid, bin bags and toilet roll. Not here: office furniture bought for an office (`office-supplies`). `food-drink`: Groceries, non-alcoholic drinks, meals, takeaway and food delivery, including baby food and formula. Not here: alcohol (`alcohol`), pet food (`pet-supplies`), vitamins and supplements (`health-beauty`). `health-beauty`: Toiletries, skincare, cosmetics, fragrance, hair and oral care, shaving, deodorant, sunscreen, sanitary and incontinence products, condoms and non-prescription contraception, vitamins and supplements, first-aid supplies, health devices and contact lenses. Not here: licensed medicines, including medicated creams (`medicines`), household cleaning (`home-garden`), nappies (`kids-baby`), CBD products (`recreational-drugs`). `kids-baby`: Toys, board games, puzzles, nursery equipment, nappies, and baby care that is neither food nor medicine. Not here: children's clothing (`clothing`), baby food (`food-drink`), video games (`media-entertainment`). `pet-supplies`: Pet food, accessories, bedding and non-prescription pet care. Not here: veterinary services (`services`), prescription pet medicines (`medicines`). `sports-outdoors`: Sports, fitness, camping and cycling equipment. Not here: sportswear (`clothing`), tickets to sports events (`media-entertainment`). `automotive`: Vehicle parts, accessories, tyres and maintenance products. Not here: fuel, parking and tolls (`travel`), repairs (`services`). `travel`: Flights, accommodation, rail, car hire, rides, parking, fuel and tolls. Not here: travel insurance (`services`). `media-entertainment`: Books and e-books, music, film, streaming media, video games and in-game items, and event tickets. Not here: betting stakes and casino credits (`gambling`). `services`: Professional, personal and booked services, repairs, insurance, education and veterinary services. Not here: software (`software`), event tickets (`media-entertainment`). `medicines`: Licensed medicines, prescription and over the counter, for people or pets: pain relief, cold and flu, allergy, medicated creams, pharmacy-only products and prescription refills. Not here: vitamins, supplements and unmedicated skincare (`health-beauty`), nicotine replacement products (`tobacco-nicotine`), CBD and other unlicensed psychoactive products (`recreational-drugs`). `gift-cards`: Gift cards, vouchers, prepaid cards, and top-ups of a merchant's or platform's wallet or account. Not here: deposits to crypto exchanges or wallets (`crypto`), goods paid for with a gift card (by what they are). `alcohol`: Alcoholic drinks of any strength, and alcohol gift sets. Not here: alcohol-free drinks (`food-drink`). `tobacco-nicotine`: Tobacco, cigarettes, cigars, e-cigarettes, vape devices and liquids, nicotine pouches and nicotine replacement products. Not here: cannabis vapes (`recreational-drugs`), lighters on their own (`home-garden`). `adult`: Sexual-wellness products, adult entertainment and age-restricted adult content. Not here: condoms and contraception (`health-beauty`). `gambling`: Lottery tickets, betting stakes and credits, casino credits including crypto casinos, and paid chance-based entries that can pay out money. Not here: in-game items with no cash value (`media-entertainment`). `crypto`: Buying cryptocurrency, stablecoins, tokens or NFTs, deposits to crypto exchanges or wallets, and crypto ATM purchases. Not here: hardware wallets (`electronics`), crypto casinos (`gambling`), crypto-themed merchandise (by what they are). `recreational-drugs`: Products containing cannabis or cannabinoids, including CBD, psilocybin, kratom and other psychoactive substances not licensed as medicine, and drug paraphernalia. Not here: licensed medicines (`medicines`), alcohol (`alcohol`), tobacco and nicotine (`tobacco-nicotine`). `other`: A product you can identify that no other category covers. Not for a product you can't identify. |
charges | object[] | Charges on the whole basket. `KNOWN` is exact: `TAX`, `DELIVERY`, `DUTY`, `FEE` or `DISCOUNT` (negative; a fee or discount says what it is). `UNEXPLAINED` is any part of the total you can't account for, and has no `kind`. `VARIABLE` is a `TAX`, `DELIVERY` or `DUTY` charge that can still change, stated with its `maximumMinor`: the purchase is decided at the known amount plus every maximum, and a use presents each one's final, which may not exceed it. |
amountMinor | string | |
classrequired | KNOWN | VARIABLE | UNEXPLAINED | |
description | string | At most 200 bytes. |
kind | TAX | DELIVERY | DUTY | FEE | DISCOUNT | |
maximumMinor | string | A variable charge's most. |
currency | string | null | Defaults to the organisation's own currency when omitted. |
items | object[] | The items, instead of `category`, for a purchase that holds different kinds of product or a product involving more than one restricted kind. `amountMinor` must then be exactly the item lines (each `quantity` × `unitAmountMinor` less `discountMinor`) plus the known and unexplained charges. Text limits are counted in UTF-8 bytes. |
category | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other | The item's category. Omit it only for an item whose category is uncertain. |
categoryUncertain | boolean | `true` when what kind of product this is can't be established. Send `uncertaintyReason` and no `category`; the item is then treated as not established in every category rule and budget. |
descriptionrequired | string | What the item is. At most 500 bytes. |
discountMinor | string | A discount on this line, in minor units. Defaults to 0. |
itemId | string | Your own label for the line, unique in the basket. At most 64 bytes. |
options | object[] | |
namerequired | string | |
valuerequired | string | |
possibleCategories | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other[] | For an uncertain item: up to three categories it might be. Shown to a person; never relied on. |
productReference | object | |
idrequired | string | |
providerrequired | string | |
productUrl | string | The product's https page, on `merchantDomain` or the same website with or without its `www` label. |
quantityrequired | integer | |
riskTags | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs[] | Every restricted kind the item involves besides its category: a crypto casino deposit is `gambling` tagged `crypto`. A rule on a tagged category applies to the item. |
uncertaintyReason | string | Why the category can't be established. At most 200 bytes. |
unitAmountMinorrequired | string | The price of one, in minor units. |
merchantrequired | string | Who the money would go to, written as a person would name them. A label: it is never looked up and does not identify the merchant. |
merchantDomain | string | null | The merchant's website: the host the purchase would be made on. A full URL is accepted and only its host is used. This is what identifies the merchant; without it no merchant is established, and a policy that restricts merchants refuses the purchase or holds it for a person. It identifies the merchant only where the organisation has that exact host on record for one; a host that is sent and not on record leaves the merchant unrecognised. `example.com` and `www.example.com` are the same host. Any other subdomain matches only where the organisation trusts that website's subdomains. |
merchantReference | CommerceReference | null | A commerce platform's id for the merchant. Like the domain it is a statement by the caller. It supports `merchantDomain` and never replaces it: sent without a website it establishes no merchant, and one that is not on record, or is on record for a different merchant than the website, leaves the merchant not established. |
idrequired | string | The id, exactly as the provider writes it. No spaces. |
providerrequired | string | The platform or scheme: a short lowercase name. |
policyVersionIdrequired | UUID | The draft version to evaluate. Name the version, not the policy. |
productReference | CommerceReference | null | An id for the product: a platform's, or a scheme such as `sku` or `gtin`. Resolved within the merchant, so it means nothing until the merchant is recognised. |
idrequired | string | The id, exactly as the provider writes it. No spaces. |
providerrequired | string | The platform or scheme: a short lowercase name. |
reasonrequired | string | Why the agent wants this purchase. Every purchase must say. |
taxIncluded | boolean | Required with `items`: whether the item prices already include tax. When `true`, tax is not stated again. |
Responses
| Status | Meaning |
|---|---|
200 | What that draft would have decided. |
400 | invalid_requestmalformed_json |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
413 | payload_too_large |
415 | unsupported_media_type |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
agentrequired | object | null | |
id | UUID | |
name | string | |
status | ENABLED | DISABLED | ARCHIVED | |
budgetsrequired | SimulatedBudget[] | |
aggregate | SUM_AMOUNT | COUNT | SUM_AMOUNT: the figures are minor units. COUNT: a number of purchases. |
currency | Currency | null | |
current | object | |
committedMinor | MinorAmount | |
heldMinor | MinorAmount | |
reservedMinor | MinorAmount | |
limits | object[] | |
effect | BLOCK | REQUIRE_APPROVAL | FLAG | |
limitMinor | MinorAmount | |
policyId | UUID | |
policyName | string | |
policyRuleId | UUID | null | |
policyVersionId | UUID | |
remainingAfterMinor | MinorAmount | Negative reads as "over by". |
remainingBeforeMinor | MinorAmount | |
ruleKey | string | null | |
wouldExceed | boolean | |
periodEnd | Timestamp | |
periodKey | string | |
periodKind | string | DAY, WEEK, MONTH, QUARTER or YEAR for a calendar window; ROLLING otherwise. |
periodStart | Timestamp | |
rollingSeconds | integer | null | |
scope | string | Whose purchases the window holds: ORGANIZATION, POLICY:<policy id>, AGENT:<agent id> or PRINCIPAL:<user:id | organization>. |
simulated | object | |
projectedMinor | MinorAmount | |
requestedMinor | MinorAmount | |
timeZone | string | null | |
wouldExceed | boolean | |
findingsrequired | Finding[] | |
currency | Currency | null | |
headroomMinor | MinorAmount | null | |
limitMinor | MinorAmount | null | |
measured | MeasuredWindow | null | |
aggregaterequired | SUM_AMOUNT | COUNT | SUM_AMOUNT: a sum of money. COUNT: a number of purchases. |
scoperequired | string | Whose purchases the window holds: POLICY (the agents the policy binds), ORGANIZATION, EACH_AGENT, AGENT:<agent id>, EACH_PRINCIPAL or PRINCIPAL:<user id>. |
windowrequired | object | |
messagerequired | string | |
observedMinor | MinorAmount | null | |
outcomerequired | PASS | NOT_APPLICABLE | FLAG | REQUIRE_APPROVAL | BLOCK | AUTHORIZED | PASS: the purchase came within the rule. AUTHORIZED: the rule asked for approval and a person gave it. FLAG: recorded, and changes nothing. A rule that only flags is the organisation's own record and is not returned by this API. |
policyIdrequired | UUID | |
policyNamerequired | string | |
policyRuleId | UUID | null | |
policyVersionIdrequired | UUID | |
policyVersionNumberrequired | integer | |
reasonCoderequired | ReasonCode | |
rolerequired | BINDING | SATISFIED | NOT_APPLICABLE | How the finding stands against the decision. |
rulerequired | string | The rule's name, or its key when it has none. |
ruleKey | string | null | |
unit | MONEY | COUNT | null | |
policiesConsideredrequired | object[] | Every candidate policy version. One whose participation is NOT_APPLICABLE governs the agent but is not about this purchase: it changed nothing, and omitting it would make a NO_APPLICABLE_POLICY answer impossible to act on. |
applicabilityReason | string | |
participation | PARTICIPATING | NOT_APPLICABLE | |
policyId | UUID | |
policyName | string | |
policyStatus | ACTIVE | DISABLED | ARCHIVED | |
policyVersionId | UUID | |
versionNumber | integer | |
versionStatus | DRAFT | ACTIVE | ARCHIVED | |
purchaserequired | object | |
amountMinor | MinorAmount | |
category | string | null | |
currency | Currency | |
currencyExponent | integer | |
maximumMinor | MinorAmount | null | The most the purchase can cost, with every `VARIABLE` charge at its maximum: what rules and budgets judged. `amountMinor` is the known amount. Null when no charge can still vary. |
merchant | string | |
merchantDomain | string | null | |
merchantKey | string | |
simulatedDecisionrequired | object | |
advancerequired | COMPLETE | PARTIAL | NOT_DECIDED | How much of the decision this answer is. COMPLETE: every rule was checked. PARTIAL: some rules are checked on request, and the others already refuse, so the refusal stands. NOT_DECIDED: some rules are checked on request and no outcome is given. |
authority | WITHIN_POLICY | AUTHORIZED | null | |
deferredRulesrequired | object[] | The rules checked when a purchase is requested, not in advance. Empty when advance is COMPLETE. |
policyIdrequired | string | |
policyNamerequired | string | |
rulerequired | string | |
ruleKeyrequired | string | |
explanationrequired | string | |
outcomerequired | APPROVED | REJECTED | REQUIRES_APPROVAL | NOT_DECIDED_IN_ADVANCE | What a purchase request would be decided as. NOT_DECIDED_IN_ADVANCE when some rules are checked only when a purchase is requested and the others do not already refuse: request the purchase to find out. |
reasonCodesrequired | ReasonCode[] | |
wouldRequireApprovalrequired | boolean | False when the outcome is NOT_DECIDED_IN_ADVANCE: it is not known. |
simulationrequired | object | |
disclaimerrequired | string | |
engineVersionrequired | string | |
isSimulationrequired | true | Always true. The field exists so its absence is a signal. |
moderequired | string | |
simulatedAtrequired | Timestamp |
Ask what a specific policy version would decide#
POST/v1/simulations/policy-version
Organisation-scoped API keyNo OAuth caller admitted
Evaluates a purchase against one `ACTIVE` or `ARCHIVED` policy version exactly as it was frozen — the question you ask about a decision that was already made, or about a version you are thinking of reinstating.
Organization-scoped API keys only.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 120 per minute per credential.
Request body: at most 65536 bytes, or 655360 bytes when it states `items`.
Request body
| Field | Type | Description |
|---|---|---|
amountMinorrequired | string | integer | A whole number of ISO 4217 minor units. Send it as a string: JSON numbers lose precision above 2^53, and this is money. It is always returned as a string. |
category | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other | Required unless the body states `items`, and never with them. What kind of product this is: exactly one of Veriticity's categories. Choose by what the product is, not where it's sold or what it's for. `other` means you know what the product is and no category covers it; never use it for a product you can't identify. A purchase that holds different kinds of product, or a product that is also one of `medicines`, `gift-cards`, `alcohol`, `tobacco-nicotine`, `adult`, `gambling`, `crypto`, `recreational-drugs` besides the category you'd choose (a crypto casino deposit, an alcohol gift card), isn't described by one category: list its items instead, each with its own category and a risk tag for every other one of those it involves. Any other value is refused. Recorded as stated by the caller, not verified. `electronics`: Physical electronic devices, components and accessories: laptops, phones, chargers, hardware wallets. Not here: software (`software`), video games (`media-entertainment`). `software`: Software licences, apps, SaaS and cloud services, hosting, domains and productivity tools. Not here: music, film, streaming media and games (`media-entertainment`). `office-supplies`: Stationery, paper, printer supplies, and office furniture and equipment bought for work. Not here: personal electronics (`electronics`). `clothing`: Clothing, footwear, bags, jewellery and watches, for any age. Not here: sports equipment (`sports-outdoors`), dress-up toys (`kids-baby`). `home-garden`: Furniture, homeware, kitchenware, appliances, DIY tools and materials, garden products, and household cleaning and consumables such as washing-up liquid, bin bags and toilet roll. Not here: office furniture bought for an office (`office-supplies`). `food-drink`: Groceries, non-alcoholic drinks, meals, takeaway and food delivery, including baby food and formula. Not here: alcohol (`alcohol`), pet food (`pet-supplies`), vitamins and supplements (`health-beauty`). `health-beauty`: Toiletries, skincare, cosmetics, fragrance, hair and oral care, shaving, deodorant, sunscreen, sanitary and incontinence products, condoms and non-prescription contraception, vitamins and supplements, first-aid supplies, health devices and contact lenses. Not here: licensed medicines, including medicated creams (`medicines`), household cleaning (`home-garden`), nappies (`kids-baby`), CBD products (`recreational-drugs`). `kids-baby`: Toys, board games, puzzles, nursery equipment, nappies, and baby care that is neither food nor medicine. Not here: children's clothing (`clothing`), baby food (`food-drink`), video games (`media-entertainment`). `pet-supplies`: Pet food, accessories, bedding and non-prescription pet care. Not here: veterinary services (`services`), prescription pet medicines (`medicines`). `sports-outdoors`: Sports, fitness, camping and cycling equipment. Not here: sportswear (`clothing`), tickets to sports events (`media-entertainment`). `automotive`: Vehicle parts, accessories, tyres and maintenance products. Not here: fuel, parking and tolls (`travel`), repairs (`services`). `travel`: Flights, accommodation, rail, car hire, rides, parking, fuel and tolls. Not here: travel insurance (`services`). `media-entertainment`: Books and e-books, music, film, streaming media, video games and in-game items, and event tickets. Not here: betting stakes and casino credits (`gambling`). `services`: Professional, personal and booked services, repairs, insurance, education and veterinary services. Not here: software (`software`), event tickets (`media-entertainment`). `medicines`: Licensed medicines, prescription and over the counter, for people or pets: pain relief, cold and flu, allergy, medicated creams, pharmacy-only products and prescription refills. Not here: vitamins, supplements and unmedicated skincare (`health-beauty`), nicotine replacement products (`tobacco-nicotine`), CBD and other unlicensed psychoactive products (`recreational-drugs`). `gift-cards`: Gift cards, vouchers, prepaid cards, and top-ups of a merchant's or platform's wallet or account. Not here: deposits to crypto exchanges or wallets (`crypto`), goods paid for with a gift card (by what they are). `alcohol`: Alcoholic drinks of any strength, and alcohol gift sets. Not here: alcohol-free drinks (`food-drink`). `tobacco-nicotine`: Tobacco, cigarettes, cigars, e-cigarettes, vape devices and liquids, nicotine pouches and nicotine replacement products. Not here: cannabis vapes (`recreational-drugs`), lighters on their own (`home-garden`). `adult`: Sexual-wellness products, adult entertainment and age-restricted adult content. Not here: condoms and contraception (`health-beauty`). `gambling`: Lottery tickets, betting stakes and credits, casino credits including crypto casinos, and paid chance-based entries that can pay out money. Not here: in-game items with no cash value (`media-entertainment`). `crypto`: Buying cryptocurrency, stablecoins, tokens or NFTs, deposits to crypto exchanges or wallets, and crypto ATM purchases. Not here: hardware wallets (`electronics`), crypto casinos (`gambling`), crypto-themed merchandise (by what they are). `recreational-drugs`: Products containing cannabis or cannabinoids, including CBD, psilocybin, kratom and other psychoactive substances not licensed as medicine, and drug paraphernalia. Not here: licensed medicines (`medicines`), alcohol (`alcohol`), tobacco and nicotine (`tobacco-nicotine`). `other`: A product you can identify that no other category covers. Not for a product you can't identify. |
charges | object[] | Charges on the whole basket. `KNOWN` is exact: `TAX`, `DELIVERY`, `DUTY`, `FEE` or `DISCOUNT` (negative; a fee or discount says what it is). `UNEXPLAINED` is any part of the total you can't account for, and has no `kind`. `VARIABLE` is a `TAX`, `DELIVERY` or `DUTY` charge that can still change, stated with its `maximumMinor`: the purchase is decided at the known amount plus every maximum, and a use presents each one's final, which may not exceed it. |
amountMinor | string | |
classrequired | KNOWN | VARIABLE | UNEXPLAINED | |
description | string | At most 200 bytes. |
kind | TAX | DELIVERY | DUTY | FEE | DISCOUNT | |
maximumMinor | string | A variable charge's most. |
currency | string | null | Defaults to the organisation's own currency when omitted. |
items | object[] | The items, instead of `category`, for a purchase that holds different kinds of product or a product involving more than one restricted kind. `amountMinor` must then be exactly the item lines (each `quantity` × `unitAmountMinor` less `discountMinor`) plus the known and unexplained charges. Text limits are counted in UTF-8 bytes. |
category | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other | The item's category. Omit it only for an item whose category is uncertain. |
categoryUncertain | boolean | `true` when what kind of product this is can't be established. Send `uncertaintyReason` and no `category`; the item is then treated as not established in every category rule and budget. |
descriptionrequired | string | What the item is. At most 500 bytes. |
discountMinor | string | A discount on this line, in minor units. Defaults to 0. |
itemId | string | Your own label for the line, unique in the basket. At most 64 bytes. |
options | object[] | |
namerequired | string | |
valuerequired | string | |
possibleCategories | electronics | software | office-supplies | clothing | home-garden | food-drink | health-beauty | kids-baby | pet-supplies | sports-outdoors | automotive | travel | media-entertainment | services | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs | other[] | For an uncertain item: up to three categories it might be. Shown to a person; never relied on. |
productReference | object | |
idrequired | string | |
providerrequired | string | |
productUrl | string | The product's https page, on `merchantDomain` or the same website with or without its `www` label. |
quantityrequired | integer | |
riskTags | medicines | gift-cards | alcohol | tobacco-nicotine | adult | gambling | crypto | recreational-drugs[] | Every restricted kind the item involves besides its category: a crypto casino deposit is `gambling` tagged `crypto`. A rule on a tagged category applies to the item. |
uncertaintyReason | string | Why the category can't be established. At most 200 bytes. |
unitAmountMinorrequired | string | The price of one, in minor units. |
merchantrequired | string | Who the money would go to, written as a person would name them. A label: it is never looked up and does not identify the merchant. |
merchantDomain | string | null | The merchant's website: the host the purchase would be made on. A full URL is accepted and only its host is used. This is what identifies the merchant; without it no merchant is established, and a policy that restricts merchants refuses the purchase or holds it for a person. It identifies the merchant only where the organisation has that exact host on record for one; a host that is sent and not on record leaves the merchant unrecognised. `example.com` and `www.example.com` are the same host. Any other subdomain matches only where the organisation trusts that website's subdomains. |
merchantReference | CommerceReference | null | A commerce platform's id for the merchant. Like the domain it is a statement by the caller. It supports `merchantDomain` and never replaces it: sent without a website it establishes no merchant, and one that is not on record, or is on record for a different merchant than the website, leaves the merchant not established. |
idrequired | string | The id, exactly as the provider writes it. No spaces. |
providerrequired | string | The platform or scheme: a short lowercase name. |
policyVersionIdrequired | UUID | The active or archived version to evaluate. |
productReference | CommerceReference | null | An id for the product: a platform's, or a scheme such as `sku` or `gtin`. Resolved within the merchant, so it means nothing until the merchant is recognised. |
idrequired | string | The id, exactly as the provider writes it. No spaces. |
providerrequired | string | The platform or scheme: a short lowercase name. |
reasonrequired | string | Why the agent wants this purchase. Every purchase must say. |
taxIncluded | boolean | Required with `items`: whether the item prices already include tax. When `true`, tax is not stated again. |
Responses
| Status | Meaning |
|---|---|
200 | What that version would have decided. |
400 | invalid_requestmalformed_json |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
413 | payload_too_large |
415 | unsupported_media_type |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
agentrequired | object | null | |
id | UUID | |
name | string | |
status | ENABLED | DISABLED | ARCHIVED | |
budgetsrequired | SimulatedBudget[] | |
aggregate | SUM_AMOUNT | COUNT | SUM_AMOUNT: the figures are minor units. COUNT: a number of purchases. |
currency | Currency | null | |
current | object | |
committedMinor | MinorAmount | |
heldMinor | MinorAmount | |
reservedMinor | MinorAmount | |
limits | object[] | |
effect | BLOCK | REQUIRE_APPROVAL | FLAG | |
limitMinor | MinorAmount | |
policyId | UUID | |
policyName | string | |
policyRuleId | UUID | null | |
policyVersionId | UUID | |
remainingAfterMinor | MinorAmount | Negative reads as "over by". |
remainingBeforeMinor | MinorAmount | |
ruleKey | string | null | |
wouldExceed | boolean | |
periodEnd | Timestamp | |
periodKey | string | |
periodKind | string | DAY, WEEK, MONTH, QUARTER or YEAR for a calendar window; ROLLING otherwise. |
periodStart | Timestamp | |
rollingSeconds | integer | null | |
scope | string | Whose purchases the window holds: ORGANIZATION, POLICY:<policy id>, AGENT:<agent id> or PRINCIPAL:<user:id | organization>. |
simulated | object | |
projectedMinor | MinorAmount | |
requestedMinor | MinorAmount | |
timeZone | string | null | |
wouldExceed | boolean | |
findingsrequired | Finding[] | |
currency | Currency | null | |
headroomMinor | MinorAmount | null | |
limitMinor | MinorAmount | null | |
measured | MeasuredWindow | null | |
aggregaterequired | SUM_AMOUNT | COUNT | SUM_AMOUNT: a sum of money. COUNT: a number of purchases. |
scoperequired | string | Whose purchases the window holds: POLICY (the agents the policy binds), ORGANIZATION, EACH_AGENT, AGENT:<agent id>, EACH_PRINCIPAL or PRINCIPAL:<user id>. |
windowrequired | object | |
messagerequired | string | |
observedMinor | MinorAmount | null | |
outcomerequired | PASS | NOT_APPLICABLE | FLAG | REQUIRE_APPROVAL | BLOCK | AUTHORIZED | PASS: the purchase came within the rule. AUTHORIZED: the rule asked for approval and a person gave it. FLAG: recorded, and changes nothing. A rule that only flags is the organisation's own record and is not returned by this API. |
policyIdrequired | UUID | |
policyNamerequired | string | |
policyRuleId | UUID | null | |
policyVersionIdrequired | UUID | |
policyVersionNumberrequired | integer | |
reasonCoderequired | ReasonCode | |
rolerequired | BINDING | SATISFIED | NOT_APPLICABLE | How the finding stands against the decision. |
rulerequired | string | The rule's name, or its key when it has none. |
ruleKey | string | null | |
unit | MONEY | COUNT | null | |
policiesConsideredrequired | object[] | Every candidate policy version. One whose participation is NOT_APPLICABLE governs the agent but is not about this purchase: it changed nothing, and omitting it would make a NO_APPLICABLE_POLICY answer impossible to act on. |
applicabilityReason | string | |
participation | PARTICIPATING | NOT_APPLICABLE | |
policyId | UUID | |
policyName | string | |
policyStatus | ACTIVE | DISABLED | ARCHIVED | |
policyVersionId | UUID | |
versionNumber | integer | |
versionStatus | DRAFT | ACTIVE | ARCHIVED | |
purchaserequired | object | |
amountMinor | MinorAmount | |
category | string | null | |
currency | Currency | |
currencyExponent | integer | |
maximumMinor | MinorAmount | null | The most the purchase can cost, with every `VARIABLE` charge at its maximum: what rules and budgets judged. `amountMinor` is the known amount. Null when no charge can still vary. |
merchant | string | |
merchantDomain | string | null | |
merchantKey | string | |
simulatedDecisionrequired | object | |
advancerequired | COMPLETE | PARTIAL | NOT_DECIDED | How much of the decision this answer is. COMPLETE: every rule was checked. PARTIAL: some rules are checked on request, and the others already refuse, so the refusal stands. NOT_DECIDED: some rules are checked on request and no outcome is given. |
authority | WITHIN_POLICY | AUTHORIZED | null | |
deferredRulesrequired | object[] | The rules checked when a purchase is requested, not in advance. Empty when advance is COMPLETE. |
policyIdrequired | string | |
policyNamerequired | string | |
rulerequired | string | |
ruleKeyrequired | string | |
explanationrequired | string | |
outcomerequired | APPROVED | REJECTED | REQUIRES_APPROVAL | NOT_DECIDED_IN_ADVANCE | What a purchase request would be decided as. NOT_DECIDED_IN_ADVANCE when some rules are checked only when a purchase is requested and the others do not already refuse: request the purchase to find out. |
reasonCodesrequired | ReasonCode[] | |
wouldRequireApprovalrequired | boolean | False when the outcome is NOT_DECIDED_IN_ADVANCE: it is not known. |
simulationrequired | object | |
disclaimerrequired | string | |
engineVersionrequired | string | |
isSimulationrequired | true | Always true. The field exists so its absence is a signal. |
moderequired | string | |
simulatedAtrequired | Timestamp |
Policies#
Read the spending rules in force. Policies are authored by a person in the dashboard, never by an API credential.
List spending policies#
GET/v1/policies
Organisation-scoped API keyNo OAuth caller admitted
Your organisation's spending policies, newest first.
Organization-scoped API keys only. An agent may not read the policies it is judged against — it learns what it may spend by asking `POST /v1/simulations/current-policies` instead. No OAuth scope reaches this.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
status | query | Return only policies in this state. |
search | query | Free-text match on the policy name. Trimmed, capped at 200 characters, and treated as absent when empty. |
page | query | 1-based page number. A page beyond the end returns an empty page rather than a 404, because a filtered set shrinking under a paging UI is normal. A page above 10000 is refused with `invalid_request`. |
pageSize | query | Rows per page. Clamped to 100 rather than refused: a caller asking for everything gets the maximum. A malformed value is refused, because it means the client believes something untrue. |
Responses
| Status | Meaning |
|---|---|
200 | A page of policies, and where it sits in the collection. |
400 | invalid_request |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
paginationrequired | Pagination | |
pagerequired | integer | |
pageSizerequired | integer | |
totalrequired | integer | |
totalPagesrequired | integer | At least 1 even when nothing matched, so a client can render "1 of 1" for an empty result and never divide by zero. |
policiesrequired | PolicySummary[] | |
activeVersionrequired | PolicyVersionSummary | null | |
activatedAt | Timestamp | null | |
archivedAt | Timestamp | null | |
createdAtrequired | Timestamp | |
idrequired | UUID | |
languageVersionrequired | integer | The version of the policy language this content is written in. |
ruleCountrequired | integer | How many rules this version carries, without fetching them. |
statusrequired | DRAFT | ACTIVE | ARCHIVED | |
versionNumberrequired | integer | Allocated by Veriticity. Never chosen by a caller. |
archivedAt | Timestamp | null | |
boundToOrganizationrequired | boolean | Whether the policy is bound to the whole organization, and so governs every agent in it. |
createdAtrequired | Timestamp | |
description | string | null | |
idrequired | UUID | |
inForcerequired | boolean | Whether the Trust Engine is actually reading this policy right now. "Has a live version" and "is being enforced" are different facts: a DISABLED policy keeps its ACTIVE version but the engine skips it. |
namerequired | string | |
statusrequired | ACTIVE | DISABLED | ARCHIVED | |
updatedAtrequired | Timestamp |
Read one policy#
GET/v1/policies/{policyId}
Organisation-scoped API keyNo OAuth caller admitted
One policy with its version history and the agents it governs.
Organization-scoped API keys only. An agent may not read the policies it is judged against — it learns what it may spend by asking `POST /v1/simulations/current-policies` instead. No OAuth scope reaches this.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
policyIdrequired | path | The policy to read. |
Responses
| Status | Meaning |
|---|---|
200 | The policy. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
policyrequired | PolicySummary | |
activeVersionrequired | PolicyVersionSummary | null | |
activatedAt | Timestamp | null | |
archivedAt | Timestamp | null | |
createdAtrequired | Timestamp | |
idrequired | UUID | |
languageVersionrequired | integer | The version of the policy language this content is written in. |
ruleCountrequired | integer | How many rules this version carries, without fetching them. |
statusrequired | DRAFT | ACTIVE | ARCHIVED | |
versionNumberrequired | integer | Allocated by Veriticity. Never chosen by a caller. |
archivedAt | Timestamp | null | |
boundToOrganizationrequired | boolean | Whether the policy is bound to the whole organization, and so governs every agent in it. |
createdAtrequired | Timestamp | |
description | string | null | |
idrequired | UUID | |
inForcerequired | boolean | Whether the Trust Engine is actually reading this policy right now. "Has a live version" and "is being enforced" are different facts: a DISABLED policy keeps its ACTIVE version but the engine skips it. |
namerequired | string | |
statusrequired | ACTIVE | DISABLED | ARCHIVED | |
updatedAtrequired | Timestamp | |
bindings | PolicyBindings | |
agentIds | UUID[] | |
organization | boolean | Bound to the whole organization: it governs every agent. |
versions | PolicyVersionSummary[] | |
activatedAt | Timestamp | null | |
archivedAt | Timestamp | null | |
createdAtrequired | Timestamp | |
idrequired | UUID | |
languageVersionrequired | integer | The version of the policy language this content is written in. |
ruleCountrequired | integer | How many rules this version carries, without fetching them. |
statusrequired | DRAFT | ACTIVE | ARCHIVED | |
versionNumberrequired | integer | Allocated by Veriticity. Never chosen by a caller. |
Read one policy version#
GET/v1/policies/{policyId}/versions/{versionId}
Organisation-scoped API keyNo OAuth caller admitted
A policy version in full, exactly as the Trust Engine reads it: what it is about, its rules, the conditions they name and its value sets.
Organization-scoped API keys only. An agent may not read the policies it is judged against — it learns what it may spend by asking `POST /v1/simulations/current-policies` instead. No OAuth scope reaches this.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
policyIdrequired | path | The policy the version belongs to. |
versionIdrequired | path | The version to read. |
Responses
| Status | Meaning |
|---|---|
200 | The version. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
versionrequired | PolicyVersion | |
activatedAt | Timestamp | null | |
archivedAt | Timestamp | null | |
createdAtrequired | Timestamp | |
idrequired | UUID | |
languageVersionrequired | integer | The version of the policy language this content is written in. |
ruleCountrequired | integer | How many rules this version carries, without fetching them. |
statusrequired | DRAFT | ACTIVE | ARCHIVED | |
versionNumberrequired | integer | Allocated by Veriticity. Never chosen by a caller. |
appliesWhen | PolicyExpression | null | |
appliesWhenConditionsrequired | PolicyCondition[] | |
accept | object | null | |
atomrequired | string | The key the expression's `atom` leaves refer to it by. |
conditionrequired | string | The condition's type. AMOUNT_LIMIT: the amount is at or under `config.max`. AMOUNT_MINIMUM: the amount is at or over `config.min`. SPEND_LIMIT: spend in `config.window`, including this purchase, is at or under `config.max`. PURCHASE_COUNT_LIMIT: the purchases in `config.window`, including this one, number at most `config.maxPurchases`. MERCHANT_IN_SET: earlier versions only, and never satisfied, since a name in the value set `config.set` identifies no merchant. CURRENCY_IN: the currency is one of `config.currencies`. MERCHANT_IDENTIFIED: the merchant is one the organisation has on record. MERCHANT_VERIFIED: it is, and it is currently verified. MERCHANT_IS: it is one of `config.merchants`. MERCHANT_COUNTRY_IN: its country is one of `config.countries`. LEGAL_ENTITY_JURISDICTION_IN: the company behind it is registered in one of `config.countries`. PRODUCT_IDENTIFIED: the product is one the organisation has on record for that merchant. PRODUCT_CATEGORY_IN: the product's category is one of `config.categories`, or inside one. PRODUCT_ATTRIBUTE_IN: the product's `config.attribute` is one of `config.values`. ATTEMPT_COUNT_LIMIT: the purchase attempts in `config.window` number at most `config.maxAttempts`. DISTINCT_MERCHANT_LIMIT: the purchases in `config.window`, including this one, are with at most `config.maxMerchants` different merchants. DISTINCT_CATEGORY_LIMIT: they are in at most `config.maxCategories` different categories. PURCHASE_HISTORY_MINIMUM: at least `config.minPurchases` purchases were approved in `config.window` before this one. AMOUNT_DEVIATION_LIMIT: the amount is at or under `config.maxPercent` per cent of the usual amount over `config.window`. |
config | object | The condition's own configuration. Its shape belongs to the condition type. `max` and `min` are `{ amountMinor, currency }`. A monetary limit compares only amounts in its own currency: there is no conversion, and a purchase in another currency cannot be shown to be within it. `window` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) is `DAY`, `WEEK`, `MONTH`, `QUARTER` or `YEAR` for a period of the organisation's calendar in its timezone; `{ "kind": "CALENDAR", "unit": "WEEK", "weekStartsOn": "SUNDAY" }` for a week that does not start on Monday; or `{ "kind": "ROLLING", "seconds": 86400 }` for a length of elapsed time from 60 seconds to 366 days. A rolling window is never a calendar period: 86400 seconds is twenty-four hours, not a day. `scope` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) is whose purchases the window holds. Omitted, or `{ "kind": "POLICY" }`: the agents the policy binds, together. `ORGANIZATION`: every agent. `EACH_AGENT`: the agent making the purchase. `{ "kind": "AGENT", "agentId" }`: one agent. `EACH_PRINCIPAL`: the person the purchase is made for. `{ "kind": "PRINCIPAL", "userId" }`: one person. A named agent must be one of the organisation's agents and not archived, and a named person one of its members; otherwise whose spending the limit covers cannot be established and the rule's `onUnestablished` applies. A window counts purchases that were approved or are waiting on a person. A refused, expired or cancelled purchase is not counted. `selector` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) narrows the window to purchases of one kind, and combines with any `scope`. `{ "kind": "MERCHANT", "merchant": "<key>" }`: purchases at that merchant. `{ "kind": "EACH_MERCHANT" }`: purchases at this purchase's merchant. `{ "kind": "CATEGORY", "category": "<key>" }`: purchases in that category or inside it. A purchase is outside a selector only when its merchant or category is on record as another one: a purchase whose merchant is not recognised is tested against the limit and counted in it. `acceptStated: true` lets the caller's own statement of the merchant or category put a purchase outside. Merchants are named by the organisation's key for them and categories by `<taxonomy>:<path>`. Every organisation has Veriticity's categories, `veriticity:<value>` for each `category` a purchase may state, as in `veriticity:electronics`. A stated category is the caller's claim: a rule counts it only when its `accept` takes `AGENT_ASSERTED` for `fact.state[product.category_id]`, or a selector or DISTINCT_CATEGORY_LIMIT has `acceptStated: true`. A purchase stating `mixed`, or none, has no established category. A category includes everything inside it. Matching is exact: a merchant is recognised by a domain or reference the organisation has on record for it, never by a similar one and never by its name. ATTEMPT_COUNT_LIMIT, DISTINCT_MERCHANT_LIMIT, DISTINCT_CATEGORY_LIMIT, PURCHASE_HISTORY_MINIMUM and AMOUNT_DEVIATION_LIMIT take `window` and `scope` as above, and `scope` is required. PURCHASE_HISTORY_MINIMUM and AMOUNT_DEVIATION_LIMIT take a rolling `window` only, and no `selector` but `{ "kind": "EACH_MERCHANT" }`. None of the five may be used in a rule's `when`. An attempt is a purchase request that reached a decision: approved, refused or waiting on a person. A request refused before a decision (validation, authentication, a rate limit, the plan's allowance) is not one. A request repeated with the same `Idempotency-Key` is the same attempt; with a new key or none it is a new one. A simulation is not an attempt. `counting` (ATTEMPT_COUNT_LIMIT, required) is `ALL` for every attempt including this one, `REFUSED` for earlier refusals, or `PENDING` for purchases waiting on a person. `level` (DISTINCT_CATEGORY_LIMIT, required) is `EXACT` to count each category separately, so that a category and one inside it are two, or `TOP` to count by top-level category. A purchase whose merchant or category is not recognised could be anywhere: the rule is met only if the limit holds wherever it was, and otherwise its `onUnestablished` applies. A merchant the caller named is not a recognised one unless `acceptStated` is true. `baseline` (AMOUNT_DEVIATION_LIMIT) is `MEAN` or `MAX` of the purchases approved in the window before this one, in `currency`. `maxPercent` is a whole number from 100. `minSample` is required, and is 5 or more: with fewer approved purchases there is no usual amount, and the rule's `onUnestablished` applies. PURCHASE_HISTORY_MINIMUM is not met, rather than unestablished, when there are too few. Purchases that were refused or are waiting on a person are never part of either. A credential acting as an agent is not shown the configuration of these five conditions or the figures of their findings. |
configVersion | integer | |
rulesrequired | PolicyRule[] | |
conditionsrequired | PolicyCondition[] | |
effectrequired | BLOCK | REQUIRE_APPROVAL | FLAG | What happens when `require` is violated. BLOCK refuses. REQUIRE_APPROVAL holds the budget and waits for a person. FLAG records the finding and changes nothing. |
id | UUID | |
keyrequired | string | The rule's identity across versions of its policy. Keep it when editing a rule: an approval is bound to it. |
name | string | null | |
onUnestablished | BLOCK | REQUIRE_APPROVAL | FLAG | What happens when `require` cannot be shown either way, for example because it rests on a merchant nobody has verified. Defaults to `effect`. FLAG exactly when `effect` is FLAG. |
requirerequired | PolicyExpression | What must be established for the purchase to proceed. |
when | PolicyExpression | null | |
valueSetsrequired | PolicyValueSet[] | |
keyrequired | string | |
kindrequired | string | |
membersrequired | object[] | |
policyIdrequired | UUID |
Check whether a version could be activated#
POST/v1/policies/{policyId}/versions/{versionId}/validate
Organisation-scoped API keyNo OAuth caller admitted
Runs activation's own check as a dry run, and changes nothing.
Answers 200 even when the draft is unacceptable — `valid` carries the verdict and `issues` carries the reasons. A 4xx would mean the question could not be answered, and it was.
`budgetImpact` is empty in the ordinary case. A non-empty one means activating this version would start a limit from a total that already includes spending nobody reserved against.
This is a `POST` that writes nothing, and it is the only policy operation on `/v1` that is not a plain read.
Organization-scoped API keys only. An agent may not read the policies it is judged against — it learns what it may spend by asking `POST /v1/simulations/current-policies` instead. No OAuth scope reaches this.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
policyIdrequired | path | The policy the version belongs to. |
versionIdrequired | path | The version to check. |
Responses
| Status | Meaning |
|---|---|
200 | The verdict. `valid` is false when the version could not be activated; this is still a 200. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
budgetImpactrequired | object[] | |
currency | Currency | |
limits | object[] | |
effect | string | |
limitMinor | MinorAmount | |
remainingMinor | MinorAmount | |
ruleKey | string | The key of the rule that sets this limit. |
periodKey | string | |
periodKind | string | |
spentMinor | MinorAmount | |
issuesrequired | PolicyIssue[] | |
coderequired | string | |
messagerequired | string | |
path | string | |
validrequired | boolean | |
versionrequired | PolicyVersion | |
activatedAt | Timestamp | null | |
archivedAt | Timestamp | null | |
createdAtrequired | Timestamp | |
idrequired | UUID | |
languageVersionrequired | integer | The version of the policy language this content is written in. |
ruleCountrequired | integer | How many rules this version carries, without fetching them. |
statusrequired | DRAFT | ACTIVE | ARCHIVED | |
versionNumberrequired | integer | Allocated by Veriticity. Never chosen by a caller. |
appliesWhen | PolicyExpression | null | |
appliesWhenConditionsrequired | PolicyCondition[] | |
accept | object | null | |
atomrequired | string | The key the expression's `atom` leaves refer to it by. |
conditionrequired | string | The condition's type. AMOUNT_LIMIT: the amount is at or under `config.max`. AMOUNT_MINIMUM: the amount is at or over `config.min`. SPEND_LIMIT: spend in `config.window`, including this purchase, is at or under `config.max`. PURCHASE_COUNT_LIMIT: the purchases in `config.window`, including this one, number at most `config.maxPurchases`. MERCHANT_IN_SET: earlier versions only, and never satisfied, since a name in the value set `config.set` identifies no merchant. CURRENCY_IN: the currency is one of `config.currencies`. MERCHANT_IDENTIFIED: the merchant is one the organisation has on record. MERCHANT_VERIFIED: it is, and it is currently verified. MERCHANT_IS: it is one of `config.merchants`. MERCHANT_COUNTRY_IN: its country is one of `config.countries`. LEGAL_ENTITY_JURISDICTION_IN: the company behind it is registered in one of `config.countries`. PRODUCT_IDENTIFIED: the product is one the organisation has on record for that merchant. PRODUCT_CATEGORY_IN: the product's category is one of `config.categories`, or inside one. PRODUCT_ATTRIBUTE_IN: the product's `config.attribute` is one of `config.values`. ATTEMPT_COUNT_LIMIT: the purchase attempts in `config.window` number at most `config.maxAttempts`. DISTINCT_MERCHANT_LIMIT: the purchases in `config.window`, including this one, are with at most `config.maxMerchants` different merchants. DISTINCT_CATEGORY_LIMIT: they are in at most `config.maxCategories` different categories. PURCHASE_HISTORY_MINIMUM: at least `config.minPurchases` purchases were approved in `config.window` before this one. AMOUNT_DEVIATION_LIMIT: the amount is at or under `config.maxPercent` per cent of the usual amount over `config.window`. |
config | object | The condition's own configuration. Its shape belongs to the condition type. `max` and `min` are `{ amountMinor, currency }`. A monetary limit compares only amounts in its own currency: there is no conversion, and a purchase in another currency cannot be shown to be within it. `window` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) is `DAY`, `WEEK`, `MONTH`, `QUARTER` or `YEAR` for a period of the organisation's calendar in its timezone; `{ "kind": "CALENDAR", "unit": "WEEK", "weekStartsOn": "SUNDAY" }` for a week that does not start on Monday; or `{ "kind": "ROLLING", "seconds": 86400 }` for a length of elapsed time from 60 seconds to 366 days. A rolling window is never a calendar period: 86400 seconds is twenty-four hours, not a day. `scope` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) is whose purchases the window holds. Omitted, or `{ "kind": "POLICY" }`: the agents the policy binds, together. `ORGANIZATION`: every agent. `EACH_AGENT`: the agent making the purchase. `{ "kind": "AGENT", "agentId" }`: one agent. `EACH_PRINCIPAL`: the person the purchase is made for. `{ "kind": "PRINCIPAL", "userId" }`: one person. A named agent must be one of the organisation's agents and not archived, and a named person one of its members; otherwise whose spending the limit covers cannot be established and the rule's `onUnestablished` applies. A window counts purchases that were approved or are waiting on a person. A refused, expired or cancelled purchase is not counted. `selector` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) narrows the window to purchases of one kind, and combines with any `scope`. `{ "kind": "MERCHANT", "merchant": "<key>" }`: purchases at that merchant. `{ "kind": "EACH_MERCHANT" }`: purchases at this purchase's merchant. `{ "kind": "CATEGORY", "category": "<key>" }`: purchases in that category or inside it. A purchase is outside a selector only when its merchant or category is on record as another one: a purchase whose merchant is not recognised is tested against the limit and counted in it. `acceptStated: true` lets the caller's own statement of the merchant or category put a purchase outside. Merchants are named by the organisation's key for them and categories by `<taxonomy>:<path>`. Every organisation has Veriticity's categories, `veriticity:<value>` for each `category` a purchase may state, as in `veriticity:electronics`. A stated category is the caller's claim: a rule counts it only when its `accept` takes `AGENT_ASSERTED` for `fact.state[product.category_id]`, or a selector or DISTINCT_CATEGORY_LIMIT has `acceptStated: true`. A purchase stating `mixed`, or none, has no established category. A category includes everything inside it. Matching is exact: a merchant is recognised by a domain or reference the organisation has on record for it, never by a similar one and never by its name. ATTEMPT_COUNT_LIMIT, DISTINCT_MERCHANT_LIMIT, DISTINCT_CATEGORY_LIMIT, PURCHASE_HISTORY_MINIMUM and AMOUNT_DEVIATION_LIMIT take `window` and `scope` as above, and `scope` is required. PURCHASE_HISTORY_MINIMUM and AMOUNT_DEVIATION_LIMIT take a rolling `window` only, and no `selector` but `{ "kind": "EACH_MERCHANT" }`. None of the five may be used in a rule's `when`. An attempt is a purchase request that reached a decision: approved, refused or waiting on a person. A request refused before a decision (validation, authentication, a rate limit, the plan's allowance) is not one. A request repeated with the same `Idempotency-Key` is the same attempt; with a new key or none it is a new one. A simulation is not an attempt. `counting` (ATTEMPT_COUNT_LIMIT, required) is `ALL` for every attempt including this one, `REFUSED` for earlier refusals, or `PENDING` for purchases waiting on a person. `level` (DISTINCT_CATEGORY_LIMIT, required) is `EXACT` to count each category separately, so that a category and one inside it are two, or `TOP` to count by top-level category. A purchase whose merchant or category is not recognised could be anywhere: the rule is met only if the limit holds wherever it was, and otherwise its `onUnestablished` applies. A merchant the caller named is not a recognised one unless `acceptStated` is true. `baseline` (AMOUNT_DEVIATION_LIMIT) is `MEAN` or `MAX` of the purchases approved in the window before this one, in `currency`. `maxPercent` is a whole number from 100. `minSample` is required, and is 5 or more: with fewer approved purchases there is no usual amount, and the rule's `onUnestablished` applies. PURCHASE_HISTORY_MINIMUM is not met, rather than unestablished, when there are too few. Purchases that were refused or are waiting on a person are never part of either. A credential acting as an agent is not shown the configuration of these five conditions or the figures of their findings. |
configVersion | integer | |
rulesrequired | PolicyRule[] | |
conditionsrequired | PolicyCondition[] | |
effectrequired | BLOCK | REQUIRE_APPROVAL | FLAG | What happens when `require` is violated. BLOCK refuses. REQUIRE_APPROVAL holds the budget and waits for a person. FLAG records the finding and changes nothing. |
id | UUID | |
keyrequired | string | The rule's identity across versions of its policy. Keep it when editing a rule: an approval is bound to it. |
name | string | null | |
onUnestablished | BLOCK | REQUIRE_APPROVAL | FLAG | What happens when `require` cannot be shown either way, for example because it rests on a merchant nobody has verified. Defaults to `effect`. FLAG exactly when `effect` is FLAG. |
requirerequired | PolicyExpression | What must be established for the purchase to proceed. |
when | PolicyExpression | null | |
valueSetsrequired | PolicyValueSet[] | |
keyrequired | string | |
kindrequired | string | |
membersrequired | object[] | |
policyIdrequired | UUID | |
warningsrequired | PolicyIssue[] | |
coderequired | string | |
messagerequired | string | |
path | string |
Agents#
The identities that spend.
Read the agent this credential acts as#
GET/v1/agent
API key bound to an agentOAuth: purchase:check
The agent bound to the calling credential, the currency and timezone its organisation operates in, and which environment answered.
Takes no agent id: a credential acts as exactly one agent and cannot ask about another.
Credential: An API key bound to an agent. OAuth connections need `purchase:check`.
Rate limit: 600 per minute per credential.
Responses
| Status | Meaning |
|---|---|
200 | The agent, and its organisation's defaults. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_included |
403 | connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
agentrequired | object | |
description | string | null | |
idrequired | UUID | |
namerequired | string | |
statusrequired | ENABLED | DISABLED | ARCHIVED | |
environmentrequired | live | sandbox | Which Veriticity environment answered. Sandbox agents, policies, budgets, purchases and approvals are separate from Live, and nothing decided in Sandbox authorises a real purchase. |
organizationrequired | object | |
currencyrequired | Currency | |
timeZonerequired | string | IANA identifier. Budget windows are measured in it. |
List this organisation's agents#
GET/v1/agents
Organisation-scoped API keyNo OAuth caller admitted
The agents in your organisation, with their status.
Organization-scoped API keys only. An agent-bound credential is refused: a roster of an organisation's other agents is a map of the identities a compromised agent might try to impersonate.
Read-only. Agents are created in the dashboard.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 600 per minute per credential.
Responses
| Status | Meaning |
|---|---|
200 | Every agent in the organisation. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
agentsrequired | object[] | |
createdAtrequired | Timestamp | |
description | string | null | |
idrequired | UUID | |
namerequired | string | |
statusrequired | ENABLED | DISABLED | ARCHIVED | |
updatedAtrequired | Timestamp |
Read how much budget this agent has left#
GET/v1/budget-status
API key bound to an agentOAuth: purchase:check
Each daily and monthly budget window that applies to the calling agent, with what has been spent, what is held for purchases awaiting approval, and what remains under each limit. Changes nothing.
`remainingMinor` can be negative: a limit lowered below what a window already holds reads as over by that amount.
Credential: An API key bound to an agent. OAuth connections need `purchase:check`.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
currency | query | ISO 4217 code to answer in. Omit for the organisation's default currency. |
Responses
| Status | Meaning |
|---|---|
200 | The budget windows in force. |
400 | invalid_request |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_included |
403 | connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
asOfrequired | Timestamp | |
currencyrequired | Currency | |
windowsrequired | object[] | |
committedMinorrequired | MinorAmount | |
currencyrequired | Currency | |
heldMinorrequired | MinorAmount | |
limitsrequired | object[] | |
effectrequired | BLOCK | REQUIRE_APPROVAL | |
limitMinorrequired | MinorAmount | |
remainingMinorrequired | string | Minor units. Negative when the window is over the limit. |
periodEndrequired | Timestamp | |
periodKeyrequired | string | Organisation-local: a date for a day, a week's first date, then `2026-10`, `2026-Q4` or `2026`. Empty for ROLLING. |
periodKindrequired | DAY | WEEK | MONTH | QUARTER | YEAR | ROLLING | A period of the organisation's calendar, or ROLLING for a length of elapsed time ending now. |
periodStartrequired | Timestamp | |
policyNamerequired | string | |
reservedMinorrequired | MinorAmount | |
scope | string | Whose spend the window holds: ORGANIZATION, POLICY:<policy id>, AGENT:<agent id> or PRINCIPAL:<user:id | organization>. |
Read the rules in force for this agent#
GET/v1/spending-rules
API key bound to an agentOAuth: purchase:check
The policies that currently govern the calling agent, with their rules. Changes nothing.
Each rule says what must be established for a purchase to proceed (`require`), when it applies (`when`), and what happens otherwise (`effect`). `participation` says whether a policy is about a purchase in the currency asked for.
`valueSets` says which lists a policy's rules name and how long each is. The names on them are not returned. A merchant is identified by its website and never by its name, so a list of typed names allows no purchase.
Credential: An API key bound to an agent. OAuth connections need `purchase:check`.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
currency | query | ISO 4217 code to answer in. Omit for the organisation's default currency. |
Responses
| Status | Meaning |
|---|---|
200 | The policies that apply, in the currency asked for. |
400 | invalid_request |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_included |
403 | connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
currencyrequired | Currency | |
policiesrequired | object[] | |
applicabilityReasonrequired | string | |
appliesWhen | objectnull | |
appliesWhenConditions | PolicyCondition[] | |
accept | object | null | |
atomrequired | string | The key the expression's `atom` leaves refer to it by. |
conditionrequired | string | The condition's type. AMOUNT_LIMIT: the amount is at or under `config.max`. AMOUNT_MINIMUM: the amount is at or over `config.min`. SPEND_LIMIT: spend in `config.window`, including this purchase, is at or under `config.max`. PURCHASE_COUNT_LIMIT: the purchases in `config.window`, including this one, number at most `config.maxPurchases`. MERCHANT_IN_SET: earlier versions only, and never satisfied, since a name in the value set `config.set` identifies no merchant. CURRENCY_IN: the currency is one of `config.currencies`. MERCHANT_IDENTIFIED: the merchant is one the organisation has on record. MERCHANT_VERIFIED: it is, and it is currently verified. MERCHANT_IS: it is one of `config.merchants`. MERCHANT_COUNTRY_IN: its country is one of `config.countries`. LEGAL_ENTITY_JURISDICTION_IN: the company behind it is registered in one of `config.countries`. PRODUCT_IDENTIFIED: the product is one the organisation has on record for that merchant. PRODUCT_CATEGORY_IN: the product's category is one of `config.categories`, or inside one. PRODUCT_ATTRIBUTE_IN: the product's `config.attribute` is one of `config.values`. ATTEMPT_COUNT_LIMIT: the purchase attempts in `config.window` number at most `config.maxAttempts`. DISTINCT_MERCHANT_LIMIT: the purchases in `config.window`, including this one, are with at most `config.maxMerchants` different merchants. DISTINCT_CATEGORY_LIMIT: they are in at most `config.maxCategories` different categories. PURCHASE_HISTORY_MINIMUM: at least `config.minPurchases` purchases were approved in `config.window` before this one. AMOUNT_DEVIATION_LIMIT: the amount is at or under `config.maxPercent` per cent of the usual amount over `config.window`. |
config | object | The condition's own configuration. Its shape belongs to the condition type. `max` and `min` are `{ amountMinor, currency }`. A monetary limit compares only amounts in its own currency: there is no conversion, and a purchase in another currency cannot be shown to be within it. `window` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) is `DAY`, `WEEK`, `MONTH`, `QUARTER` or `YEAR` for a period of the organisation's calendar in its timezone; `{ "kind": "CALENDAR", "unit": "WEEK", "weekStartsOn": "SUNDAY" }` for a week that does not start on Monday; or `{ "kind": "ROLLING", "seconds": 86400 }` for a length of elapsed time from 60 seconds to 366 days. A rolling window is never a calendar period: 86400 seconds is twenty-four hours, not a day. `scope` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) is whose purchases the window holds. Omitted, or `{ "kind": "POLICY" }`: the agents the policy binds, together. `ORGANIZATION`: every agent. `EACH_AGENT`: the agent making the purchase. `{ "kind": "AGENT", "agentId" }`: one agent. `EACH_PRINCIPAL`: the person the purchase is made for. `{ "kind": "PRINCIPAL", "userId" }`: one person. A named agent must be one of the organisation's agents and not archived, and a named person one of its members; otherwise whose spending the limit covers cannot be established and the rule's `onUnestablished` applies. A window counts purchases that were approved or are waiting on a person. A refused, expired or cancelled purchase is not counted. `selector` (SPEND_LIMIT, PURCHASE_COUNT_LIMIT) narrows the window to purchases of one kind, and combines with any `scope`. `{ "kind": "MERCHANT", "merchant": "<key>" }`: purchases at that merchant. `{ "kind": "EACH_MERCHANT" }`: purchases at this purchase's merchant. `{ "kind": "CATEGORY", "category": "<key>" }`: purchases in that category or inside it. A purchase is outside a selector only when its merchant or category is on record as another one: a purchase whose merchant is not recognised is tested against the limit and counted in it. `acceptStated: true` lets the caller's own statement of the merchant or category put a purchase outside. Merchants are named by the organisation's key for them and categories by `<taxonomy>:<path>`. Every organisation has Veriticity's categories, `veriticity:<value>` for each `category` a purchase may state, as in `veriticity:electronics`. A stated category is the caller's claim: a rule counts it only when its `accept` takes `AGENT_ASSERTED` for `fact.state[product.category_id]`, or a selector or DISTINCT_CATEGORY_LIMIT has `acceptStated: true`. A purchase stating `mixed`, or none, has no established category. A category includes everything inside it. Matching is exact: a merchant is recognised by a domain or reference the organisation has on record for it, never by a similar one and never by its name. ATTEMPT_COUNT_LIMIT, DISTINCT_MERCHANT_LIMIT, DISTINCT_CATEGORY_LIMIT, PURCHASE_HISTORY_MINIMUM and AMOUNT_DEVIATION_LIMIT take `window` and `scope` as above, and `scope` is required. PURCHASE_HISTORY_MINIMUM and AMOUNT_DEVIATION_LIMIT take a rolling `window` only, and no `selector` but `{ "kind": "EACH_MERCHANT" }`. None of the five may be used in a rule's `when`. An attempt is a purchase request that reached a decision: approved, refused or waiting on a person. A request refused before a decision (validation, authentication, a rate limit, the plan's allowance) is not one. A request repeated with the same `Idempotency-Key` is the same attempt; with a new key or none it is a new one. A simulation is not an attempt. `counting` (ATTEMPT_COUNT_LIMIT, required) is `ALL` for every attempt including this one, `REFUSED` for earlier refusals, or `PENDING` for purchases waiting on a person. `level` (DISTINCT_CATEGORY_LIMIT, required) is `EXACT` to count each category separately, so that a category and one inside it are two, or `TOP` to count by top-level category. A purchase whose merchant or category is not recognised could be anywhere: the rule is met only if the limit holds wherever it was, and otherwise its `onUnestablished` applies. A merchant the caller named is not a recognised one unless `acceptStated` is true. `baseline` (AMOUNT_DEVIATION_LIMIT) is `MEAN` or `MAX` of the purchases approved in the window before this one, in `currency`. `maxPercent` is a whole number from 100. `minSample` is required, and is 5 or more: with fewer approved purchases there is no usual amount, and the rule's `onUnestablished` applies. PURCHASE_HISTORY_MINIMUM is not met, rather than unestablished, when there are too few. Purchases that were refused or are waiting on a person are never part of either. A credential acting as an agent is not shown the configuration of these five conditions or the figures of their findings. |
configVersion | integer | |
participationrequired | PARTICIPATING | NOT_APPLICABLE | NOT_APPLICABLE: the policy governs this agent and is not about a purchase in the currency asked for. |
policyNamerequired | string | |
rulesrequired | PolicyRule[] | |
conditionsrequired | PolicyCondition[] | |
effectrequired | BLOCK | REQUIRE_APPROVAL | FLAG | What happens when `require` is violated. BLOCK refuses. REQUIRE_APPROVAL holds the budget and waits for a person. FLAG records the finding and changes nothing. |
id | UUID | |
keyrequired | string | The rule's identity across versions of its policy. Keep it when editing a rule: an approval is bound to it. |
name | string | null | |
onUnestablished | BLOCK | REQUIRE_APPROVAL | FLAG | What happens when `require` cannot be shown either way, for example because it rests on a merchant nobody has verified. Defaults to `effect`. FLAG exactly when `effect` is FLAG. |
requirerequired | PolicyExpression | What must be established for the purchase to proceed. |
when | PolicyExpression | null | |
valueSetsrequired | object[] | |
keyrequired | string | |
kindrequired | string | |
memberCountrequired | integer |
Webhooks#
Receive events when purchases are decided, budgets move, or connections change.
List webhook endpoints#
GET/v1/webhooks
Organisation-scoped API keyNo OAuth caller admitted
Every webhook endpoint on the account, including revoked ones — the delivery history against a revoked endpoint is the answer to "where were our events going in March".
Never carries a signing secret.
Organization-scoped API keys only. An agent-bound credential must not be able to decide where the whole account's events go, and no OAuth scope reaches this at all — admitting a connection would let an AI assistant point your event stream at a URL of its own choosing.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 600 per minute per credential.
Responses
| Status | Meaning |
|---|---|
200 | The account's endpoints. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
itemsrequired | WebhookEndpoint[] | |
apiVersionrequired | string | The webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH. |
consecutiveFailuresrequired | integer | Veriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it. |
createdAtrequired | Timestamp | |
description | string | null | |
disabledAt | Timestamp | null | |
idrequired | UUID | |
lastFailureAt | Timestamp | null | |
lastSuccessAt | Timestamp | null | |
revokedAt | Timestamp | null | |
statusrequired | ACTIVE | DISABLED | REVOKED | |
subscribedTypesrequired | WebhookEventType[] | |
urlrequired | string |
Create a webhook endpoint#
POST/v1/webhooks
Organisation-scoped API keyNo OAuth caller admitted
Registers a URL to receive events, and returns the signing secret.
The secret is shown once. Veriticity cannot show it again — store it before you close the response. If you lose it, rotate.
The URL must be HTTPS on port 443, must be a hostname rather than an IP literal, must carry no userinfo, and must resolve to a publicly routable address. This is checked now and again before every delivery. A URL that fails answers `webhook_url_not_permitted`.
New endpoints are created on the current webhook API version.
Organization-scoped API keys only. An agent-bound credential must not be able to decide where the whole account's events go, and no OAuth scope reaches this at all — admitting a connection would let an AI assistant point your event stream at a URL of its own choosing.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 120 per minute per credential.
Request body: at most 16384 bytes.
Request body
| Field | Type | Description |
|---|---|---|
description | string | null | For your own reference. Never sent anywhere. |
subscribedTypesrequired | WebhookEventType[] | Choose at least one event to receive. |
urlrequired | string |
Responses
| Status | Meaning |
|---|---|
201 | Created. **Carries the signing secret, once.** |
400 | invalid_requestmalformed_jsonwebhook_url_not_permitted |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
402 | feature_not_includedplan_limit_exceeded |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permittedbilling_suspended |
409 | resource_limit_reached |
413 | payload_too_large |
415 | unsupported_media_type |
429 | rate_limited |
500 | internal_error |
503 | webhook_not_configured |
Response body
| Field | Type | Description |
|---|---|---|
apiVersionrequired | string | The webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH. |
consecutiveFailuresrequired | integer | Veriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it. |
createdAtrequired | Timestamp | |
description | string | null | |
disabledAt | Timestamp | null | |
idrequired | UUID | |
lastFailureAt | Timestamp | null | |
lastSuccessAt | Timestamp | null | |
revokedAt | Timestamp | null | |
statusrequired | ACTIVE | DISABLED | REVOKED | |
subscribedTypesrequired | WebhookEventType[] | |
urlrequired | string | |
secretrequired | string | The signing secret, shown exactly once. Veriticity cannot show it again. Store it before you close the response. |
Read a webhook endpoint#
GET/v1/webhooks/{webhookId}
Organisation-scoped API keyNo OAuth caller admitted
One endpoint, including its operational health. `consecutiveFailures` is the number that tells you a receiver has been refusing events since Tuesday; any success resets it.
Never carries a signing secret.
Organization-scoped API keys only. An agent-bound credential must not be able to decide where the whole account's events go, and no OAuth scope reaches this at all — admitting a connection would let an AI assistant point your event stream at a URL of its own choosing.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
webhookIdrequired | path | The endpoint to read. |
Responses
| Status | Meaning |
|---|---|
200 | The endpoint. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
apiVersionrequired | string | The webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH. |
consecutiveFailuresrequired | integer | Veriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it. |
createdAtrequired | Timestamp | |
description | string | null | |
disabledAt | Timestamp | null | |
idrequired | UUID | |
lastFailureAt | Timestamp | null | |
lastSuccessAt | Timestamp | null | |
revokedAt | Timestamp | null | |
statusrequired | ACTIVE | DISABLED | REVOKED | |
subscribedTypesrequired | WebhookEventType[] | |
urlrequired | string |
Update a webhook endpoint#
PATCH/v1/webhooks/{webhookId}
Organisation-scoped API keyNo OAuth caller admitted
Changes the URL, description, subscriptions, status or API version. Send at least one field.
Omitting `description` leaves it alone; sending `null` clears it.
`status` accepts `ACTIVE` and `DISABLED` only. Revoking destroys key material and cannot be undone, so it is its own verb: `DELETE /v1/webhooks/{webhookId}`.
The secret is never changed here — use rotate-secret.
Organization-scoped API keys only. An agent-bound credential must not be able to decide where the whole account's events go, and no OAuth scope reaches this at all — admitting a connection would let an AI assistant point your event stream at a URL of its own choosing.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 120 per minute per credential.
Request body: at most 16384 bytes.
Parameters
| Name | In | Description |
|---|---|---|
webhookIdrequired | path | The endpoint to update. |
Request body
| Field | Type | Description |
|---|---|---|
apiVersion | string | |
description | string | null | |
status | ACTIVE | DISABLED | |
subscribedTypes | WebhookEventType[] | |
url | string |
Responses
| Status | Meaning |
|---|---|
200 | The updated endpoint. |
400 | invalid_requestmalformed_jsonwebhook_url_not_permitted |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
409 | webhook_endpoint_revoked |
413 | payload_too_large |
415 | unsupported_media_type |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
apiVersionrequired | string | The webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH. |
consecutiveFailuresrequired | integer | Veriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it. |
createdAtrequired | Timestamp | |
description | string | null | |
disabledAt | Timestamp | null | |
idrequired | UUID | |
lastFailureAt | Timestamp | null | |
lastSuccessAt | Timestamp | null | |
revokedAt | Timestamp | null | |
statusrequired | ACTIVE | DISABLED | REVOKED | |
subscribedTypesrequired | WebhookEventType[] | |
urlrequired | string |
Revoke a webhook endpoint#
DELETE/v1/webhooks/{webhookId}
Organisation-scoped API keyNo OAuth caller admitted
Stops delivery permanently and destroys the endpoint's signing secrets. Revocation is terminal — it cannot be undone, and a revoked endpoint cannot be re-enabled. Create a new one instead.
Returns the endpoint in its revoked state rather than `204`: the row is not gone, it still appears in listings, and its delivery history survives.
Organization-scoped API keys only. An agent-bound credential must not be able to decide where the whole account's events go, and no OAuth scope reaches this at all — admitting a connection would let an AI assistant point your event stream at a URL of its own choosing.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 120 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
webhookIdrequired | path | The endpoint to revoke. |
Responses
| Status | Meaning |
|---|---|
200 | The endpoint, now revoked. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
409 | webhook_endpoint_revoked |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
apiVersionrequired | string | The webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH. |
consecutiveFailuresrequired | integer | Veriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it. |
createdAtrequired | Timestamp | |
description | string | null | |
disabledAt | Timestamp | null | |
idrequired | UUID | |
lastFailureAt | Timestamp | null | |
lastSuccessAt | Timestamp | null | |
revokedAt | Timestamp | null | |
statusrequired | ACTIVE | DISABLED | REVOKED | |
subscribedTypesrequired | WebhookEventType[] | |
urlrequired | string |
Read an endpoint's delivery history#
GET/v1/webhooks/{webhookId}/deliveries
Organisation-scoped API keyNo OAuth caller admitted
Recent delivery attempts, newest first — what was sent, what your server answered, and when the next attempt is due.
`hasMore` rather than a total: what a debugging session wants is "is there anything older", and counting a table that only grows costs a scan to answer a question nobody asks.
This is a third pagination model, distinct from the page/pageSize used by `GET /v1/policies`.
Organization-scoped API keys only. An agent-bound credential must not be able to decide where the whole account's events go, and no OAuth scope reaches this at all — admitting a connection would let an AI assistant point your event stream at a URL of its own choosing.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 600 per minute per credential.
Parameters
| Name | In | Description |
|---|---|---|
webhookIdrequired | path | The endpoint whose deliveries these are. |
limit | query | How many attempts to return. Clamped to 100 rather than refused. |
Responses
| Status | Meaning |
|---|---|
200 | A page of delivery attempts. |
400 | invalid_request |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
429 | rate_limited |
500 | internal_error |
Response body
| Field | Type | Description |
|---|---|---|
hasMorerequired | boolean | |
itemsrequired | WebhookDelivery[] | |
attemptCountrequired | integer | |
createdAtrequired | Timestamp | |
deliveredAt | Timestamp | null | |
eventIdrequired | UUID | The logical event, and your deduplication key. Identical to the Veriticity-Webhook-Id header, and identical at every endpoint subscribed to this event. |
eventTyperequired | DeliverableWebhookEventType | |
idrequired | UUID | This delivery: one endpoint's attempt stream for one event. |
lastAttemptAt | Timestamp | null | |
lastDurationMs | integer | null | |
lastErrorClass | string | null | |
lastResponseSnippet | string | null | |
lastStatusCode | integer | null | |
nextAttemptAt | Timestamp | null | |
occurredAtrequired | Timestamp | When it happened in the domain, not when it was sent. |
statusrequired | string |
Rotate an endpoint's signing secret#
POST/v1/webhooks/{webhookId}/rotate-secret
Organisation-scoped API keyNo OAuth caller admitted
Issues a new signing secret and returns it once.
By default the old secret keeps signing for 24 hours alongside the new one, so every delivery in that window carries two signatures and a receiver verifying with either succeeds. That is what lets you deploy the new secret without coordinating a cutover.
Send `{"retireImmediately": true}` to kill the old secret at once, which is what a leak calls for.
The body is optional: an absent body means the defaults. This is the only operation on the API that does not require a JSON body when it takes one.
Organization-scoped API keys only. An agent-bound credential must not be able to decide where the whole account's events go, and no OAuth scope reaches this at all — admitting a connection would let an AI assistant point your event stream at a URL of its own choosing.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 120 per minute per credential.
Request body: at most 16384 bytes.
Parameters
| Name | In | Description |
|---|---|---|
webhookIdrequired | path | The endpoint to rotate. |
Request body (optional)
| Field | Type | Description |
|---|---|---|
retireImmediately | boolean | Retire the old secret now instead of after 24 hours. Deliveries signed with it stop verifying immediately. |
Responses
| Status | Meaning |
|---|---|
200 | Rotated. **Carries the new signing secret, once.** |
400 | invalid_requestmalformed_json |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
409 | webhook_endpoint_revoked |
429 | rate_limited |
500 | internal_error |
503 | webhook_not_configured |
Response body
| Field | Type | Description |
|---|---|---|
apiVersionrequired | string | The webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH. |
consecutiveFailuresrequired | integer | Veriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it. |
createdAtrequired | Timestamp | |
description | string | null | |
disabledAt | Timestamp | null | |
idrequired | UUID | |
lastFailureAt | Timestamp | null | |
lastSuccessAt | Timestamp | null | |
revokedAt | Timestamp | null | |
statusrequired | ACTIVE | DISABLED | REVOKED | |
subscribedTypesrequired | WebhookEventType[] | |
urlrequired | string | |
secretrequired | string | The signing secret, shown exactly once. Veriticity cannot show it again. Store it before you close the response. |
Send a test event#
POST/v1/webhooks/{webhookId}/test
Organisation-scoped API keyNo OAuth caller admitted
Queues a real `webhook.test` event through the real pipeline: signed with your real secret, recorded as a real delivery, retried on the real schedule. There is no verification handshake — this is better, because it proves the thing you actually care about on every request rather than once.
Answers 202, not 200: the event is queued rather than delivered. Use the returned ids to find it in the delivery history.
Delivered regardless of what the endpoint subscribes to.
Organization-scoped API keys only. An agent-bound credential must not be able to decide where the whole account's events go, and no OAuth scope reaches this at all — admitting a connection would let an AI assistant point your event stream at a URL of its own choosing.
Credential: An organization-scoped API key. No OAuth scope reaches this operation.
Rate limit: 5 per minute per webhook endpoint; 30 per 15 minutes per organization.
Parameters
| Name | In | Description |
|---|---|---|
webhookIdrequired | path | The endpoint to send a test to. |
Responses
| Status | Meaning |
|---|---|
202 | Queued. Look in the delivery history for what happened. |
401 | unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token |
403 | connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted |
404 | not_found |
409 | webhook_endpoint_revoked |
429 | rate_limited |
500 | internal_error |
503 | webhook_not_configuredtemporarily_unavailable |
Response body
| Field | Type | Description |
|---|---|---|
deliveryIdrequired | UUID | |
eventIdrequired | UUID | |
statusrequired | "queued" |
Using the OpenAPI document#
The document is OpenAPI 3.1, so its schemas are JSON Schema 2020-12 and can be used directly by a validator. It is generated from the application’s own constants and regenerated on every build, so it cannot describe an endpoint that does not exist or omit an error code the API can return.
curl -s https://docs.veriticity.com/openapi.json -o veriticity.json
