curl --request POST \
--url https://api.platform.dakota.xyz/payment-agents/{payment_agent_id}/x402/mandates \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--header 'x-idempotency-key: <x-idempotency-key>' \
--data '
{
"asset": "USDC",
"network": "base-sepolia",
"max_per_call": "500000",
"max_per_window": "2000000",
"window_seconds": 3600,
"payee_policy": {
"mode": "domain_allowlist",
"domains": [
"*.marketpulse.example"
]
}
}
'const options = {
method: 'POST',
headers: {
'x-idempotency-key': '<x-idempotency-key>',
'x-api-key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
asset: 'USDC',
network: 'base-sepolia',
max_per_call: '500000',
max_per_window: '2000000',
window_seconds: 3600,
payee_policy: {mode: 'domain_allowlist', domains: ['*.marketpulse.example']}
})
};
fetch('https://api.platform.dakota.xyz/payment-agents/{payment_agent_id}/x402/mandates', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.platform.dakota.xyz/payment-agents/{payment_agent_id}/x402/mandates"
payload = {
"asset": "USDC",
"network": "base-sepolia",
"max_per_call": "500000",
"max_per_window": "2000000",
"window_seconds": 3600,
"payee_policy": {
"mode": "domain_allowlist",
"domains": ["*.marketpulse.example"]
}
}
headers = {
"x-idempotency-key": "<x-idempotency-key>",
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.platform.dakota.xyz/payment-agents/{payment_agent_id}/x402/mandates"
payload := strings.NewReader("{\n \"asset\": \"USDC\",\n \"network\": \"base-sepolia\",\n \"max_per_call\": \"500000\",\n \"max_per_window\": \"2000000\",\n \"window_seconds\": 3600,\n \"payee_policy\": {\n \"mode\": \"domain_allowlist\",\n \"domains\": [\n \"*.marketpulse.example\"\n ]\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-idempotency-key", "<x-idempotency-key>")
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"id": "2vWxX402Mandate000000000001",
"agent_id": "2vWxAgent000000000000000000",
"wallet_id": "2vWxWallet00000000000000000",
"asset": "USDC",
"network": "base-sepolia",
"max_per_call": "500000",
"max_per_window": "2000000",
"window_seconds": 3600,
"payee_policy": {
"mode": "domain_allowlist",
"domains": [
"*.marketpulse.example"
]
},
"valid_from": "2026-09-08T00:00:00Z",
"valid_until": "2026-10-08T00:00:00Z",
"window_committed": "100000",
"window_calls": 1
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}Create an x402 spend mandate (BETA)
Beta — early access.
Creates the standing allowance an agent pays x402-metered resources under. Unlike a payment mandate this authorizes a BUDGET rather than a payee: x402 payees are discovered at request time from a seller’s 402 response, so the controls are a per-call ceiling plus a rolling aggregate across every payee. Amounts are atomic units of the asset (USDC has 6 decimals, so “100000” is $0.10).
The budget belongs to the agent’s x402 wallet in that asset on that network, not to one mandate. A new mandate replaces the agent’s live mandate for the same asset and network: the old one is cancelled in the same step (its revoked_at is set) and never comes back into force, even after the new one expires. Spend already committed in the new mandate’s window counts against it, whichever mandate authorized it, so lowering a limit over the same or a longer window never frees up more spend. A shorter window looks back only that far: replacing 100 per day with 50 per hour allows 50 in the next hour, whatever the day has already spent.
curl --request POST \
--url https://api.platform.dakota.xyz/payment-agents/{payment_agent_id}/x402/mandates \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--header 'x-idempotency-key: <x-idempotency-key>' \
--data '
{
"asset": "USDC",
"network": "base-sepolia",
"max_per_call": "500000",
"max_per_window": "2000000",
"window_seconds": 3600,
"payee_policy": {
"mode": "domain_allowlist",
"domains": [
"*.marketpulse.example"
]
}
}
'const options = {
method: 'POST',
headers: {
'x-idempotency-key': '<x-idempotency-key>',
'x-api-key': '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
asset: 'USDC',
network: 'base-sepolia',
max_per_call: '500000',
max_per_window: '2000000',
window_seconds: 3600,
payee_policy: {mode: 'domain_allowlist', domains: ['*.marketpulse.example']}
})
};
fetch('https://api.platform.dakota.xyz/payment-agents/{payment_agent_id}/x402/mandates', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.platform.dakota.xyz/payment-agents/{payment_agent_id}/x402/mandates"
payload = {
"asset": "USDC",
"network": "base-sepolia",
"max_per_call": "500000",
"max_per_window": "2000000",
"window_seconds": 3600,
"payee_policy": {
"mode": "domain_allowlist",
"domains": ["*.marketpulse.example"]
}
}
headers = {
"x-idempotency-key": "<x-idempotency-key>",
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.platform.dakota.xyz/payment-agents/{payment_agent_id}/x402/mandates"
payload := strings.NewReader("{\n \"asset\": \"USDC\",\n \"network\": \"base-sepolia\",\n \"max_per_call\": \"500000\",\n \"max_per_window\": \"2000000\",\n \"window_seconds\": 3600,\n \"payee_policy\": {\n \"mode\": \"domain_allowlist\",\n \"domains\": [\n \"*.marketpulse.example\"\n ]\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-idempotency-key", "<x-idempotency-key>")
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}{
"id": "2vWxX402Mandate000000000001",
"agent_id": "2vWxAgent000000000000000000",
"wallet_id": "2vWxWallet00000000000000000",
"asset": "USDC",
"network": "base-sepolia",
"max_per_call": "500000",
"max_per_window": "2000000",
"window_seconds": 3600,
"payee_policy": {
"mode": "domain_allowlist",
"domains": [
"*.marketpulse.example"
]
},
"valid_from": "2026-09-08T00:00:00Z",
"valid_until": "2026-10-08T00:00:00Z",
"window_committed": "100000",
"window_calls": 1
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}{
"type": "https://docs.dakota.xyz/api-reference/errors#not-found",
"title": "Customer Not Found",
"status": 404,
"detail": "Customer cst_2abc123 was not found in your organization.",
"instance": "https://api.platform.dakota.xyz/customers/cst_2abc123",
"request_id": "req_7f3a8b2c"
}Authorizations
Headers
Unique key (UUID) to ensure request idempotency on POST, PUT and PATCH requests.
A key is scoped to your client, the HTTP method and the path, and is remembered for 24 hours after a
successful (below 400) response. Repeating the same request (same JSON body, whatever the key order or
whitespace) with the same key within that window returns the original response with
X-Idempotent-Replayed: true instead of executing the request again. Using the same key on the same
endpoint with a different body returns 422 with problem type idempotency-key-reused; the request is
not executed. Error responses (4xx and 5xx) are not remembered, so a request refused for a fixable reason
can be corrected and retried under the same key. Request bodies that are not JSON (for example file
uploads) are not compared.
Path Parameters
Body
Asset symbol. Only USDC is supported in this release; any other value is refused with a 400, since such a mandate could never be used.
Network the mandate covers, e.g. base-sepolia. Only Base and Base Sepolia are supported.
Per-authorization ceiling, in atomic units.
Ceiling on total committed spend across ALL payees in one rolling window, in atomic units. Omit for unbounded (discouraged).
Rolling aggregate window. Defaults to 24h.
Optional cap on authorizations per window. 0 is no cap.
Mandate lifetime from now. Defaults to 30 days.
What an x402 mandate may pay for, beyond its budget. Omit the whole object to leave the budget as the only control.
mode is required when the object is present, and the list must match it: address_allowlist needs addresses and reads no domains, domain_allowlist needs domains and reads no addresses, and any_screened enforces neither, so it carries no list. A policy that breaks those rules is refused with a 400 rather than stored as a restriction nothing applies.
any_screened accepts any payee that passes address screening. address_allowlist pins the payee address itself. domain_allowlist pins the RESOURCE host the caller names when it asks for a signature - the readable form, since an operator approves "any metered API under this vendor's domain" rather than a hex address. Note that it bounds the resource, not the payee: a seller who prices one resource can be paid for it whatever address it nominates.
Show child attributes
Show child attributes
Response
Mandate created
What an x402 mandate may pay for, beyond its budget. Omit the whole object to leave the budget as the only control.
mode is required when the object is present, and the list must match it: address_allowlist needs addresses and reads no domains, domain_allowlist needs domains and reads no addresses, and any_screened enforces neither, so it carries no list. A policy that breaks those rules is refused with a 400 rather than stored as a restriction nothing applies.
any_screened accepts any payee that passes address screening. address_allowlist pins the payee address itself. domain_allowlist pins the RESOURCE host the caller names when it asks for a signature - the readable form, since an operator approves "any metered API under this vendor's domain" rather than a hex address. Note that it bounds the resource, not the payee: a seller who prices one resource can be paid for it whatever address it nominates.
Show child attributes
Show child attributes
When the mandate was cancelled, or replaced by a newer mandate for the same asset and network. A revoked mandate authorizes nothing.
Spend already committed on this mandate's budget in the window ending now, in atomic units: every authorization from the agent's x402 wallet in this asset and network over this mandate's window_seconds, whichever mandate issued it, counting outstanding holds as well as settled ones. It is the budget's figure, not this mandate's own: a replaced or cancelled mandate shows the same spend as the mandate in force, so never add it up across mandates.
Authorizations already issued in the current window, counted the same way as window_committed.
Was this page helpful?

