Skip to content
DecisionNodeDecisionNdedocs
  • Guides
  • API reference
  • Examples
  • Playground

start here

  • QuickstartGet startedGet an API key, send one request with three questions, and branch your code on the typed answers. Plain HTTPS, nothing to install.
  • POST /v1/decideAPI referenceAnswer typed questions about a state, optional images and video frames. One request, one buffered JSON response, one answer per question.
  • QuestionsConceptsQuestions say what to decide. Each one has a type that fixes the shape of its answer: a choice from your options, a score on your scale, a…
  • ConfidenceConceptsProbabilities are calibrated per question type, so a threshold means what it says on the data we measure.
  • ImagesConceptsSend up to 16 images and text in the same request. The model reads printed and handwritten text, amounts, dates, objects and layout, and…
  • Batch jobsPatternsSend up to 10,000 requests in one file and collect the answers later, at a lower price than live calls.
  • Pricing and billingEvery workspace runs on a plan: Starter, Pro, Business or Dedicated.
↑↓ moveopen7 suggestions
Get API keyGet API key
DecisionNodeDecisionNde

Get started

  • Introduction
  • Quickstart
  • CLI
  • Playground
  • Console and keys
  • With coding agents
  • MCP server
  • Examples

Concepts

  • State
  • Questions
  • Choice
  • Score
  • Truth
  • Number
  • Points and boxes
  • Images
  • Video
  • Boosters
  • Collections
  • Confidence
  • Determinism
  • API keys

Knowledge

  • Overview
  • Documents
  • In a decision
  • Pricing and your data
  • When a rule does not appear

Models

  • DecisionNode
  • DecisionNode Flash
  • DecisionNode Edge
  • Limits
  • Versions
  • Dedicated capacity

Fine-tuning

  • Overview
  • Prepare your dataset
  • Upload and validation
  • Start a training run
  • Watch a run
  • The quality gate
  • Use your model
  • Limits and pricing

Patterns

  • Confidence-gated routing
  • Form router
  • Fan-out
  • Guardrails
  • Control loops
  • Batch jobs
  • Keys on devices
  • Bill your customers per device
  • Publishable keys

Integrations

  • Home Assistant
  • MQTT bridge
  • ROS 2

API reference

  • Overview
  • POST/v1/decide
  • POST/v1/systemone
  • GET/v1/models
  • POST/v1/uploads
  • /v1/keys
  • GET/v1/keys/{key_id}/usage
  • GET/v1/usage
  • POST/v1/credit
  • Errors
  • Safety check
  • Rate limits

Sessions API

  • Sessions overview
  • POSTOpen a session
  • WSStream frames
  • DELEnd a session

Collections API

  • Overview
  • POSTCreate a collection
  • GETList collections
  • GETRetrieve a collection
  • DELDelete a collection
  • POSTEnroll a face
  • GETList items
  • PATCHUpdate an item
  • DELDelete an item
  • POSTAdd a document
  • PUTReplace a document
  • GETRetrieve an item
  • GETList passages
  • DELDelete failed documents
  • POSTSearch a knowledge base

Batch API

  • The batch object
  • POSTCreate a batch
  • POSTAdd requests
  • POSTFinalize a batch
  • GETRetrieve a batch
  • GETGet batch results
  • POSTCancel a batch
  • GETList batches

Pricing and billing

  • Pricing and billing
  • Refer & earn

Policies

  • Responsible use
  • Data and privacy
  • Benchmarks
  • Pricing
  • Playground
Get API key
  • Guides
  • API reference
  • Examples
  • Playground

Get started

  • Introduction
  • Quickstart
  • CLI
  • Playground
  • Console and keys
  • With coding agents
  • MCP server
  • Examples

Concepts

  • State
  • Questions
  • Choice
  • Score
  • Truth
  • Number
  • Points and boxes
  • Images
  • Video
  • Boosters
  • Collections
  • Confidence
  • Determinism
  • API keys

Knowledge

  • Overview
  • Documents
  • In a decision
  • Pricing and your data
  • When a rule does not appear

Models

  • DecisionNode
  • DecisionNode Flash
  • DecisionNode Edge
  • Limits
  • Versions
  • Dedicated capacity

Fine-tuning

  • Overview
  • Prepare your dataset
  • Upload and validation
  • Start a training run
  • Watch a run
  • The quality gate
  • Use your model
  • Limits and pricing

Patterns

  • Confidence-gated routing
  • Form router
  • Fan-out
  • Guardrails
  • Control loops
  • Batch jobs
  • Keys on devices
  • Bill your customers per device
  • Publishable keys

Integrations

  • Home Assistant
  • MQTT bridge
  • ROS 2

API reference

  • Overview
  • POST/v1/decide
  • POST/v1/systemone
  • GET/v1/models
  • POST/v1/uploads
  • /v1/keys
  • GET/v1/keys/{key_id}/usage
  • GET/v1/usage
  • POST/v1/credit
  • Errors
  • Safety check
  • Rate limits

Sessions API

  • Sessions overview
  • POSTOpen a session
  • WSStream frames
  • DELEnd a session

Collections API

  • Overview
  • POSTCreate a collection
  • GETList collections
  • GETRetrieve a collection
  • DELDelete a collection
  • POSTEnroll a face
  • GETList items
  • PATCHUpdate an item
  • DELDelete an item
  • POSTAdd a document
  • PUTReplace a document
  • GETRetrieve an item
  • GETList passages
  • DELDelete failed documents
  • POSTSearch a knowledge base

Batch API

  • The batch object
  • POSTCreate a batch
  • POSTAdd requests
  • POSTFinalize a batch
  • GETRetrieve a batch
  • GETGet batch results
  • POSTCancel a batch
  • GETList batches

Pricing and billing

  • Pricing and billing
  • Refer & earn

Policies

  • Responsible use
  • Data and privacy
  1. docs
  2. /
  3. API reference

Machine payments

An agent buys prepaid credit without a person: POST /v1/credit answers 402 with a payment challenge, the agent pays it with a card through a Stripe Shared Payment Token, USDC on Tempo or USDC on Base, and the amount is booked as credit at once. $5.00 to $5,000.00 a purchase; without an API key, the payment makes an agent workspace and a key.

on this page9 sections
  1. How a purchase goes
  2. The challenge
  3. The credential
  4. The answer
  5. Amounts
  6. Without a key
  7. Paying on /v1/decide
  8. Refusals
  9. Discovery
POSThttps://api.decisionnode.com/v1/credit

Credit normally comes from the console: a person signs in and pays a top-up. An agent cannot do that, so an agent that runs out of credit stops, and one with no account cannot start. Machine payments remove both stops. The route speaks two protocols: Stripe's Machine Payments Protocol (MPP), with a card through a Shared Payment Token (Stripe's Link agent wallet) or USDC on Tempo, and x402 with USDC on Base. Every payment ends as a Stripe payment, and the credit it buys is the same prepaid credit a console top-up buys: on the key's workspace, drawn down by every call, with a receipt in Billing. A standard MPP client such as Stripe's Link CLI or Tempo's CLI handles the exchange on its own; the sections below are for a client you write yourself.

# Without a key: the challenges for $20.00 of credit
curl -i -X POST https://api.decisionnode.com/v1/credit \
  -H "Content-Type: application/json" \
  -d '{"amount":"20.00"}'

# With a key: the same, credited to the key's workspace
curl -i -X POST https://api.decisionnode.com/v1/credit \
  -H "Authorization: Bearer $DECISIONNODE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":"20.00"}'

How a purchase goes#

  1. 1.Ask. POST /v1/credit with {"amount": "20.00"}, with your API key or without one. The answer is 402 with one WWW-Authenticate: Payment challenge per method on offer, stripe and tempo, and a PAYMENT-REQUIRED header for x402.
  2. 2.Pay. Hand one challenge to your payment method: Stripe issues a Shared Payment Token for the amount, a Tempo wallet broadcasts the USDC transfer to the address in the challenge, an x402 wallet signs the USDC authorization.
  3. 3.Retry with the credential. The same call again, with the challenge echoed and the method's payload in Payment-Authorization (MPP) or the signed payload in PAYMENT-SIGNATURE (x402). The body is ignored on this call: the amount is the challenge's.
  4. 4.Read the answer. 200 with the workspace, the credit now on it, the receipt and, for a keyless purchase, the key. The Payment-Receipt or PAYMENT-RESPONSE header is the protocol's own receipt.

Who may call

Anyone, over HTTPS. With a key: a Full access or Manage device keys key of a prepaid workspace, blocked for credit or not (a Decisions-only or Publishable key is 403 key_scope_denied; a dedicated workspace, invoiced monthly, is 400 api_usage_error; a suspended workspace 403 workspace_suspended, which no payment lifts). Without a key: a keyless agent, whose purchase makes a workspace (Without a key).

The challenge#

Without a credential the route answers 402 Payment Required with Cache-Control: no-store, a body of type application/problem+json (RFC 9457) whose detail names the amount and the methods, and one WWW-Authenticate line per method, in the order stripe, tempo. Each line is the Payment scheme of the Payment HTTP authentication draft with these parameters, every value a quoted string:

WWW-Authenticate: Payment

idstring
An HMAC-SHA256 over the other parameters (description excepted), base64url. The server verifies the challenge you echo against it, so a changed amount, address or expiry is refused. Opaque to you.
realmstring
api.decisionnode.com.
methodstring
stripe (a card through a Shared Payment Token) or tempo (USDC on Tempo).
intentstring
charge, always: one payment of the amount.
requeststring
The method's request object, JSON canonicalized (RFC 8785) and base64url without padding; decoded below. Hand it to your payment method as it is.
descriptionstring
"DecisionNode prepaid credit, $20.00. Paying accepts https://decisionnode.com/legal/terms": the amount, and that paying accepts the Terms of Service.
expiresstring
RFC 3339, 10 minutes after the challenge was made. Past it, the credential is 402 payment_expired: ask for a fresh challenge.
headerstring
Payment-Authorization: the request header the credential goes in. The API key keeps Authorization; a credential sent there satisfies nothing.
opaquestring
A nonce and, for a key holder, the workspace the credit will land on ({"n", "ws"}), base64url. Echo it as it is: a challenge is good only for the caller it was issued to.
The request objects, decoded
{
  "amount": "2000",
  "currency": "usd",
  "description": "DecisionNode prepaid credit",
  "methodDetails": {
    "networkId": "profile_...",
    "paymentMethodTypes": ["card", "link"]
  }
}
  • stripe

    Amount in request
    amount in cents as a string ("2000"), currency usd
    Where the money goes
    methodDetails.networkId is DecisionNode's Stripe profile; paymentMethodTypes are card and link. Stripe issues the token against that profile.
  • tempo

    Amount in request
    amount in USDC base units, six decimals ("20000000"); currency is the USDC.e token on Tempo
    Where the money goes
    recipient is DecisionNode's deposit address on Tempo mainnet (methodDetails.chainId 4217). Push mode only (supportedModes): your wallet broadcasts the transfer and pays its own network fee.
  • x402

    Amount in request
    accepts[0].amount in USDC base units; asset is USDC on Base (network eip155:8453)
    Where the money goes
    payTo is DecisionNode's deposit address on Base; scheme exact, the EIP-3009 transfer method; maxTimeoutSeconds 600.
What each method asks for
MethodAmount in requestWhere the money goes
stripeamount in cents as a string ("2000"), currency usdmethodDetails.networkId is DecisionNode's Stripe profile; paymentMethodTypes are card and link. Stripe issues the token against that profile.
tempoamount in USDC base units, six decimals ("20000000"); currency is the USDC.e token on Temporecipient is DecisionNode's deposit address on Tempo mainnet (methodDetails.chainId 4217). Push mode only (supportedModes): your wallet broadcasts the transfer and pays its own network fee.
x402accepts[0].amount in USDC base units; asset is USDC on Base (network eip155:8453)payTo is DecisionNode's deposit address on Base; scheme exact, the EIP-3009 transfer method; maxTimeoutSeconds 600.

Read the addresses from the challenge every time, never from a note of your own: they are part of what the id binds, and a payment to any other address buys nothing. The PAYMENT-REQUIRED header is standard base64 of the x402 v2 document shown above; it carries no workspace, since x402 identifies the workspace by the API key alone.

The credential#

For MPP, send Payment-Authorization: Payment <base64url>, the base64url without padding of a JSON object with challenge (the challenge's parameters echoed as they came, request and opaque still their wire strings) and payload (the method's), at most 16 KiB. For x402, send PAYMENT-SIGNATURE: <base64> of the x402 payment payload. Send one or the other: both at once is 400 api_usage_error, "Send one payment credential."

The credential, decoded
{
  "challenge": {
    "id": "X6v1eo7fJ76gAxqY0xN9Jd__4lUyDDYmriryOM-5FO4",
    "realm": "api.decisionnode.com",
    "method": "stripe",
    "intent": "charge",
    "request": "eyJhbW91bnQiOiIyMDAwIiwiY3VycmVuY3kiOiJ1c2QiLCJkZXNjcmlwdGlvbiI6...",
    "expires": "2026-10-26T12:10:00Z",
    "header": "Payment-Authorization",
    "opaque": "eyJuIjoiUjNmNGtMbTJwUTl6WHZCbk43U3cifQ"
  },
  "payload": { "spt": "spt_1N4Zv32eZvKYlo2CPhVPkJlW" }
}
  • stripe

    Payload
    {"spt": "spt_..."}: a Shared Payment Token for the challenge's amount, issued by Stripe to your agent (the Link wallet, or Stripe's agent toolkit); externalId optional
    What the server does with it
    Confirms a Stripe payment with the token for the challenge's amount. succeeded books the credit. A token that needs an authentication (3-D Secure) is 402 payment_action_required: a machine cannot do one.
  • tempo

    Payload
    {"type": "hash", "hash": "0x<64 hex>"}: the hash of your transfer of the amount to the challenge's recipient, after you broadcast it
    What the server does with it
    Asks Stripe to verify that the transaction paid the deposit address at least the amount. While the transaction is still being verified the answer is 529 with Retry-After: 5: send the same credential again, and it resumes where it was. A pull-mode transaction payload or a proof is 402 invalid_payload.
  • x402

    Payload
    The x402 v2 payment payload: accepted (the accepts entry, amount set to what you signed) and payload with the EIP-3009 signature and authorization (from, to, value, validAfter, validBefore, nonce)
    What the server does with it
    Checks that accepted is ours (network, asset, payTo), that accepted.amount equals authorization.value and lies within $5.00 to $5,000.00, then verifies and settles the transfer through a facilitator and records it at Stripe. The amount you signed is the amount credited; it need not equal an earlier challenge's. Permit2 and ERC-7710 payloads are refused.
The payload of each method
MethodPayloadWhat the server does with it
stripe{"spt": "spt_..."}: a Shared Payment Token for the challenge's amount, issued by Stripe to your agent (the Link wallet, or Stripe's agent toolkit); externalId optionalConfirms a Stripe payment with the token for the challenge's amount. succeeded books the credit. A token that needs an authentication (3-D Secure) is 402 payment_action_required: a machine cannot do one.
tempo{"type": "hash", "hash": "0x<64 hex>"}: the hash of your transfer of the amount to the challenge's recipient, after you broadcast itAsks Stripe to verify that the transaction paid the deposit address at least the amount. While the transaction is still being verified the answer is 529 with Retry-After: 5: send the same credential again, and it resumes where it was. A pull-mode transaction payload or a proof is 402 invalid_payload.
x402The x402 v2 payment payload: accepted (the accepts entry, amount set to what you signed) and payload with the EIP-3009 signature and authorization (from, to, value, validAfter, validBefore, nonce)Checks that accepted is ours (network, asset, payTo), that accepted.amount equals authorization.value and lies within $5.00 to $5,000.00, then verifies and settles the transfer through a facilitator and records it at Stripe. The amount you signed is the amount credited; it need not equal an earlier challenge's. Permit2 and ERC-7710 payloads are refused.

Checked on every credential: the id binds the echoed parameters (a tampered challenge fails); expires has not passed; realm and header are ours; the challenge was issued to this caller (a keyless challenge presented with a key, a key holder's without one, or another workspace's, is refused); and the payment reference (the token, the transaction hash, the x402 nonce) has not been presented before. Only Stripe's confirmation books credit: a settled amount or currency other than the challenge's, a replayed payment, or a facilitator's word alone book nothing.

The answer#

200 OK with Cache-Control: private, no-store, and the protocol's receipt in a header: Payment-Receipt for MPP (base64url JSON: method, reference, status, timestamp; the reference is the Stripe payment's id for stripe and the transaction hash for tempo), PAYMENT-RESPONSE for x402 (standard base64 JSON: success, transaction, network, payer).

Response body

workspace_idstring
The workspace the credit landed on: your key's, or the new agent workspace of a keyless purchase.
creditobject
What was booked.
added_microsinteger
The amount, in micro-dollars (20000000 is $20.00).
balance_microsinteger
The workspace's balance after the booking, live.
expires_atstring
When this credit expires: 12 months after purchase. Credit is used oldest first.
receiptobject
The receipt, as the console lists it under Billing.
numberstring
The receipt number, DN- and digits; the PDF is in the console.
payment_intentstring
The Stripe payment's id, pi_....
methodstring
How it was paid, as the receipt says it: the card's brand and last four (Visa 4242), Link card, USDC on Tempo or USDC on Base.
keyobject
Only on a keyless purchase, and only once: the new workspace's key.
idstring
key_....
secretstring
The key, dn_live_.... It is shown here and nowhere else; store it at once.
scopestring
full: a Full access key.
HTTP/1.1 402 Payment RequiredCache-Control: no-storeContent-Type: application/problem+jsonWWW-Authenticate: Payment id="X6v1eo7fJ76gAxqY0xN9Jd__4lUyDDYmriryOM-5FO4", realm="api.decisionnode.com", method="stripe", intent="charge", request="eyJhbW91bnQiOiIyMDAwIiwiY3VycmVuY3kiOiJ1c2QiLCJkZXNjcmlwdGlvbiI6...", description="DecisionNode prepaid credit, $20.00. Paying accepts https://decisionnode.com/legal/terms", expires="2026-10-26T12:10:00Z", header="Payment-Authorization", opaque="eyJuIjoiUjNmNGtMbTJwUTl6WHZCbk43U3cifQ"WWW-Authenticate: Payment id="S91xi-OFGZPMs-j7GsX0FDpIkmCcZT1P9XyV58WNy_U", realm="api.decisionnode.com", method="tempo", intent="charge", request="eyJhbW91bnQiOiIyMDAwMDAwMCIsImN1cnJlbmN5IjoiMHgyMGMwMDAw...", description="DecisionNode prepaid credit, $20.00. Paying accepts https://decisionnode.com/legal/terms", expires="2026-10-26T12:10:00Z", header="Payment-Authorization", opaque="eyJuIjoiOUhxMlRwV2M3WGtMbTRSalZuMmEifQ"PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MiwiZXJyb3IiOiJQQVlNRU5ULVNJR05BVFVSRSBoZWFkZXIgaXMgcmVxdWlyZWQiLCJyZXNvdXJjZSI6...{"type":"https://paymentauth.org/problems/payment-required","title":"Payment Required","status":402,"detail":"$20.00 of DecisionNode credit. Pay with a Stripe Shared Payment Token, USDC on Tempo, USDC on Base (x402).","error_type":"payment_required"}
The receipt headers, decoded
{
  "method": "stripe",
  "reference": "pi_3SLx8k2eZvKYlo2C1aBcDeFg",
  "status": "success",
  "timestamp": "2026-10-26T12:04:32Z"
}
  1. How a purchase goes
  2. The challenge
  3. The credential
  4. The answer
  5. Amounts
  6. Without a key
  7. Paying on /v1/decide
  8. Refusals
  9. Discovery
request
# Without a key: the challenges for $20.00 of creditcurl -i -X POST https://api.decisionnode.com/v1/credit \  -H "Content-Type: application/json" \  -d '{"amount":"20.00"}'# With a key: the same, credited to the key's workspacecurl -i -X POST https://api.decisionnode.com/v1/credit \  -H "Authorization: Bearer $DECISIONNODE_API_KEY" \  -H "Content-Type: application/json" \  -d '{"amount":"20.00"}'

Amounts#

  • $5.00 to $5,000.00 a purchase, in whole cents: the body's amount is a decimal string in USD with at most two decimals, {"amount": "20.00"}; "7" is $7.00. Anything else, "4.99" or "5000.01" included, is 400 api_usage_error, "amount must be 5.00 to 5000.00 USD in whole cents." The body is optional and at most 4 KiB.
  • $5.00 without a body, and on the challenges a 402 of /v1/decide carries. Buy more in one go for a long job: a purchase is one Stripe payment, with Stripe's own minimum per charge, so a decision is never paid one at a time.
  • The amount is the credit. Nothing is added on top: no fee, and no tax is collected on a machine payment. The receipt shows the method and tax 0.
  • On the retry the body is ignored: what is charged is the challenge's amount, bound by its id. To buy another amount, ask for a fresh challenge.
  • Expiry. Credit bought this way expires 12 months after purchase and is used oldest first, as every top-up (Credit, refunds and disputes). It is prepaid credit under the Terms of Service, which the challenge's description names and the payment accepts.
  • No referral discount, no auto reload. There is no Checkout, so a referral code's first-top-up discount does not apply and no card is saved: an agent that runs low pays again. The console's own top-ups and auto reload work on the same workspace as before.

Without a key#

A call without Authorization is a keyless agent. Its challenges carry no workspace (opaque is {"n"} alone), and a successful payment makes an agent workspace: prepaid, without a free tier, without members, named "Agent" and eight hex digits, with one Full access key, which the answer's key field shows once. The money is the only gate: no email, no sign-up, no console, since nobody can sign in to it. Keep the key: an agent that loses it cannot recover it, and buys again.

200 of a keyless purchase
{
  "workspace_id": "ws_5b8d2f1e-3c7a-4e9b-8d6c-1a4f7e2b9c03",
  "credit": {
    "added_micros": 5000000,
    "balance_micros": 5000000,
    "expires_at": "2027-10-26T12:04:32Z"
  },
  "receipt": {
    "number": "DN-100124",
    "payment_intent": "pi_3SLx9q2eZvKYlo2C0ZyXwVuT",
    "method": "Link card 4242"
  },
  "key": {
    "id": "key_9f2b7c1d4e6a4b8c",
    "secret": "dn_live_...",
    "scope": "full"
  }
}
  • Use the key like every Full access key: POST /v1/decide, GET /v1/models, uploads, sessions and collections, under a prepaid workspace's default limits and the default safety policy. Top it up with the same key: a key holder's challenges carry the workspace, and the credit lands on it.
  • Caps per client address: 20 challenges a minute and 20 settled purchases a UTC day; past either, 429 rate_limit_error with Retry-After: 60. A key holder is under its workspace's usual rate limits instead.
  • An agent workspace is a workspace: its receipts, its usage and its balance are kept like every other workspace's, and DecisionNode may pause or close it under the Terms. A payment dispute freezes it like every workspace.

Paying on /v1/decide#

When an agent workspace's balance is empty, POST /v1/decide answers 402 insufficient_credit with details.reason balance_empty (Errors). That answer also carries the challenges of this page, for $5.00, bound to the key's workspace, with Cache-Control: no-store; its body and content type stay the contract's. A standard MPP client that meets it pays and retries the same call, and that works: the same request with the credential settles the payment, books the credit, then runs the decision, and the answer carries Payment-Receipt beside its own headers. The credit is booked whatever the decision then answers, so a 422 on the request still bought the credit. A plan's allowance_used carries the same challenges; plan_required and trial_allowance_used are not a payment matter and carry none.

HTTP/1.1 402 Payment RequiredCache-Control: no-storeContent-Type: application/jsonWWW-Authenticate: Payment id="CJ4X1O4aTDmS59hfdhnhBtxIQjWDOf0bcrhsswwMOW8", realm="api.decisionnode.com", method="stripe", intent="charge", request="eyJhbW91bnQiOiI1MDAiLCJjdXJyZW5jeSI6InVzZCIsImRlc2NyaXB0aW9uIjoi...", description="DecisionNode prepaid credit, $5.00. Paying accepts https://decisionnode.com/legal/terms", expires="2026-10-26T12:10:00Z", header="Payment-Authorization", opaque="eyJuIjoiWHE3cFI0a0wyTW5WOWpUM0J3Iiwid3MiOiJ3c18wZjNhOWMyZS0uLi4ifQ"WWW-Authenticate: Payment id="...", realm="api.decisionnode.com", method="tempo", intent="charge", request="...", description="DecisionNode prepaid credit, $5.00. Paying accepts https://decisionnode.com/legal/terms", expires="2026-10-26T12:10:00Z", header="Payment-Authorization", opaque="..."PAYMENT-REQUIRED: ...{"detail":{"error_type":"insufficient_credit","message":"Your prepaid balance is empty. Add credit in the console to continue.","details":{"reason":"balance_empty"}}}
  • Only when blocked. A credential on a decision call whose workspace has credit is not read: the decision runs as usual and nothing is charged. To buy ahead, call /v1/credit.
  • A refused credential on /v1/decide is the problem body of Refusals, with fresh challenges, and no decision runs. A suspended workspace is 403 before any payment is looked at.
  • Every other route's 402 (uploads, sessions, collections, knowledge) carries the same challenge headers and nothing else changes; pay at /v1/credit, then retry it.

Refusals#

A credential that cannot be settled is refused with the draft's problem body: application/problem+json, type under https://paymentauth.org/problems/https://paymentauth.org/problems/, title, status, a detail sentence, and error_type, the same code with underscores, to branch on. Every refusal has Cache-Control: no-store, and a 402 carries fresh challenges beside it. The calls that never reach a settlement (a bad amount, the wrong key, a suspended workspace) answer the API's usual {"detail": {...}} body (Errors).

A refusal
{
  "type": "https://paymentauth.org/problems/invalid-challenge",
  "title": "Invalid Payment Challenge",
  "status": 402,
  "detail": "This payment was already applied.",
  "error_type": "invalid_challenge"
}
  • 402

    error_type
    payment_required
    When
    No credential: the challenge itself, not a refusal
    What to do
    Pay one of the challenges and retry
  • 402

    error_type
    malformed_credential
    When
    Not Payment <base64url>, not JSON, over 16 KiB, or a challenge without its required parameters
    What to do
    Fix the encoding; echo the challenge whole
  • 402

    error_type
    invalid_challenge
    When
    The id does not bind what was echoed; realm or header is not ours; the challenge was issued to another caller or workspace; or the payment was presented before ("This payment was already applied."). One still being settled by another call is "This payment is being settled; retry in a moment." with Retry-After: 2
    What to do
    Take a fresh challenge with this key; never present a spent token or hash again
  • 402

    error_type
    payment_expired
    When
    Past expires (10 minutes)
    What to do
    Take a fresh challenge
  • 400

    error_type
    method_unsupported
    When
    A method or intent the challenges did not offer
    What to do
    Use a method from the challenges
  • 402

    error_type
    invalid_payload
    When
    The method's payload is missing or malformed: spt not a Shared Payment Token id, hash not 32 bytes of hex, a Tempo transaction (pull mode: "This server takes push mode: broadcast the transfer and send its hash.") or proof payload
    What to do
    Fix the payload
  • 400

    error_type
    invalid_payload
    When
    An x402 payload that is not ours: another network, asset or payTo, accepted.amount unequal to authorization.value or outside the bounds, a Permit2 or ERC-7710 transfer. PAYMENT-RESPONSE says success: false, errorReason invalid_payload
    What to do
    Sign the accepts entry as it came, for an amount within the bounds
  • 402

    error_type
    verification_failed
    When
    Stripe did not confirm the payment: a declined token ("The payment was declined (<code>)."), a transfer it could not find or that paid less; the x402 facilitator found the payment invalid (PAYMENT-RESPONSE errorReason, such as insufficient_funds); or the settled amount or currency is not the challenge's
    What to do
    Pay with another card, wallet or amount; nothing was credited
  • 402

    error_type
    payment_action_required
    When
    The card needs an authentication (3-D Secure), which a machine cannot do; Retry-After: 60
    What to do
    Use another card, or a stablecoin
  • 402

    error_type
    payment_insufficient
    When
    The challenge's amount is under $5.00; never issued by this server, so only a tampered challenge, which fails the binding first
    What to do
    Take a fresh challenge
  • 500

    error_type
    internal_payment_error
    When
    An x402 payment settled on chain that Stripe would not record. The detail names the transaction; the payment is never charged again, and nothing is credited until an operator reconciles it
    What to do
    Write to hello@bynn.com with the transaction hash
  • 529

    error_type
    overloaded_error
    When
    Stripe or the facilitator cannot answer, or refuses our key; Retry-After: 2, 5 while a stablecoin payment is still being verified, 15 while an x402 settlement is pending
    What to do
    Send the same credential again after Retry-After: a payment already made is found and resumed, never made twice
  • 429

    error_type
    rate_limit_error
    When
    A keyless caller past 20 challenges a minute or 20 purchases a day per client address; Retry-After: 60
    What to do
    Wait, then retry; or call with a key
  • 400

    error_type
    api_usage_error
    When
    An amount outside $5.00 to $5,000.00 or not in whole cents; both credential headers at once; a dedicated workspace ("Dedicated workspaces are invoiced monthly and take no prepaid credit.")
    What to do
    Fix the request; the message says what
  • 403

    error_type
    key_scope_denied
    When
    A Decisions-only or Publishable key
    What to do
    Call with a Full access or Manage device keys key, or without a key
  • 403

    error_type
    workspace_suspended
    When
    The workspace is paused, or frozen after a payment dispute; no payment lifts it
    What to do
    Check the console or write to hello@bynn.com
  • 405

    error_type
    When
    Any method but POST; Allow: POST
    What to do
    Use POST
Refusals of a credential
Statuserror_typeWhenWhat to do
402payment_requiredNo credential: the challenge itself, not a refusalPay one of the challenges and retry
402malformed_credentialNot Payment <base64url>, not JSON, over 16 KiB, or a challenge without its required parametersFix the encoding; echo the challenge whole
402invalid_challengeThe id does not bind what was echoed; realm or header is not ours; the challenge was issued to another caller or workspace; or the payment was presented before ("This payment was already applied."). One still being settled by another call is "This payment is being settled; retry in a moment." with Retry-After: 2Take a fresh challenge with this key; never present a spent token or hash again
402payment_expiredPast expires (10 minutes)Take a fresh challenge
400method_unsupportedA method or intent the challenges did not offerUse a method from the challenges
402invalid_payloadThe method's payload is missing or malformed: spt not a Shared Payment Token id, hash not 32 bytes of hex, a Tempo transaction (pull mode: "This server takes push mode: broadcast the transfer and send its hash.") or proof payloadFix the payload
400invalid_payloadAn x402 payload that is not ours: another network, asset or payTo, accepted.amount unequal to authorization.value or outside the bounds, a Permit2 or ERC-7710 transfer. PAYMENT-RESPONSE says success: false, errorReason invalid_payloadSign the accepts entry as it came, for an amount within the bounds
402verification_failedStripe did not confirm the payment: a declined token ("The payment was declined (<code>)."), a transfer it could not find or that paid less; the x402 facilitator found the payment invalid (PAYMENT-RESPONSE errorReason, such as insufficient_funds); or the settled amount or currency is not the challenge'sPay with another card, wallet or amount; nothing was credited
402payment_action_requiredThe card needs an authentication (3-D Secure), which a machine cannot do; Retry-After: 60Use another card, or a stablecoin
402payment_insufficientThe challenge's amount is under $5.00; never issued by this server, so only a tampered challenge, which fails the binding firstTake a fresh challenge
500internal_payment_errorAn x402 payment settled on chain that Stripe would not record. The detail names the transaction; the payment is never charged again, and nothing is credited until an operator reconciles itWrite to hello@bynn.com with the transaction hash
529overloaded_errorStripe or the facilitator cannot answer, or refuses our key; Retry-After: 2, 5 while a stablecoin payment is still being verified, 15 while an x402 settlement is pendingSend the same credential again after Retry-After: a payment already made is found and resumed, never made twice
429rate_limit_errorA keyless caller past 20 challenges a minute or 20 purchases a day per client address; Retry-After: 60Wait, then retry; or call with a key
400api_usage_errorAn amount outside $5.00 to $5,000.00 or not in whole cents; both credential headers at once; a dedicated workspace ("Dedicated workspaces are invoiced monthly and take no prepaid credit.")Fix the request; the message says what
403key_scope_deniedA Decisions-only or Publishable keyCall with a Full access or Manage device keys key, or without a key
403workspace_suspendedThe workspace is paused, or frozen after a payment dispute; no payment lifts itCheck the console or write to hello@bynn.com
405Any method but POST; Allow: POSTUse POST

A 529 also carries retry-after-ms. A payment that was made but whose answer you lost is safe to present again: the same token or hash finds its own record and the answer is the booking it made, or invalid_challenge "This payment was already applied." once booked. Credentials, tokens and signatures are never logged, never in a receipt and never in an audit entry.

Discovery#

GET https://api.decisionnode.com/openapi.json is public, needs no key and is cached a minute: an OpenAPI 3.1 document in the shape of the payment discovery draft, so an agent or a registry finds where and how to pay. x-service-info names the docs, this page's llms.txt included; paths./v1/credit.post.x-payment-info.offers lists one offer per method on (amount null: the body sets it), and paths./v1/decide.post carries the same offers with the note that a decision is paid from credit. Offers appear and go with the methods the deployment has on; read the document rather than assuming all three.

GET /openapi.json
{
  "openapi": "3.1.0",
  "info": { "title": "DecisionNode API", "version": "..." },
  "servers": [
    { "url": "https://api.decisionnode.com" }
  ],
  "x-service-info": {
    "categories": ["developer-tools", "compute"],
    "docs": {
      "homepage": "https://decisionnode.com/docs",
      "llms": "https://decisionnode.com/llms.txt",
      "apiReference": "https://decisionnode.com/docs/api"
    }
  },
  "paths": {
    "/v1/credit": {
      "post": {
        "operationId": "buyCredit",
        "x-payment-info": {
          "offers": [
            {
              "intent": "charge",
              "method": "stripe",
              "amount": null,
              "currency": "usd",
              "description": "A card through a Stripe Shared Payment Token. ..."
            },
            {
              "intent": "charge",
              "method": "tempo",
              "amount": null,
              "currency": "0x20c000000000000000000000b9537d11c60e8b50",
              "description": "USDC on Tempo (chain 4217), push mode. ..."
            },
            {
              "intent": "charge",
              "method": "x402",
              "amount": null,
              "currency": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
              "description": "x402 exact on eip155:8453 (USDC), PAYMENT-SIGNATURE header. ..."
            }
          ]
        }
      }
    },
    "/v1/decide": {
      "post": {
        "operationId": "decide",
        "x-payment-info": {
          "offers": ["..."]
        }
      }
    }
  }
}

Give your agent the page, not the protocol

An agent that reads llms.txt finds this page and the discovery document. With Stripe's agent toolkit or Link CLI, or Tempo's CLI, the whole exchange is one command: the client reads the challenge, pays it, and retries with the credential. Build your own client only where those do not run.

previousUsage by keynextErrors

DecisionNode is built and run by Bynn Intelligence, Inc.

  • Home
  • Playground
  • Examples
  • Console
  • Responsible use
  • Terms
  • Acceptable use
  • Privacy
  • Data processing
  • Defence addendum
  • Cookies
  • Affiliate program