Submit your first purchase through the Veriticity API and read the decision. A complete worked example in cURL and TypeScript, from API key to explained outcome.
This walks through one complete purchase: check it, submit it, handle the answer, and follow it if a person has to decide. About five minutes.
{"simulation":{"isSimulation":true,"mode":"CURRENT_POLICIES","disclaimer":"This is a simulation, not a decision. Veriticity created no purchase request, no decision, no budget reservation and no audit evidence, and consumed no budget.","simulatedAt":"2026-09-12T09:14:02.471Z"},"simulatedDecision":{"outcome":"APPROVED","wouldRequireApproval":false,"reasonCodes":["WITHIN_POLICY"],"explanation":"Acme Cloud is on the allow list. GBP 75.00 is within the GBP 500.00 single-purchase maximum."},"budgets":[{"periodKind":"DAY","currency":"GBP","simulated":{"requestedMinor":"7500","projectedMinor":"12500"},"wouldExceed":false}]}
Note there is no id anywhere in a simulation response. The fields a real decision is identified by are absent rather than null, so a simulation cannot be mistaken for one.
{"purchaseRequest":{"id":"0199a3f1-8c2e-7b40-9f31-6d2e5a1c0b77","status":"APPROVED","amountMinor":"7500","currency":"GBP"},"decision":{"id":"0199a3f1-91b4-7c02-a55d-3e7f1d9c4a20","outcome":"APPROVED","reasonCodes":["WITHIN_POLICY"],"explanation":"Acme Cloud is on the allow list. GBP 75.00 is within the GBP 500.00 single-purchase maximum, and the daily budget has GBP 420.00 remaining.","evaluatedAt":"2026-09-12T09:14:07.882Z","engineVersion":"veriticity-trust-engine@0.1.0"},"approval":{"required":false,"expiresAt":null},"ruleEvaluations":[ /* every rule considered, including the ones that passed */ ],"replayed":false}
The same call in TypeScript, using the official client. Install it with npm install @veriticity/sdk; the SDK page covers the rest.
With the SDK
import { Veriticity } from"@veriticity/sdk";
const veriticity = new Veriticity({ apiKey: process.env.VERITICITY_API_KEY! });
const result = await veriticity.purchases.submit({
merchant: "Acme Cloud",
amountMinor: 42000n,
currency: "GBP",
reason: "Annual renewal of the build pipeline plan",
});
console.log(result.decision.outcome, result.decision.explanation);
Idempotency-Key makes a retry safe. Send the same key with the same body and you get the original decision back with replayed: true and a 200, with no new budget reserved. Send it with a different body and you get 409 idempotency_key_reuse.
type Outcome = "APPROVED" | "REJECTED" | "REQUIRES_APPROVAL";
const response = await fetch(
"https://app.veriticity.com/v1/purchase-requests",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.VERITICITY_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify({
merchant: "Acme Cloud",
amountMinor: "7500",
currency: "GBP",
reason: "Renew build pipeline compute credits",
}),
},
);
if (!response.ok) {
const { error } = await response.json();
thrownew Error(`${error.code}: ${error.message}`);
}
const result = await response.json();
// Branch on the decision, never on the status code. All three outcomes// arrive as 201 (or 200 for an idempotent replay).
switch (result.decision.outcome as Outcome) {
case "APPROVED":
await completeCheckout(result.purchaseRequest.id);
break;
case "REJECTED":
// result.decision.explanation says why, in a sentence you can log or// show. result.decision.reasonCodes is the machine-readable half.await recordRefusal(result.decision);
break;
case "REQUIRES_APPROVAL":
// A person has been asked, and the budget is held until this instant.await waitForHuman(
result.purchaseRequest.id,
result.approval.expiresAt,
);
break;
}
When the answer is REQUIRES_APPROVAL, a person has been asked and the budget is held until approval.expiresAt. If nobody answers by then, the hold is released and the purchase will not happen unless you submit it again.
Read the purchase back to find out what they decided:
{"purchase":{"id":"0199a3f1-8c2e-7b40-9f31-6d2e5a1c0b77","status":"APPROVED","merchant":"Acme Cloud","amountMinor":"42000","currency":"GBP","decision":{"outcome":"APPROVED","kind":"APPROVAL_REEVALUATION","explanation":"A person approved this purchase, and it was re-evaluated against the policies in force."},"approval":{"state":"APPROVED","isPending":false,"resolvedBy":{"name":"Priya Raman"},"comment":"Signed off — this is the renewal we discussed."}}}
decision.kind is APPROVAL_REEVALUATION once a person has answered, against INITIAL for the engine’s first decision. The purchase is re-evaluated against the policies in force at that moment, so an approval is not a bypass — if the rules changed in the meantime, the new rules apply.
Polling works, and webhooks are better: subscribe to purchase.approved and purchase.rejected and you will be told rather than having to ask.