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#

MethodPathCredential
GET/v1/purchase-requestsAPI key bound to an agent · purchase:read
POST/v1/purchase-requestsAPI 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}/cancelAPI key of either kind · purchase:request
POST/v1/purchase-requests/{purchaseRequestId}/outcomeAPI key of either kind · purchase:request
GET/v1/purchase-authorizationsOrganisation-scoped API key
GET/v1/purchase-authorizations/{authorizationId}Organisation-scoped API key
POST/v1/purchase-authorizations/{authorizationId}/execution-tokenAPI key bound to an agent
POST/v1/purchase-authorizations/{authorizationId}/useAPI key of either kind
POST/v1/purchase-authorizations/{authorizationId}/voidOrganisation-scoped API key
POST/v1/purchase-authorizations/{authorizationId}/withdrawAPI key of either kind · purchase:request
POST/v1/simulations/current-policiesAPI key of either kind · purchase:check
POST/v1/simulations/draft-policyOrganisation-scoped API key
POST/v1/simulations/policy-versionOrganisation-scoped API key
GET/v1/policiesOrganisation-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}/validateOrganisation-scoped API key
GET/v1/agentAPI key bound to an agent · purchase:check
GET/v1/agentsOrganisation-scoped API key
GET/v1/budget-statusAPI key bound to an agent · purchase:check
GET/v1/spending-rulesAPI key bound to an agent · purchase:check
GET/v1/webhooksOrganisation-scoped API key
POST/v1/webhooksOrganisation-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}/deliveriesOrganisation-scoped API key
POST/v1/webhooks/{webhookId}/rotate-secretOrganisation-scoped API key
POST/v1/webhooks/{webhookId}/testOrganisation-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

NameInDescription
statusqueryOnly purchases in this state.
sincequeryOnly purchases requested at or after this instant.
untilqueryOnly purchases requested before this instant.
limitqueryHow many to return.
cursorqueryThe `nextCursor` from the previous page. An unrecognised value returns the first page.

Responses

StatusMeaning
200One page of purchases.
400invalid_request
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope
429rate_limited
500internal_error

Response body

FieldTypeDescription
hasMorerequiredboolean
nextCursorrequiredstring | null
purchasesrequiredPurchaseReadback[]
agentrequiredobject
idrequiredUUID
namerequiredstring
statusrequiredENABLED | DISABLED | ARCHIVED
amountMinorrequiredMinorAmount
approvalobject | null
commentrequiredstring | null
escalationReasonCodesrequiredReasonCode[]
expiresAtrequiredTimestamp
isPendingrequiredboolean
resolvedAtrequiredTimestamp | null
resolvedByrequiredobject | null
staterequiredPENDING | APPROVED | REJECTED | EXPIRED | CANCELLED
authorizationPurchaseAuthorizationSummary | null
expiresAtrequiredTimestamp
idrequiredUUID
statusrequiredactive | used | expired | cancelled`active` until `expiresAt`, unless it is used or cancelled first. An expired authorization is not renewed: request the purchase again.
categorystring | null
currencyrequiredCurrency
decisionobject | null
authorityrequiredWITHIN_POLICY | AUTHORIZED | null
engineVersionrequiredstring
evaluatedAtrequiredTimestamp
explanationrequiredstring
kindrequiredstringINITIAL, or APPROVAL_REEVALUATION once a person answered.
outcomerequiredAPPROVED | REJECTED | REQUIRES_APPROVAL
reasonCodesrequiredReasonCode[]
idrequiredUUID
initiatedByobject | null
namerequiredstring
maximumMinorrequiredMinorAmount | nullThe 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.
merchantrequiredstring
merchantDomainstring | null
originDASHBOARD | API | MCP | null
reasonrequiredstring
requestedAtrequiredTimestamp
resolvedAtTimestamp | null
statusrequiredEVALUATING | PENDING_APPROVAL | APPROVED | REJECTED | EXPIRED | CANCELLED
submittedViaobject | 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

NameInDescription
Idempotency-KeyrequiredheaderRequired. 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

FieldTypeDescription
amountMinorrequiredstring | integerA 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.
categoryelectronics | 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 | otherRequired 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.
chargesobject[]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.
amountMinorstring
classrequiredKNOWN | VARIABLE | UNEXPLAINED
descriptionstringAt most 200 bytes.
kindTAX | DELIVERY | DUTY | FEE | DISCOUNT
maximumMinorstringA variable charge's most.
currencystring | nullDefaults to the organisation's own currency when omitted.
itemsobject[]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.
categoryelectronics | 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 | otherThe item's category. Omit it only for an item whose category is uncertain.
categoryUncertainboolean`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.
descriptionrequiredstringWhat the item is. At most 500 bytes.
discountMinorstringA discount on this line, in minor units. Defaults to 0.
itemIdstringYour own label for the line, unique in the basket. At most 64 bytes.
optionsobject[]
namerequiredstring
valuerequiredstring
possibleCategorieselectronics | 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.
productReferenceobject
idrequiredstring
providerrequiredstring
productUrlstringThe product's https page, on `merchantDomain` or the same website with or without its `www` label.
quantityrequiredinteger
riskTagsmedicines | 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.
uncertaintyReasonstringWhy the category can't be established. At most 200 bytes.
unitAmountMinorrequiredstringThe price of one, in minor units.
merchantrequiredstringWho 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.
merchantDomainstring | nullThe 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.
merchantReferenceCommerceReference | nullA 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.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
metadataobject | nullAnything you want recorded alongside the purchase, as a JSON object of at most 16 KiB, nested at most 8 levels. No rule reads it.
productReferenceCommerceReference | nullAn 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.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
reasonrequiredstringWhy the agent wants this purchase. Every purchase must say.
taxIncludedbooleanRequired with `items`: whether the item prices already include tax. When `true`, tax is not stated again.

Responses

StatusMeaning
200An idempotent replay. The stored decision, returned again; no new budget was reserved. `replayed` is true.
201The purchase was evaluated and a decision recorded.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402purchase_allowance_exhaustedplan_requiredplan_limit_exceededfeature_not_included
403connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scopebilling_suspended
409idempotency_key_reuse
413payload_too_large
415unsupported_media_type
429rate_limited
500internal_error
503temporarily_unavailable

Response body

FieldTypeDescription
approvalrequiredobjectWhether a person still has to answer, and by when. The budget hold behind an escalation is released when the approval lapses.
expiresAtrequiredTimestamp | null
requiredrequiredboolean
authorizationrequiredPurchaseAuthorizationSummary | null
expiresAtrequiredTimestamp
idrequiredUUID
statusrequiredactive | used | expired | cancelled`active` until `expiresAt`, unless it is used or cancelled first. An expired authorization is not renewed: request the purchase again.
decisionrequiredDecision
authorityWITHIN_POLICY | AUTHORIZED | null
engineVersionrequiredstring
evaluatedAtrequiredTimestamp
explanationrequiredstringThe sentence a person reads. Not a stable contract.
idrequiredUUID
outcomerequiredAPPROVED | REJECTED | REQUIRES_APPROVALBranch on this, never on the HTTP status code.
reasonCodesrequiredReasonCode[]
findingsrequiredFinding[]
currencyCurrency | null
headroomMinorMinorAmount | null
limitMinorMinorAmount | null
measuredMeasuredWindow | null
aggregaterequiredSUM_AMOUNT | COUNTSUM_AMOUNT: a sum of money. COUNT: a number of purchases.
scoperequiredstringWhose purchases the window holds: POLICY (the agents the policy binds), ORGANIZATION, EACH_AGENT, AGENT:<agent id>, EACH_PRINCIPAL or PRINCIPAL:<user id>.
windowrequiredobject
messagerequiredstring
observedMinorMinorAmount | null
outcomerequiredPASS | NOT_APPLICABLE | FLAG | REQUIRE_APPROVAL | BLOCK | AUTHORIZEDPASS: 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.
policyIdrequiredUUID
policyNamerequiredstring
policyRuleIdUUID | null
policyVersionIdrequiredUUID
policyVersionNumberrequiredinteger
reasonCoderequiredReasonCode
rolerequiredBINDING | SATISFIED | NOT_APPLICABLEHow the finding stands against the decision.
rulerequiredstringThe rule's name, or its key when it has none.
ruleKeystring | null
unitMONEY | COUNT | null
policyVersionsrequiredobject[]
applicabilityReasonrequiredstring
policyVersionIdrequiredUUID
purchaseRequestrequiredPurchaseRequestSummary
amountMinorrequiredMinorAmount
currencyrequiredCurrency
idrequiredUUID
maximumMinorrequiredMinorAmount | nullThe 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.
statusrequiredEVALUATING | PENDING_APPROVAL | APPROVED | REJECTED | EXPIRED | CANCELLED
replayedrequiredbooleanTrue 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

NameInDescription
purchaseRequestIdrequiredpathThe purchase request to read.

Responses

StatusMeaning
200The purchase, as its submitter may see it.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
purchaserequiredPurchaseReadback
agentrequiredobject
idrequiredUUID
namerequiredstring
statusrequiredENABLED | DISABLED | ARCHIVED
amountMinorrequiredMinorAmount
approvalobject | null
commentrequiredstring | null
escalationReasonCodesrequiredReasonCode[]
expiresAtrequiredTimestamp
isPendingrequiredboolean
resolvedAtrequiredTimestamp | null
resolvedByrequiredobject | null
staterequiredPENDING | APPROVED | REJECTED | EXPIRED | CANCELLED
authorizationPurchaseAuthorizationSummary | null
expiresAtrequiredTimestamp
idrequiredUUID
statusrequiredactive | used | expired | cancelled`active` until `expiresAt`, unless it is used or cancelled first. An expired authorization is not renewed: request the purchase again.
categorystring | null
currencyrequiredCurrency
decisionobject | null
authorityrequiredWITHIN_POLICY | AUTHORIZED | null
engineVersionrequiredstring
evaluatedAtrequiredTimestamp
explanationrequiredstring
kindrequiredstringINITIAL, or APPROVAL_REEVALUATION once a person answered.
outcomerequiredAPPROVED | REJECTED | REQUIRES_APPROVAL
reasonCodesrequiredReasonCode[]
idrequiredUUID
initiatedByobject | null
namerequiredstring
maximumMinorrequiredMinorAmount | nullThe 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.
merchantrequiredstring
merchantDomainstring | null
originDASHBOARD | API | MCP | null
reasonrequiredstring
requestedAtrequiredTimestamp
resolvedAtTimestamp | null
statusrequiredEVALUATING | PENDING_APPROVAL | APPROVED | REJECTED | EXPIRED | CANCELLED
submittedViaobject | 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

NameInDescription
purchaseRequestIdrequiredpathThe purchase request to cancel.

Request body (optional)

FieldTypeDescription
reasonstring | nullShown to your organization's members in the audit trail.

Responses

StatusMeaning
200The purchase, cancelled.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedinsufficient_scope
404not_found
409purchase_authorization_conflict
413payload_too_large
429rate_limited
500internal_error

Response body

FieldTypeDescription
purchaseRequestrequiredobject
idrequiredUUID
statusrequiredCANCELLED
replayedrequiredboolean

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

NameInDescription
purchaseRequestIdrequiredpathThe purchase request the report is about.

Request body

FieldTypeDescription
currencyrequiredstringThe purchase's currency.
finalAmountMinorstringWhat was charged in total. COMPLETED and PARTIAL only, and required for both.
itemPositionsinteger[]PARTIAL only: the bought items, by their zero-based position in the request's `items`.
merchantOrderReferencestringAt most 255 bytes in UTF-8 once trimmed, with no control characters.
observedAtstringWhen the outcome was seen. Not in the future.
outcomerequiredCOMPLETED | COMPLETED_AMOUNT_UNKNOWN | PARTIAL | FAILED | REFUNDED
refundMinorstringREFUNDED only: the amount refunded.
reportIdrequiredstring
variableFinalobjectCOMPLETED only: each variable charge's kind and its final. Exactly the purchase's variable charges.
DELIVERYstring
DUTYstring
TAXstring

Responses

StatusMeaning
200The report, recorded, and what the purchase is recorded at now.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedinsufficient_scope
404not_found
409report_id_reusepurchase_outcome_conflict
413payload_too_large
415unsupported_media_type
429rate_limited
500internal_error
503temporarily_unavailable

Response body

FieldTypeDescription
outcomeReportrequiredobject
purchaseRequestIdrequiredUUID
recordedrequiredtrue
reportIdrequiredstring
sourcerequiredAGENT_REPORT
purchaserequiredobjectnullThe purchase the report was recorded against, to check it was the one meant.
reconciliationrequiredobject
countedMinorrequiredstringWhat it is recorded at: exposure until an accepted outcome says otherwise.
currencyrequiredstring
exposureMinorrequiredstringThe most the purchase was approved for.
outcomerequiredNOT_REPORTED | COMPLETED | COMPLETED_AMOUNT_UNKNOWN | PARTIAL | FAILED | REFUNDED | CONFLICTING
staterequiredUNRECONCILED | RECONCILED | RECONCILED_WITH_EXCEPTION | CORRECTED
replayedrequiredboolean

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.

List purchase authorizations#

GET/v1/purchase-authorizations

Organisation-scoped API keyNo OAuth caller admitted

Your organization's purchase authorizations, newest first.

Filter with `status`. `status=expired` is the one to act on: authorizations that were never used and have expired. An expiry releases nothing, so each of their purchases still counts towards your budgets until you withdraw it.

No entry carries the purchase's terms.

Requires an organization-scoped API key.

Credential: An organization-scoped API key. No OAuth scope reaches this operation.

Rate limit: 600 per minute per credential.

Parameters

NameInDescription
statusqueryOnly authorizations in this state.
cursorquery`nextCursor` from the previous page.
limitqueryBetween 1 and 100. Defaults to 25.

Responses

StatusMeaning
200One page of authorizations.
400invalid_request
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
429rate_limited
500internal_error

Response body

FieldTypeDescription
authorizationsrequiredPurchaseAuthorization[]
cancellationrequiredobject | null
cancelledAtrequiredTimestamp
kindrequiredwithdrawn | voided
reasonrequiredstring | null
expiresAtrequiredTimestamp
idrequiredUUID
issuedAtrequiredTimestamp
purchaseRequestIdrequiredUUID
statusrequiredactive | used | expired | withdrawn | voided
usablerequiredbooleanWhether it can be used right now: active, in an active organization, for an agent that is enabled.
userequiredobject | null
executionReferencerequiredstring
usedAtrequiredTimestamp
nextCursorrequiredstring | null

Read the state of a purchase authorization#

GET/v1/purchase-authorizations/{authorizationId}

Organisation-scoped API keyNo OAuth caller admitted

Whether a purchase authorization is active, used, expired, withdrawn or voided, and whether it can be used right now.

It returns none of the purchase's terms. An authorization from another organization and a malformed id are the same 404.

Requires an organization-scoped API key. An agent reads the authorization of its own purchase on the purchase.

Credential: An organization-scoped API key. No OAuth scope reaches this operation.

Rate limit: 600 per minute per credential.

Parameters

NameInDescription
authorizationIdrequiredpathThe purchase authorization to read.

Responses

StatusMeaning
200The authorization, as the organization may see it.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
authorizationrequiredPurchaseAuthorization
cancellationrequiredobject | null
cancelledAtrequiredTimestamp
kindrequiredwithdrawn | voided
reasonrequiredstring | null
expiresAtrequiredTimestamp
idrequiredUUID
issuedAtrequiredTimestamp
purchaseRequestIdrequiredUUID
statusrequiredactive | used | expired | withdrawn | voided
usablerequiredbooleanWhether it can be used right now: active, in an active organization, for an agent that is enabled.
userequiredobject | null
executionReferencerequiredstring
usedAtrequiredTimestamp

Get an execution token for your own purchase authorization#

POST/v1/purchase-authorizations/{authorizationId}/execution-token

API key bound to an agentNo OAuth caller admitted

Issues the token an agent's key sends to use one purchase authorization. Only the key that submitted the purchase can ask, and only while the authorization is `active`.

The token is shown once and works for this authorization only. It has no lifetime of its own: it stops working when the authorization is used, withdrawn, voided or expires, whichever is first, and when another token is issued for it. It does not change the authorization's terms or its expiry, and grants nothing else.

Asking again issues a new token and the earlier one stops working, so a lost response is recovered by asking again. A used authorization gets no new token: to retry a use whose response was lost, send the same token, reference and terms again.

Requires an API key bound to an agent. An organization-scoped key uses authorizations without a token, and no OAuth connection can ask.

Credential: An API key bound to an agent. No OAuth scope reaches this operation.

Rate limit: 120 per minute per credential; 600 per minute per organization.

Parameters

NameInDescription
authorizationIdrequiredpathThe purchase authorization the token is for.

Responses

StatusMeaning
201A token, shown once.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentoauth_not_permitted
404not_found
409purchase_authorization_conflict
429rate_limited
500internal_error
503temporarily_unavailable

Response body

FieldTypeDescription
executionTokenrequiredobject
authorizationIdrequiredUUID
expiresAtrequiredTimestampThe authorization's own expiry. The token stops working then at the latest, and sooner if the authorization is used, withdrawn or voided, or another token is issued for it.
tokenrequiredstringShown once. Send it in the `Veriticity-Execution-Token` header when using this authorization. Only a hash is kept.
replacedrequiredbooleanTrue when an earlier token for this authorization stopped working.

Use a purchase authorization#

POST/v1/purchase-authorizations/{authorizationId}/use

API key of either kindNo OAuth caller admitted

Say what you are about to do, and learn whether the authorization covers exactly that. A match uses it, in the same step, and it cannot be used again.

`authorized` means the purchase authorization was verified and consumed. It does not mean a purchase was made. Veriticity does not execute the purchase and is not told its outcome.

A refusal is an answer, so it is a 200: branch on `result`, never on the HTTP status code, and do not proceed unless it is `authorized`. A refusal changes nothing: the authorization is exactly as it was, whatever was presented and however many times.

Terms are compared exactly. The amount must be the authorized amount to the minor unit, and every identifier the purchase stated must be presented and must match. `merchant` is compared with what was submitted; it is never looked up again. You are not told which term failed.

`executionReference` is your own identifier for this execution. Repeating a successful call with the same reference and the same terms returns the original answer with `replayed: true`.

Takes an API key of either kind. An agent's key uses only its own agent's authorization, and must send the execution token issued for it in `Veriticity-Execution-Token`: see `POST /v1/purchase-authorizations/{authorizationId}/execution-token`. Another agent's authorization is the same 404 as an unknown one. The organization's own key sends no token. No OAuth connection can use an authorization.

An authorization is used in Live only while the organization has a plan that includes purchase requests. Once its trial has ended, or with no plan, a use is refused with `plan_required` until an owner chooses one.

Credential: An API key of either kind. No OAuth scope reaches this operation.

Rate limit: 120 per minute per credential; 600 per minute per organization.

Request body: at most 16384 bytes, or 655360 bytes when it states `items`.

Parameters

NameInDescription
authorizationIdrequiredpathThe purchase authorization to use.
Veriticity-Execution-TokenheaderRequired with an agent's key, refused with the organization's own key. The token issued for this authorization. A missing or malformed one is a 400 `invalid_request`; one that is not the latest issued for this authorization is refused with `execution_token_invalid`.

Request body

FieldTypeDescription
agentIdUUID | nullOptional. When you know which agent you are acting for, present it and it must match.
amountMinorrequiredstring | integerThe amount about to be paid, in ISO 4217 minor units: the authorized amount exactly, or for a purchase with charges that can still vary, its known amount plus every `variableFinal`.
categoryelectronics | 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 | otherThe category the purchase stated, when it stated one. Leave it out when it stated `items`, or none.
chargesobject[]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.
amountMinorstring
classrequiredKNOWN | VARIABLE | UNEXPLAINED
descriptionstringAt most 200 bytes.
kindTAX | DELIVERY | DUTY | FEE | DISCOUNT
maximumMinorstringA variable charge's most.
currencyrequiredstringRequired. It must equal the authorized currency.
executionReferencerequiredstringYour own identifier for this execution, such as an order id. A retry carries the same one.
itemsobject[]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.
categoryelectronics | 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 | otherThe item's category. Omit it only for an item whose category is uncertain.
categoryUncertainboolean`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.
descriptionrequiredstringWhat the item is. At most 500 bytes.
discountMinorstringA discount on this line, in minor units. Defaults to 0.
itemIdstringYour own label for the line, unique in the basket. At most 64 bytes.
optionsobject[]
namerequiredstring
valuerequiredstring
possibleCategorieselectronics | 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.
productReferenceobject
idrequiredstring
providerrequiredstring
productUrlstringThe product's https page, on `merchantDomain` or the same website with or without its `www` label.
quantityrequiredinteger
riskTagsmedicines | 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.
uncertaintyReasonstringWhy the category can't be established. At most 200 bytes.
unitAmountMinorrequiredstringThe price of one, in minor units.
merchantrequiredstringThe merchant, as it was named when the purchase was submitted. Compared with that; never looked up.
merchantDomainstring | nullRequired when the purchase stated one, and it must match. Leave it out when the purchase stated none: presenting one then does not match.
merchantReferenceCommerceReference | nullAs `merchantDomain`: present it exactly when the purchase stated one.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
productReferenceCommerceReference | nullAs `merchantDomain`: present it exactly when the purchase stated one.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
taxIncludedbooleanRequired with `items`: whether the item prices already include tax. When `true`, tax is not stated again.
variableFinalobjectOne final for each `VARIABLE` charge the purchase stated, by kind, in minor units from 0. A final above its `maximumMinor` is refused with `allowance_exceeded`.
DELIVERYstringWhat the variable delivery came to.
DUTYstringWhat the variable duty came to.
TAXstringWhat the variable tax came to.

Responses

StatusMeaning
200The answer: `authorized`, or `refused` with a reason. Both are successful calls.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedoauth_not_permitted
404not_found
413payload_too_large
415unsupported_media_type
429rate_limited
500internal_error
503temporarily_unavailable

Response body

FieldTypeDescription
authorizationobjectPresent when `result` is `authorized`.
expiresAtrequiredTimestamp
idrequiredUUID
statusrequiredused
usedAtrequiredTimestamp
reasonwithdrawn | voided | already_used | expired | not_usable | terms_mismatch | allowance_exceeded | execution_token_invalid | plan_requiredPresent when `result` is `refused`. `not_usable`: it cannot be used right now, or ever, for a purchase recorded before its terms were. `terms_mismatch`: at least one term did not match; which one is not said. `allowance_exceeded`: a variable charge's final is above its maximum; ask again. `execution_token_invalid`: the execution token is not the latest one issued for this authorization. `plan_required`: the organisation's trial has ended or it has no plan; it can be used once an owner chooses a plan, until it expires. A refusal leaves the authorization unchanged.
replayedbooleanPresent when `result` is `authorized`. True when this execution had already used the authorization and this is the original answer again.
resultrequiredauthorized | refused

Void a used purchase authorization#

POST/v1/purchase-authorizations/{authorizationId}/void

Organisation-scoped API keyNo OAuth caller admitted

Says an authorization was used and the purchase did not complete. Its purchase becomes `CANCELLED` and stops counting towards budgets. This can't be undone.

Works only on a `used` authorization. An unused one is refused with `purchase_authorization_conflict`: withdraw it instead.

Voiding is not a refund or a reversal. Veriticity moves no money and tells no payment system anything. It records what you say happened.

Repeating the call returns the same authorization with `replayed: true` and changes nothing.

Requires an organization-scoped API key.

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

NameInDescription
authorizationIdrequiredpathThe purchase authorization to void.

Request body

FieldTypeDescription
reasonstring | nullShown to your organization's members in the audit trail.

Responses

StatusMeaning
200The authorization, voided.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
409purchase_authorization_conflict
413payload_too_large
429rate_limited
500internal_error

Response body

FieldTypeDescription
authorizationrequiredPurchaseAuthorization
cancellationrequiredobject | null
cancelledAtrequiredTimestamp
kindrequiredwithdrawn | voided
reasonrequiredstring | null
expiresAtrequiredTimestamp
idrequiredUUID
issuedAtrequiredTimestamp
purchaseRequestIdrequiredUUID
statusrequiredactive | used | expired | withdrawn | voided
usablerequiredbooleanWhether it can be used right now: active, in an active organization, for an agent that is enabled.
userequiredobject | null
executionReferencerequiredstring
usedAtrequiredTimestamp
replayedrequiredbooleanTrue when this had already been done and nothing changed.

Withdraw an unused purchase authorization#

POST/v1/purchase-authorizations/{authorizationId}/withdraw

API key of either kindOAuth: purchase:request

Says an authorization that has not been used will not be. It can't be used afterwards, its purchase becomes `CANCELLED`, and the purchase stops counting towards budgets. This can't be undone.

Works on an `active` or an `expired` authorization. A `used` one is refused with `purchase_authorization_conflict`: void it instead.

Withdrawing is the only way an unused approval stops counting. An expiry does not release it.

Repeating the call returns the same authorization with `replayed: true` and changes nothing.

An organization-scoped key may withdraw any authorization. The credential that submitted a purchase may withdraw its authorization while it is unused, giving up authority it no longer needs; another credential's 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

NameInDescription
authorizationIdrequiredpathThe purchase authorization to withdraw.

Request body

FieldTypeDescription
reasonstring | nullShown to your organization's members in the audit trail.

Responses

StatusMeaning
200The authorization, withdrawn.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedinsufficient_scope
404not_found
409purchase_authorization_conflict
413payload_too_large
429rate_limited
500internal_error

Response body

FieldTypeDescription
authorizationrequiredPurchaseAuthorization
cancellationrequiredobject | null
cancelledAtrequiredTimestamp
kindrequiredwithdrawn | voided
reasonrequiredstring | null
expiresAtrequiredTimestamp
idrequiredUUID
issuedAtrequiredTimestamp
purchaseRequestIdrequiredUUID
statusrequiredactive | used | expired | withdrawn | voided
usablerequiredbooleanWhether it can be used right now: active, in an active organization, for an agent that is enabled.
userequiredobject | null
executionReferencerequiredstring
usedAtrequiredTimestamp
replayedrequiredbooleanTrue when this had already been done and nothing changed.

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

FieldTypeDescription
agentIdUUID | nullRequired 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.
amountMinorrequiredstring | integerA 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.
categoryelectronics | 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 | otherRequired 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.
chargesobject[]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.
amountMinorstring
classrequiredKNOWN | VARIABLE | UNEXPLAINED
descriptionstringAt most 200 bytes.
kindTAX | DELIVERY | DUTY | FEE | DISCOUNT
maximumMinorstringA variable charge's most.
currencystring | nullDefaults to the organisation's own currency when omitted.
itemsobject[]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.
categoryelectronics | 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 | otherThe item's category. Omit it only for an item whose category is uncertain.
categoryUncertainboolean`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.
descriptionrequiredstringWhat the item is. At most 500 bytes.
discountMinorstringA discount on this line, in minor units. Defaults to 0.
itemIdstringYour own label for the line, unique in the basket. At most 64 bytes.
optionsobject[]
namerequiredstring
valuerequiredstring
possibleCategorieselectronics | 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.
productReferenceobject
idrequiredstring
providerrequiredstring
productUrlstringThe product's https page, on `merchantDomain` or the same website with or without its `www` label.
quantityrequiredinteger
riskTagsmedicines | 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.
uncertaintyReasonstringWhy the category can't be established. At most 200 bytes.
unitAmountMinorrequiredstringThe price of one, in minor units.
merchantrequiredstringWho 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.
merchantDomainstring | nullThe 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.
merchantReferenceCommerceReference | nullA 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.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
productReferenceCommerceReference | nullAn 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.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
reasonrequiredstringWhy the agent wants this purchase. Every purchase must say.
taxIncludedbooleanRequired with `items`: whether the item prices already include tax. When `true`, tax is not stated again.

Responses

StatusMeaning
200What would have happened. Nothing was changed.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedinsufficient_scope
404not_found
413payload_too_large
415unsupported_media_type
429rate_limited
500internal_error

Response body

FieldTypeDescription
agentrequiredobject | null
idUUID
namestring
statusENABLED | DISABLED | ARCHIVED
budgetsrequiredSimulatedBudget[]
aggregateSUM_AMOUNT | COUNTSUM_AMOUNT: the figures are minor units. COUNT: a number of purchases.
currencyCurrency | null
currentobject
committedMinorMinorAmount
heldMinorMinorAmount
reservedMinorMinorAmount
limitsobject[]
effectBLOCK | REQUIRE_APPROVAL | FLAG
limitMinorMinorAmount
policyIdUUID
policyNamestring
policyRuleIdUUID | null
policyVersionIdUUID
remainingAfterMinorMinorAmountNegative reads as "over by".
remainingBeforeMinorMinorAmount
ruleKeystring | null
wouldExceedboolean
periodEndTimestamp
periodKeystring
periodKindstringDAY, WEEK, MONTH, QUARTER or YEAR for a calendar window; ROLLING otherwise.
periodStartTimestamp
rollingSecondsinteger | null
scopestringWhose purchases the window holds: ORGANIZATION, POLICY:<policy id>, AGENT:<agent id> or PRINCIPAL:<user:id | organization>.
simulatedobject
projectedMinorMinorAmount
requestedMinorMinorAmount
timeZonestring | null
wouldExceedboolean
findingsrequiredFinding[]
currencyCurrency | null
headroomMinorMinorAmount | null
limitMinorMinorAmount | null
measuredMeasuredWindow | null
aggregaterequiredSUM_AMOUNT | COUNTSUM_AMOUNT: a sum of money. COUNT: a number of purchases.
scoperequiredstringWhose purchases the window holds: POLICY (the agents the policy binds), ORGANIZATION, EACH_AGENT, AGENT:<agent id>, EACH_PRINCIPAL or PRINCIPAL:<user id>.
windowrequiredobject
messagerequiredstring
observedMinorMinorAmount | null
outcomerequiredPASS | NOT_APPLICABLE | FLAG | REQUIRE_APPROVAL | BLOCK | AUTHORIZEDPASS: 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.
policyIdrequiredUUID
policyNamerequiredstring
policyRuleIdUUID | null
policyVersionIdrequiredUUID
policyVersionNumberrequiredinteger
reasonCoderequiredReasonCode
rolerequiredBINDING | SATISFIED | NOT_APPLICABLEHow the finding stands against the decision.
rulerequiredstringThe rule's name, or its key when it has none.
ruleKeystring | null
unitMONEY | COUNT | null
policiesConsideredrequiredobject[]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.
applicabilityReasonstring
participationPARTICIPATING | NOT_APPLICABLE
policyIdUUID
policyNamestring
policyStatusACTIVE | DISABLED | ARCHIVED
policyVersionIdUUID
versionNumberinteger
versionStatusDRAFT | ACTIVE | ARCHIVED
purchaserequiredobject
amountMinorMinorAmount
categorystring | null
currencyCurrency
currencyExponentinteger
maximumMinorMinorAmount | nullThe 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.
merchantstring
merchantDomainstring | null
merchantKeystring
simulatedDecisionrequiredobject
advancerequiredCOMPLETE | PARTIAL | NOT_DECIDEDHow 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.
authorityWITHIN_POLICY | AUTHORIZED | null
deferredRulesrequiredobject[]The rules checked when a purchase is requested, not in advance. Empty when advance is COMPLETE.
policyIdrequiredstring
policyNamerequiredstring
rulerequiredstring
ruleKeyrequiredstring
explanationrequiredstring
outcomerequiredAPPROVED | REJECTED | REQUIRES_APPROVAL | NOT_DECIDED_IN_ADVANCEWhat 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.
reasonCodesrequiredReasonCode[]
wouldRequireApprovalrequiredbooleanFalse when the outcome is NOT_DECIDED_IN_ADVANCE: it is not known.
simulationrequiredobject
disclaimerrequiredstring
engineVersionrequiredstring
isSimulationrequiredtrueAlways true. The field exists so its absence is a signal.
moderequiredstring
simulatedAtrequiredTimestamp

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

FieldTypeDescription
amountMinorrequiredstring | integerA 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.
categoryelectronics | 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 | otherRequired 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.
chargesobject[]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.
amountMinorstring
classrequiredKNOWN | VARIABLE | UNEXPLAINED
descriptionstringAt most 200 bytes.
kindTAX | DELIVERY | DUTY | FEE | DISCOUNT
maximumMinorstringA variable charge's most.
currencystring | nullDefaults to the organisation's own currency when omitted.
itemsobject[]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.
categoryelectronics | 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 | otherThe item's category. Omit it only for an item whose category is uncertain.
categoryUncertainboolean`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.
descriptionrequiredstringWhat the item is. At most 500 bytes.
discountMinorstringA discount on this line, in minor units. Defaults to 0.
itemIdstringYour own label for the line, unique in the basket. At most 64 bytes.
optionsobject[]
namerequiredstring
valuerequiredstring
possibleCategorieselectronics | 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.
productReferenceobject
idrequiredstring
providerrequiredstring
productUrlstringThe product's https page, on `merchantDomain` or the same website with or without its `www` label.
quantityrequiredinteger
riskTagsmedicines | 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.
uncertaintyReasonstringWhy the category can't be established. At most 200 bytes.
unitAmountMinorrequiredstringThe price of one, in minor units.
merchantrequiredstringWho 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.
merchantDomainstring | nullThe 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.
merchantReferenceCommerceReference | nullA 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.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
policyVersionIdrequiredUUIDThe draft version to evaluate. Name the version, not the policy.
productReferenceCommerceReference | nullAn 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.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
reasonrequiredstringWhy the agent wants this purchase. Every purchase must say.
taxIncludedbooleanRequired with `items`: whether the item prices already include tax. When `true`, tax is not stated again.

Responses

StatusMeaning
200What that draft would have decided.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
413payload_too_large
415unsupported_media_type
429rate_limited
500internal_error

Response body

FieldTypeDescription
agentrequiredobject | null
idUUID
namestring
statusENABLED | DISABLED | ARCHIVED
budgetsrequiredSimulatedBudget[]
aggregateSUM_AMOUNT | COUNTSUM_AMOUNT: the figures are minor units. COUNT: a number of purchases.
currencyCurrency | null
currentobject
committedMinorMinorAmount
heldMinorMinorAmount
reservedMinorMinorAmount
limitsobject[]
effectBLOCK | REQUIRE_APPROVAL | FLAG
limitMinorMinorAmount
policyIdUUID
policyNamestring
policyRuleIdUUID | null
policyVersionIdUUID
remainingAfterMinorMinorAmountNegative reads as "over by".
remainingBeforeMinorMinorAmount
ruleKeystring | null
wouldExceedboolean
periodEndTimestamp
periodKeystring
periodKindstringDAY, WEEK, MONTH, QUARTER or YEAR for a calendar window; ROLLING otherwise.
periodStartTimestamp
rollingSecondsinteger | null
scopestringWhose purchases the window holds: ORGANIZATION, POLICY:<policy id>, AGENT:<agent id> or PRINCIPAL:<user:id | organization>.
simulatedobject
projectedMinorMinorAmount
requestedMinorMinorAmount
timeZonestring | null
wouldExceedboolean
findingsrequiredFinding[]
currencyCurrency | null
headroomMinorMinorAmount | null
limitMinorMinorAmount | null
measuredMeasuredWindow | null
aggregaterequiredSUM_AMOUNT | COUNTSUM_AMOUNT: a sum of money. COUNT: a number of purchases.
scoperequiredstringWhose purchases the window holds: POLICY (the agents the policy binds), ORGANIZATION, EACH_AGENT, AGENT:<agent id>, EACH_PRINCIPAL or PRINCIPAL:<user id>.
windowrequiredobject
messagerequiredstring
observedMinorMinorAmount | null
outcomerequiredPASS | NOT_APPLICABLE | FLAG | REQUIRE_APPROVAL | BLOCK | AUTHORIZEDPASS: 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.
policyIdrequiredUUID
policyNamerequiredstring
policyRuleIdUUID | null
policyVersionIdrequiredUUID
policyVersionNumberrequiredinteger
reasonCoderequiredReasonCode
rolerequiredBINDING | SATISFIED | NOT_APPLICABLEHow the finding stands against the decision.
rulerequiredstringThe rule's name, or its key when it has none.
ruleKeystring | null
unitMONEY | COUNT | null
policiesConsideredrequiredobject[]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.
applicabilityReasonstring
participationPARTICIPATING | NOT_APPLICABLE
policyIdUUID
policyNamestring
policyStatusACTIVE | DISABLED | ARCHIVED
policyVersionIdUUID
versionNumberinteger
versionStatusDRAFT | ACTIVE | ARCHIVED
purchaserequiredobject
amountMinorMinorAmount
categorystring | null
currencyCurrency
currencyExponentinteger
maximumMinorMinorAmount | nullThe 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.
merchantstring
merchantDomainstring | null
merchantKeystring
simulatedDecisionrequiredobject
advancerequiredCOMPLETE | PARTIAL | NOT_DECIDEDHow 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.
authorityWITHIN_POLICY | AUTHORIZED | null
deferredRulesrequiredobject[]The rules checked when a purchase is requested, not in advance. Empty when advance is COMPLETE.
policyIdrequiredstring
policyNamerequiredstring
rulerequiredstring
ruleKeyrequiredstring
explanationrequiredstring
outcomerequiredAPPROVED | REJECTED | REQUIRES_APPROVAL | NOT_DECIDED_IN_ADVANCEWhat 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.
reasonCodesrequiredReasonCode[]
wouldRequireApprovalrequiredbooleanFalse when the outcome is NOT_DECIDED_IN_ADVANCE: it is not known.
simulationrequiredobject
disclaimerrequiredstring
engineVersionrequiredstring
isSimulationrequiredtrueAlways true. The field exists so its absence is a signal.
moderequiredstring
simulatedAtrequiredTimestamp

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

FieldTypeDescription
amountMinorrequiredstring | integerA 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.
categoryelectronics | 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 | otherRequired 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.
chargesobject[]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.
amountMinorstring
classrequiredKNOWN | VARIABLE | UNEXPLAINED
descriptionstringAt most 200 bytes.
kindTAX | DELIVERY | DUTY | FEE | DISCOUNT
maximumMinorstringA variable charge's most.
currencystring | nullDefaults to the organisation's own currency when omitted.
itemsobject[]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.
categoryelectronics | 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 | otherThe item's category. Omit it only for an item whose category is uncertain.
categoryUncertainboolean`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.
descriptionrequiredstringWhat the item is. At most 500 bytes.
discountMinorstringA discount on this line, in minor units. Defaults to 0.
itemIdstringYour own label for the line, unique in the basket. At most 64 bytes.
optionsobject[]
namerequiredstring
valuerequiredstring
possibleCategorieselectronics | 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.
productReferenceobject
idrequiredstring
providerrequiredstring
productUrlstringThe product's https page, on `merchantDomain` or the same website with or without its `www` label.
quantityrequiredinteger
riskTagsmedicines | 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.
uncertaintyReasonstringWhy the category can't be established. At most 200 bytes.
unitAmountMinorrequiredstringThe price of one, in minor units.
merchantrequiredstringWho 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.
merchantDomainstring | nullThe 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.
merchantReferenceCommerceReference | nullA 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.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
policyVersionIdrequiredUUIDThe active or archived version to evaluate.
productReferenceCommerceReference | nullAn 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.
idrequiredstringThe id, exactly as the provider writes it. No spaces.
providerrequiredstringThe platform or scheme: a short lowercase name.
reasonrequiredstringWhy the agent wants this purchase. Every purchase must say.
taxIncludedbooleanRequired with `items`: whether the item prices already include tax. When `true`, tax is not stated again.

Responses

StatusMeaning
200What that version would have decided.
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
413payload_too_large
415unsupported_media_type
429rate_limited
500internal_error

Response body

FieldTypeDescription
agentrequiredobject | null
idUUID
namestring
statusENABLED | DISABLED | ARCHIVED
budgetsrequiredSimulatedBudget[]
aggregateSUM_AMOUNT | COUNTSUM_AMOUNT: the figures are minor units. COUNT: a number of purchases.
currencyCurrency | null
currentobject
committedMinorMinorAmount
heldMinorMinorAmount
reservedMinorMinorAmount
limitsobject[]
effectBLOCK | REQUIRE_APPROVAL | FLAG
limitMinorMinorAmount
policyIdUUID
policyNamestring
policyRuleIdUUID | null
policyVersionIdUUID
remainingAfterMinorMinorAmountNegative reads as "over by".
remainingBeforeMinorMinorAmount
ruleKeystring | null
wouldExceedboolean
periodEndTimestamp
periodKeystring
periodKindstringDAY, WEEK, MONTH, QUARTER or YEAR for a calendar window; ROLLING otherwise.
periodStartTimestamp
rollingSecondsinteger | null
scopestringWhose purchases the window holds: ORGANIZATION, POLICY:<policy id>, AGENT:<agent id> or PRINCIPAL:<user:id | organization>.
simulatedobject
projectedMinorMinorAmount
requestedMinorMinorAmount
timeZonestring | null
wouldExceedboolean
findingsrequiredFinding[]
currencyCurrency | null
headroomMinorMinorAmount | null
limitMinorMinorAmount | null
measuredMeasuredWindow | null
aggregaterequiredSUM_AMOUNT | COUNTSUM_AMOUNT: a sum of money. COUNT: a number of purchases.
scoperequiredstringWhose purchases the window holds: POLICY (the agents the policy binds), ORGANIZATION, EACH_AGENT, AGENT:<agent id>, EACH_PRINCIPAL or PRINCIPAL:<user id>.
windowrequiredobject
messagerequiredstring
observedMinorMinorAmount | null
outcomerequiredPASS | NOT_APPLICABLE | FLAG | REQUIRE_APPROVAL | BLOCK | AUTHORIZEDPASS: 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.
policyIdrequiredUUID
policyNamerequiredstring
policyRuleIdUUID | null
policyVersionIdrequiredUUID
policyVersionNumberrequiredinteger
reasonCoderequiredReasonCode
rolerequiredBINDING | SATISFIED | NOT_APPLICABLEHow the finding stands against the decision.
rulerequiredstringThe rule's name, or its key when it has none.
ruleKeystring | null
unitMONEY | COUNT | null
policiesConsideredrequiredobject[]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.
applicabilityReasonstring
participationPARTICIPATING | NOT_APPLICABLE
policyIdUUID
policyNamestring
policyStatusACTIVE | DISABLED | ARCHIVED
policyVersionIdUUID
versionNumberinteger
versionStatusDRAFT | ACTIVE | ARCHIVED
purchaserequiredobject
amountMinorMinorAmount
categorystring | null
currencyCurrency
currencyExponentinteger
maximumMinorMinorAmount | nullThe 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.
merchantstring
merchantDomainstring | null
merchantKeystring
simulatedDecisionrequiredobject
advancerequiredCOMPLETE | PARTIAL | NOT_DECIDEDHow 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.
authorityWITHIN_POLICY | AUTHORIZED | null
deferredRulesrequiredobject[]The rules checked when a purchase is requested, not in advance. Empty when advance is COMPLETE.
policyIdrequiredstring
policyNamerequiredstring
rulerequiredstring
ruleKeyrequiredstring
explanationrequiredstring
outcomerequiredAPPROVED | REJECTED | REQUIRES_APPROVAL | NOT_DECIDED_IN_ADVANCEWhat 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.
reasonCodesrequiredReasonCode[]
wouldRequireApprovalrequiredbooleanFalse when the outcome is NOT_DECIDED_IN_ADVANCE: it is not known.
simulationrequiredobject
disclaimerrequiredstring
engineVersionrequiredstring
isSimulationrequiredtrueAlways true. The field exists so its absence is a signal.
moderequiredstring
simulatedAtrequiredTimestamp

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

NameInDescription
statusqueryReturn only policies in this state.
searchqueryFree-text match on the policy name. Trimmed, capped at 200 characters, and treated as absent when empty.
pagequery1-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`.
pageSizequeryRows 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

StatusMeaning
200A page of policies, and where it sits in the collection.
400invalid_request
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
429rate_limited
500internal_error

Response body

FieldTypeDescription
paginationrequiredPagination
pagerequiredinteger
pageSizerequiredinteger
totalrequiredinteger
totalPagesrequiredintegerAt least 1 even when nothing matched, so a client can render "1 of 1" for an empty result and never divide by zero.
policiesrequiredPolicySummary[]
activeVersionrequiredPolicyVersionSummary | null
activatedAtTimestamp | null
archivedAtTimestamp | null
createdAtrequiredTimestamp
idrequiredUUID
languageVersionrequiredintegerThe version of the policy language this content is written in.
ruleCountrequiredintegerHow many rules this version carries, without fetching them.
statusrequiredDRAFT | ACTIVE | ARCHIVED
versionNumberrequiredintegerAllocated by Veriticity. Never chosen by a caller.
archivedAtTimestamp | null
boundToOrganizationrequiredbooleanWhether the policy is bound to the whole organization, and so governs every agent in it.
createdAtrequiredTimestamp
descriptionstring | null
idrequiredUUID
inForcerequiredbooleanWhether 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.
namerequiredstring
statusrequiredACTIVE | DISABLED | ARCHIVED
updatedAtrequiredTimestamp

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

NameInDescription
policyIdrequiredpathThe policy to read.

Responses

StatusMeaning
200The policy.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
policyrequiredPolicySummary
activeVersionrequiredPolicyVersionSummary | null
activatedAtTimestamp | null
archivedAtTimestamp | null
createdAtrequiredTimestamp
idrequiredUUID
languageVersionrequiredintegerThe version of the policy language this content is written in.
ruleCountrequiredintegerHow many rules this version carries, without fetching them.
statusrequiredDRAFT | ACTIVE | ARCHIVED
versionNumberrequiredintegerAllocated by Veriticity. Never chosen by a caller.
archivedAtTimestamp | null
boundToOrganizationrequiredbooleanWhether the policy is bound to the whole organization, and so governs every agent in it.
createdAtrequiredTimestamp
descriptionstring | null
idrequiredUUID
inForcerequiredbooleanWhether 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.
namerequiredstring
statusrequiredACTIVE | DISABLED | ARCHIVED
updatedAtrequiredTimestamp
bindingsPolicyBindings
agentIdsUUID[]
organizationbooleanBound to the whole organization: it governs every agent.
versionsPolicyVersionSummary[]
activatedAtTimestamp | null
archivedAtTimestamp | null
createdAtrequiredTimestamp
idrequiredUUID
languageVersionrequiredintegerThe version of the policy language this content is written in.
ruleCountrequiredintegerHow many rules this version carries, without fetching them.
statusrequiredDRAFT | ACTIVE | ARCHIVED
versionNumberrequiredintegerAllocated 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

NameInDescription
policyIdrequiredpathThe policy the version belongs to.
versionIdrequiredpathThe version to read.

Responses

StatusMeaning
200The version.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
versionrequiredPolicyVersion
activatedAtTimestamp | null
archivedAtTimestamp | null
createdAtrequiredTimestamp
idrequiredUUID
languageVersionrequiredintegerThe version of the policy language this content is written in.
ruleCountrequiredintegerHow many rules this version carries, without fetching them.
statusrequiredDRAFT | ACTIVE | ARCHIVED
versionNumberrequiredintegerAllocated by Veriticity. Never chosen by a caller.
appliesWhenPolicyExpression | null
appliesWhenConditionsrequiredPolicyCondition[]
acceptobject | null
atomrequiredstringThe key the expression's `atom` leaves refer to it by.
conditionrequiredstringThe 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`.
configobjectThe 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.
configVersioninteger
rulesrequiredPolicyRule[]
conditionsrequiredPolicyCondition[]
effectrequiredBLOCK | REQUIRE_APPROVAL | FLAGWhat happens when `require` is violated. BLOCK refuses. REQUIRE_APPROVAL holds the budget and waits for a person. FLAG records the finding and changes nothing.
idUUID
keyrequiredstringThe rule's identity across versions of its policy. Keep it when editing a rule: an approval is bound to it.
namestring | null
onUnestablishedBLOCK | REQUIRE_APPROVAL | FLAGWhat 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.
requirerequiredPolicyExpressionWhat must be established for the purchase to proceed.
whenPolicyExpression | null
valueSetsrequiredPolicyValueSet[]
keyrequiredstring
kindrequiredstring
membersrequiredobject[]
policyIdrequiredUUID

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

NameInDescription
policyIdrequiredpathThe policy the version belongs to.
versionIdrequiredpathThe version to check.

Responses

StatusMeaning
200The verdict. `valid` is false when the version could not be activated; this is still a 200.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
budgetImpactrequiredobject[]
currencyCurrency
limitsobject[]
effectstring
limitMinorMinorAmount
remainingMinorMinorAmount
ruleKeystringThe key of the rule that sets this limit.
periodKeystring
periodKindstring
spentMinorMinorAmount
issuesrequiredPolicyIssue[]
coderequiredstring
messagerequiredstring
pathstring
validrequiredboolean
versionrequiredPolicyVersion
activatedAtTimestamp | null
archivedAtTimestamp | null
createdAtrequiredTimestamp
idrequiredUUID
languageVersionrequiredintegerThe version of the policy language this content is written in.
ruleCountrequiredintegerHow many rules this version carries, without fetching them.
statusrequiredDRAFT | ACTIVE | ARCHIVED
versionNumberrequiredintegerAllocated by Veriticity. Never chosen by a caller.
appliesWhenPolicyExpression | null
appliesWhenConditionsrequiredPolicyCondition[]
acceptobject | null
atomrequiredstringThe key the expression's `atom` leaves refer to it by.
conditionrequiredstringThe 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`.
configobjectThe 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.
configVersioninteger
rulesrequiredPolicyRule[]
conditionsrequiredPolicyCondition[]
effectrequiredBLOCK | REQUIRE_APPROVAL | FLAGWhat happens when `require` is violated. BLOCK refuses. REQUIRE_APPROVAL holds the budget and waits for a person. FLAG records the finding and changes nothing.
idUUID
keyrequiredstringThe rule's identity across versions of its policy. Keep it when editing a rule: an approval is bound to it.
namestring | null
onUnestablishedBLOCK | REQUIRE_APPROVAL | FLAGWhat 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.
requirerequiredPolicyExpressionWhat must be established for the purchase to proceed.
whenPolicyExpression | null
valueSetsrequiredPolicyValueSet[]
keyrequiredstring
kindrequiredstring
membersrequiredobject[]
policyIdrequiredUUID
warningsrequiredPolicyIssue[]
coderequiredstring
messagerequiredstring
pathstring

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

StatusMeaning
200The agent, and its organisation's defaults.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
agentrequiredobject
descriptionstring | null
idrequiredUUID
namerequiredstring
statusrequiredENABLED | DISABLED | ARCHIVED
environmentrequiredlive | sandboxWhich Veriticity environment answered. Sandbox agents, policies, budgets, purchases and approvals are separate from Live, and nothing decided in Sandbox authorises a real purchase.
organizationrequiredobject
currencyrequiredCurrency
timeZonerequiredstringIANA 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

StatusMeaning
200Every agent in the organisation.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
429rate_limited
500internal_error

Response body

FieldTypeDescription
agentsrequiredobject[]
createdAtrequiredTimestamp
descriptionstring | null
idrequiredUUID
namerequiredstring
statusrequiredENABLED | DISABLED | ARCHIVED
updatedAtrequiredTimestamp

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

NameInDescription
currencyqueryISO 4217 code to answer in. Omit for the organisation's default currency.

Responses

StatusMeaning
200The budget windows in force.
400invalid_request
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
asOfrequiredTimestamp
currencyrequiredCurrency
windowsrequiredobject[]
committedMinorrequiredMinorAmount
currencyrequiredCurrency
heldMinorrequiredMinorAmount
limitsrequiredobject[]
effectrequiredBLOCK | REQUIRE_APPROVAL
limitMinorrequiredMinorAmount
remainingMinorrequiredstringMinor units. Negative when the window is over the limit.
periodEndrequiredTimestamp
periodKeyrequiredstringOrganisation-local: a date for a day, a week's first date, then `2026-10`, `2026-Q4` or `2026`. Empty for ROLLING.
periodKindrequiredDAY | WEEK | MONTH | QUARTER | YEAR | ROLLINGA period of the organisation's calendar, or ROLLING for a length of elapsed time ending now.
periodStartrequiredTimestamp
policyNamerequiredstring
reservedMinorrequiredMinorAmount
scopestringWhose 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

NameInDescription
currencyqueryISO 4217 code to answer in. Omit for the organisation's default currency.

Responses

StatusMeaning
200The policies that apply, in the currency asked for.
400invalid_request
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_included
403connection_unauthorizedorganization_suspendedapi_key_not_bound_to_agentinsufficient_scope
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
currencyrequiredCurrency
policiesrequiredobject[]
applicabilityReasonrequiredstring
appliesWhenobjectnull
appliesWhenConditionsPolicyCondition[]
acceptobject | null
atomrequiredstringThe key the expression's `atom` leaves refer to it by.
conditionrequiredstringThe 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`.
configobjectThe 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.
configVersioninteger
participationrequiredPARTICIPATING | NOT_APPLICABLENOT_APPLICABLE: the policy governs this agent and is not about a purchase in the currency asked for.
policyNamerequiredstring
rulesrequiredPolicyRule[]
conditionsrequiredPolicyCondition[]
effectrequiredBLOCK | REQUIRE_APPROVAL | FLAGWhat happens when `require` is violated. BLOCK refuses. REQUIRE_APPROVAL holds the budget and waits for a person. FLAG records the finding and changes nothing.
idUUID
keyrequiredstringThe rule's identity across versions of its policy. Keep it when editing a rule: an approval is bound to it.
namestring | null
onUnestablishedBLOCK | REQUIRE_APPROVAL | FLAGWhat 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.
requirerequiredPolicyExpressionWhat must be established for the purchase to proceed.
whenPolicyExpression | null
valueSetsrequiredobject[]
keyrequiredstring
kindrequiredstring
memberCountrequiredinteger

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

StatusMeaning
200The account's endpoints.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
429rate_limited
500internal_error

Response body

FieldTypeDescription
itemsrequiredWebhookEndpoint[]
apiVersionrequiredstringThe webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH.
consecutiveFailuresrequiredintegerVeriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it.
createdAtrequiredTimestamp
descriptionstring | null
disabledAtTimestamp | null
idrequiredUUID
lastFailureAtTimestamp | null
lastSuccessAtTimestamp | null
revokedAtTimestamp | null
statusrequiredACTIVE | DISABLED | REVOKED
subscribedTypesrequiredWebhookEventType[]
urlrequiredstring

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

FieldTypeDescription
descriptionstring | nullFor your own reference. Never sent anywhere.
subscribedTypesrequiredWebhookEventType[]Choose at least one event to receive.
urlrequiredstring

Responses

StatusMeaning
201Created. **Carries the signing secret, once.**
400invalid_requestmalformed_jsonwebhook_url_not_permitted
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
402feature_not_includedplan_limit_exceeded
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permittedbilling_suspended
409resource_limit_reached
413payload_too_large
415unsupported_media_type
429rate_limited
500internal_error
503webhook_not_configured

Response body

FieldTypeDescription
apiVersionrequiredstringThe webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH.
consecutiveFailuresrequiredintegerVeriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it.
createdAtrequiredTimestamp
descriptionstring | null
disabledAtTimestamp | null
idrequiredUUID
lastFailureAtTimestamp | null
lastSuccessAtTimestamp | null
revokedAtTimestamp | null
statusrequiredACTIVE | DISABLED | REVOKED
subscribedTypesrequiredWebhookEventType[]
urlrequiredstring
secretrequiredstringThe 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

NameInDescription
webhookIdrequiredpathThe endpoint to read.

Responses

StatusMeaning
200The endpoint.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
apiVersionrequiredstringThe webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH.
consecutiveFailuresrequiredintegerVeriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it.
createdAtrequiredTimestamp
descriptionstring | null
disabledAtTimestamp | null
idrequiredUUID
lastFailureAtTimestamp | null
lastSuccessAtTimestamp | null
revokedAtTimestamp | null
statusrequiredACTIVE | DISABLED | REVOKED
subscribedTypesrequiredWebhookEventType[]
urlrequiredstring

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

NameInDescription
webhookIdrequiredpathThe endpoint to update.

Request body

FieldTypeDescription
apiVersionstring
descriptionstring | null
statusACTIVE | DISABLED
subscribedTypesWebhookEventType[]
urlstring

Responses

StatusMeaning
200The updated endpoint.
400invalid_requestmalformed_jsonwebhook_url_not_permitted
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
409webhook_endpoint_revoked
413payload_too_large
415unsupported_media_type
429rate_limited
500internal_error

Response body

FieldTypeDescription
apiVersionrequiredstringThe webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH.
consecutiveFailuresrequiredintegerVeriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it.
createdAtrequiredTimestamp
descriptionstring | null
disabledAtTimestamp | null
idrequiredUUID
lastFailureAtTimestamp | null
lastSuccessAtTimestamp | null
revokedAtTimestamp | null
statusrequiredACTIVE | DISABLED | REVOKED
subscribedTypesrequiredWebhookEventType[]
urlrequiredstring

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

NameInDescription
webhookIdrequiredpathThe endpoint to revoke.

Responses

StatusMeaning
200The endpoint, now revoked.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
409webhook_endpoint_revoked
429rate_limited
500internal_error

Response body

FieldTypeDescription
apiVersionrequiredstringThe webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH.
consecutiveFailuresrequiredintegerVeriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it.
createdAtrequiredTimestamp
descriptionstring | null
disabledAtTimestamp | null
idrequiredUUID
lastFailureAtTimestamp | null
lastSuccessAtTimestamp | null
revokedAtTimestamp | null
statusrequiredACTIVE | DISABLED | REVOKED
subscribedTypesrequiredWebhookEventType[]
urlrequiredstring

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

NameInDescription
webhookIdrequiredpathThe endpoint whose deliveries these are.
limitqueryHow many attempts to return. Clamped to 100 rather than refused.

Responses

StatusMeaning
200A page of delivery attempts.
400invalid_request
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
429rate_limited
500internal_error

Response body

FieldTypeDescription
hasMorerequiredboolean
itemsrequiredWebhookDelivery[]
attemptCountrequiredinteger
createdAtrequiredTimestamp
deliveredAtTimestamp | null
eventIdrequiredUUIDThe logical event, and your deduplication key. Identical to the Veriticity-Webhook-Id header, and identical at every endpoint subscribed to this event.
eventTyperequiredDeliverableWebhookEventType
idrequiredUUIDThis delivery: one endpoint's attempt stream for one event.
lastAttemptAtTimestamp | null
lastDurationMsinteger | null
lastErrorClassstring | null
lastResponseSnippetstring | null
lastStatusCodeinteger | null
nextAttemptAtTimestamp | null
occurredAtrequiredTimestampWhen it happened in the domain, not when it was sent.
statusrequiredstring

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

NameInDescription
webhookIdrequiredpathThe endpoint to rotate.

Request body (optional)

FieldTypeDescription
retireImmediatelybooleanRetire the old secret now instead of after 24 hours. Deliveries signed with it stop verifying immediately.

Responses

StatusMeaning
200Rotated. **Carries the new signing secret, once.**
400invalid_requestmalformed_json
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
409webhook_endpoint_revoked
429rate_limited
500internal_error
503webhook_not_configured

Response body

FieldTypeDescription
apiVersionrequiredstringThe webhook contract version this endpoint receives. Pinned when the endpoint is created and changed only by PATCH.
consecutiveFailuresrequiredintegerVeriticity does not disable a failing endpoint, so this is the number that tells you a receiver has been refusing events. Any success resets it.
createdAtrequiredTimestamp
descriptionstring | null
disabledAtTimestamp | null
idrequiredUUID
lastFailureAtTimestamp | null
lastSuccessAtTimestamp | null
revokedAtTimestamp | null
statusrequiredACTIVE | DISABLED | REVOKED
subscribedTypesrequiredWebhookEventType[]
urlrequiredstring
secretrequiredstringThe 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

NameInDescription
webhookIdrequiredpathThe endpoint to send a test to.

Responses

StatusMeaning
202Queued. Look in the delivery history for what happened.
401unauthorizedinvalid_api_keyapi_key_revokedapi_key_expiredwrong_environmentinvalid_token
403connection_unauthorizedorganization_suspendedapi_key_not_organization_scopedoauth_not_permitted
404not_found
409webhook_endpoint_revoked
429rate_limited
500internal_error
503webhook_not_configuredtemporarily_unavailable

Response body

FieldTypeDescription
deliveryIdrequiredUUID
eventIdrequiredUUID
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.

Fetch the document
curl -s https://docs.veriticity.com/openapi.json -o veriticity.json