Make a decision
POST /v1/understand answers one decision: a named one we maintain, or your own question.
Built and tested, not yet deployed. Early access.
On this page
curl "$UNDERSTANDING_API_URL/v1/understand" \ -H "Authorization: Bearer $UNDERSTANDING_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "decision": "restricted_merchant", "input": { "merchant": "DRAFTKINGS", "mcc": "7995" } }'
const base = process.env.UNDERSTANDING_API_URL; const key = process.env.UNDERSTANDING_API_KEY; const body = { decision: "restricted_merchant", input: { merchant: "DRAFTKINGS", mcc: "7995", }, }; const res = await fetch(`${base}/v1/understand`, { method: "POST", headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json", }, body: JSON.stringify(body), }); const result = await res.json();
import os import requests base = os.environ["UNDERSTANDING_API_URL"] key = os.environ["UNDERSTANDING_API_KEY"] body = { "decision": "restricted_merchant", "input": { "merchant": "DRAFTKINGS", "mcc": "7995", }, } res = requests.post( f"{base}/v1/understand", headers={"Authorization": f"Bearer {key}"}, json=body, timeout=10, ) result = res.json()
{ "decision": "restricted_merchant", "type": "predicate", "answer": true, "probability": 0.97, "reasons": [ "mcc_7995", "rule_gambling_category", "kind_sportsbook" ], "abstain": false }
{ "error": { "message": "input must be an object", "field": "input" } }
Headers
AuthorizationstringrequiredBearer <your key>, orBearer <access token>from a connected app. The key alone also works. Keys and authenticationx-api-keystringoptionalYour key, for a client that can’t set
Authorization. Read only when there’s noAuthorizationheader.Content-Typestringrecommendedapplication/json. The body is read as JSON either way.Idempotency-Keystringoptional1 to 255 printable characters. The same key within 24 hours returns the first successful response, with no second model call and no second charge. With a different body: 422. While the first is still running: 409.
x-request-idstringoptionalYour own id for the call: 1 to 128 letters, digits and
. _ : -. We send it back; without one, we make one.
Body: a named decision
decisionstringrequiredOne of
restricted_merchant,spend_type,receipt_match,refund_pairing,return_window,support_route,signal_baseline,member_route,document_complete.inputobjectrequiredThe decision’s input. Each decision’s page lists its fields.
Show 4 fields
restricted_merchantobjectmerchant,mcc. Restricted merchantspend_typeobjectmcc,merchant. Spend typereceipt_matchobjectreceipt,transactions. Receipt to chargerefund_pairingobjectpurchase,transactions. Refund to purchase
abstainBelownumberoptionalThe probability under which
abstainis true, from 0.5 to 0.99. Defaults to 0.8.
Body: your own question
questionobjectrequiredYour question.
Show 4 fields
typestringrequiredpredicate,choiceorscore.instructionsstringrequired1 to 4,000 characters.
optionsarray or objectchoice2 to 50 names, or a map of name to description.
levelsarrayscore2 to 11 ordered levels.
inputobject or stringrequiredAn object, or text up to 20,000 characters.
abstainBelownumberoptionalFrom 0.5 to 0.99. Defaults to 0.8.
If a body has both decision and question, decision wins.
Response 200
decisionstring or nullThe named decision, or
nullfor your own question.typestringpredicate,choiceorscore.answerboolean, string, number or nullPredicate:
trueorfalse. Choice: one option name, a transaction id for receipts and refunds, or"none"when nothing matches. Score: an integer from 0 to 100.nullwhen the model declined.probabilitynumber or nullThe probability of the answer given. For a predicate answered false it is one minus the probability of true; for a score it is the most likely level’s probability.
1when a rule settled the answer.nullwhen the model declined or gave no probability.reasonsarray of stringsCodes for what the answer rests on: rule checks that fired and the model’s sub-answers. Empty for your own question, unless the model declined: then
["model_declined"], as for a named decision.abstainbooleanTrue when the probability is under your line, when the model gave no probability for its answer (
answeris still set) or when the model declined. Send these cases to a person.
Response headers
cache-controlstringalwaysno-store, on every response, errors included.x-request-idstringalwaysYours, if you sent a valid one; otherwise ours, starting
req_. Quote it when you ask us about a call.x-understanding-input-tokensstringmetered callsThe input tokens the call counted, as a whole number. On a 200 to an API key or a connected app’s token.
x-understanding-cost-usdstringmetered callsWhat the call cost, in US dollars, as a number with six decimal places and no dollar sign. On a 200 to an API key or a connected app’s token. A call that fails costs nothing.
idempotent-replayedstringreplays onlytruewhen the response is the stored one for a repeatedIdempotency-Key.retry-afterstring409 and 429Seconds to wait before the next call:
1on a 409,10on a 429.www-authenticatestring401 onlyBearer realm="understanding-api".
Errors
| Status | When |
|---|---|
400 | The body isn’t JSON, a field is wrong, the input holds a card, Social Security or account number, or the Idempotency-Key is malformed. error.field names the part at fault when one can be named. |
401 | No credential, or a key or access token that isn’t valid, has expired or was revoked. |
402 | The account is out of credit. |
409 | A request with this Idempotency-Key is still running. |
413 | The body is over 256 KB. |
422 | This Idempotency-Key was used with a different body. |
429 | Over 60 requests in a minute on one key. |
500 | Something went wrong on our side. Try again. |
502 | The decision model didn’t answer. Try again. |
503 | We couldn’t check the key or the account just then. Try again. |
Checked against the code
The fields on this page are checked against the contract in our code each time the site is built (last on 2026-11-02).
# Make a decision
`POST /v1/understand` answers one decision: a named one we maintain, or your own question.
Built and tested, not yet deployed. Early access.
The request in cURL:
```bash
curl "$UNDERSTANDING_API_URL/v1/understand" \
-H "Authorization: Bearer $UNDERSTANDING_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"decision": "restricted_merchant",
"input": {
"merchant": "DRAFTKINGS",
"mcc": "7995"
}
}'
```
The request in TypeScript:
```typescript
const base = process.env.UNDERSTANDING_API_URL;
const key = process.env.UNDERSTANDING_API_KEY;
const body = {
decision: "restricted_merchant",
input: {
merchant: "DRAFTKINGS",
mcc: "7995",
},
};
const res = await fetch(`${base}/v1/understand`, {
method: "POST",
headers: {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
const result = await res.json();
```
The request in Python:
```python
import os
import requests
base = os.environ["UNDERSTANDING_API_URL"]
key = os.environ["UNDERSTANDING_API_KEY"]
body = {
"decision": "restricted_merchant",
"input": {
"merchant": "DRAFTKINGS",
"mcc": "7995",
},
}
res = requests.post(
f"{base}/v1/understand",
headers={"Authorization": f"Bearer {key}"},
json=body,
timeout=10,
)
result = res.json()
```
The 200 response:
```json
{
"decision": "restricted_merchant",
"type": "predicate",
"answer": true,
"probability": 0.97,
"reasons": [
"mcc_7995",
"rule_gambling_category",
"kind_sportsbook"
],
"abstain": false
}
```
The 400 response to {"decision":"spend_type","input":[]}:
```json
{
"error": {
"message": "input must be an object",
"field": "input"
}
}
```
## Headers
- `Authorization` (string, required): `Bearer <your key>`, or `Bearer <access token>` from a connected app. The key alone also works. [Keys and authentication](/api/docs/authentication)
- `x-api-key` (string, optional): Your key, for a client that can’t set `Authorization`. Read only when there’s no `Authorization` header.
- `Content-Type` (string, recommended): `application/json`. The body is read as JSON either way.
- `Idempotency-Key` (string, optional): 1 to 255 printable characters. The same key within 24 hours returns the first successful response, with no second model call and no second charge. With a different body: 422. While the first is still running: 409.
- `x-request-id` (string, optional): Your own id for the call: 1 to 128 letters, digits and `. _ : -`. We send it back; without one, we make one.
## Body: a named decision
- `decision` (string, required): One of `restricted_merchant`, `spend_type`, `receipt_match`, `refund_pairing`, `return_window`, `support_route`, `signal_baseline`, `member_route`, `document_complete`.
- `input` (object, required): The decision’s input. Each decision’s page lists its fields.
- `restricted_merchant` (object): `merchant`, `mcc`. [Restricted merchant](/api/docs/decisions/restricted-merchant)
- `spend_type` (object): `mcc`, `merchant`. [Spend type](/api/docs/decisions/spend-type)
- `receipt_match` (object): `receipt`, `transactions`. [Receipt to charge](/api/docs/decisions/receipt-match)
- `refund_pairing` (object): `purchase`, `transactions`. [Refund to purchase](/api/docs/decisions/refund-pairing)
- `abstainBelow` (number, optional): The probability under which `abstain` is true, from 0.5 to 0.99. Defaults to 0.8.
## Body: your own question
- `question` (object, required): Your question.
- `type` (string, required): `predicate`, `choice` or `score`.
- `instructions` (string, required): 1 to 4,000 characters.
- `options` (array or object, choice): 2 to 50 names, or a map of name to description.
- `levels` (array, score): 2 to 11 ordered levels.
- `input` (object or string, required): An object, or text up to 20,000 characters.
- `abstainBelow` (number, optional): From 0.5 to 0.99. Defaults to 0.8.
If a body has both `decision` and `question`, `decision` wins.
## Response 200
- `decision` (string or null): The named decision, or `null` for your own question.
- `type` (string): `predicate`, `choice` or `score`.
- `answer` (boolean, string, number or null): Predicate: `true` or `false`. Choice: one option name, a transaction id for receipts and refunds, or `"none"` when nothing matches. Score: an integer from 0 to 100. `null` when the model declined.
- `probability` (number or null): The probability of the answer given. For a predicate answered false it is one minus the probability of true; for a score it is the most likely level’s probability. `1` when a rule settled the answer. `null` when the model declined or gave no probability.
- `reasons` (array of strings): Codes for what the answer rests on: rule checks that fired and the model’s sub-answers. Empty for your own question, unless the model declined: then `["model_declined"]`, as for a named decision.
- `abstain` (boolean): True when the probability is under your line, when the model gave no probability for its answer (`answer` is still set) or when the model declined. Send these cases to a person.
## Response headers
- `cache-control` (string, always): `no-store`, on every response, errors included.
- `x-request-id` (string, always): Yours, if you sent a valid one; otherwise ours, starting `req_`. Quote it when you ask us about a call.
- `x-understanding-input-tokens` (string, metered calls): The input tokens the call counted, as a whole number. On a 200 to an API key or a connected app’s token.
- `x-understanding-cost-usd` (string, metered calls): What the call cost, in US dollars, as a number with six decimal places and no dollar sign. On a 200 to an API key or a connected app’s token. A call that fails costs nothing.
- `idempotent-replayed` (string, replays only): `true` when the response is the stored one for a repeated `Idempotency-Key`.
- `retry-after` (string, 409 and 429): Seconds to wait before the next call: `1` on a 409, `10` on a 429.
- `www-authenticate` (string, 401 only): `Bearer realm="understanding-api"`.
## Errors
| Status | When |
| --- | --- |
| `400` | The body isn’t JSON, a field is wrong, the input holds a card, Social Security or account number, or the `Idempotency-Key` is malformed. `error.field` names the part at fault when one can be named. |
| `401` | No credential, or a key or access token that isn’t valid, has expired or was revoked. |
| `402` | The account is out of credit. |
| `409` | A request with this `Idempotency-Key` is still running. |
| `413` | The body is over 256 KB. |
| `422` | This `Idempotency-Key` was used with a different body. |
| `429` | Over 60 requests in a minute on one key. |
| `500` | Something went wrong on our side. Try again. |
| `502` | The decision model didn’t answer. Try again. |
| `503` | We couldn’t check the key or the account just then. Try again. |
[Every error message](/api/docs/errors)
## Checked against the code
The fields on this page are checked against the contract in our code each time the site is built (last on 2026-11-02).
Source: /api/docs/reference