Error codes
This guide covers /api/chain/* responses. Deepstate market data (/deepstate/v1/*) only ever emits the flat gateway shape below, never the envelope shape; see Deepstate Market Data → Errors.
OneSource can emit three error shapes depending on where the failure originates. Your client needs to handle all three:
- Envelope errors: from the OneSource service itself (parameter validation, on-chain lookups, upstream RPC issues). Carries
{error: {code, message}, meta}. - Gateway errors: from the API gateway in front of OneSource (auth, payment, routing). Flat
{error, message}. - 402 + MPP RFC 7807 errors: payment-protocol-specific shapes the gateway forwards verbatim. Different again.
The HTTP status is the only thing all three share. Branch on it when in doubt; use the body to read details.
Envelope errors (from the OneSource service)
{
"error": {
"code": 400,
"message": "address param required"
},
"meta": { "endpoint": "/api/chain/live-balance", "request_id": "abcdef0123456789" }
}
error.codeis the HTTP status as an integer (400,402,500,502), mirroring the status line. It is not a stable string identifier.error.messageis human-readable English produced by the handler. The wording is not a stable contract; don't parse it.meta.request_idmirrors the server-side trace. Always log it.datais omitted on error (not present asnull).
Branch on the HTTP status (or equivalently error.code), and use error.message for diagnostics.
What you'll see by status
| HTTP | When | Example error.message |
|---|---|---|
400 | Parameter missing, malformed, or wrong type. | "address param required", "invalid number \"notanumber\": expected decimal or 0x-prefixed hex", "to and data fields required", "invalid JSON body" |
404 | Path doesn't match any route on OneSource (the gateway forwarded an unknown path). Unregistered /api/chain/* paths never get this far: the gateway catches them and returns the flat shape below. | "404 page not found" |
500 | Unhandled internal failure. | Captured handler message. |
502 | Upstream RPC node returned an error; the message passes the upstream verbatim. | "ownerOf failed: RPC error 3: execution reverted: ERC721: owner query for nonexistent token", "getLogs failed: RPC error -32602: query exceeds max results 20000, retry with the range ..." |
The 400 bucket is by far the most common; pre-validate parameters client-side against the API Reference before sending.
"Resource not found" doesn't mean 404
OneSource does not return 404 for missing on-chain resources. The semantics vary by endpoint, and you generally need to look at the response body:
| Endpoint | Unknown / missing resource returns |
|---|---|
/api/chain/block/{n} (past tip or future) | 200 + {"data": {"result": null}, ...} |
/api/chain/tx/{hash} (unknown hash) | 200 + {"data": {"transaction": null, "receipt": null}, ...} |
/api/chain/receipt/{hash} (unknown / unmined) | 200 + {"data": {"result": null}, ...} |
/api/chain/nft-owner (nonexistent token_id) | 502 envelope error; the RPC revert ("execution reverted: ERC721: owner query for nonexistent token") passes through |
/api/chain/ens/{name} (no resolver) | 200 + {"data": {"name": "...", "error": "no resolver set for this name"}, ...}: inline data.error string, NOT the envelope error field |
Polling on HTTP 404 to detect "tx pending" or "NFT doesn't exist" will fail. The reliable patterns:
// "Is this tx confirmed?": receipt by hash
const { data } = await res.json();
if (data?.result === null) return 'pending';
return { status: 'confirmed', receipt: data.result };
// "Does this NFT exist?": catch the 502 revert
if (!res.ok && body?.error?.code === 502 &&
body.error.message.includes('owner query for nonexistent token')) {
return 'does-not-exist';
}
Gateway errors (auth, payment, routing)
Returned by the gateway before the request reaches OneSource, so there's no meta block. Flat shape:
{ "error": "invalid_token", "message": "Invalid or expired API key" }
error is a lowercase snake_case identifier. Branch on it the same way you would on an HTTP status.
error | HTTP | Cause | Recovery |
|---|---|---|---|
invalid_token | 401 | Authorization: Bearer sk_… was present but the key isn't recognized or has been revoked. | Re-copy the key from app.onesource.io → API Keys. Rotate if compromised. |
api_key_required | 401 | The endpoint requires a Bearer key but x402/MPP isn't accepted, or both x402 and MPP are disabled. An anonymous call to a paid endpoint returns 402 with a payment challenge instead (see the next section). | Add Authorization: Bearer sk_…. |
plan_required | 403 | Your API key doesn't have an active subscription yet. | Finish subscribing at app.onesource.io, or call the endpoint with x402 / MPP instead. |
daily_limit_exceeded | 429 | Your account's daily request quota is spent. | Wait for the 00:00 UTC reset, move overflow to x402 / MPP, or email support@onesource.io for a higher cap. See Usage quotas. |
monthly_limit_exceeded | 429 | Your account's monthly request quota is spent. | Route overflow to x402 / MPP until the calendar month rolls over, or email support@onesource.io for a higher cap. |
rate_limited | 429 | An extreme burst tripped the backend's abuse guard. This is not a plan limit. | Back off and retry; Retry-After: 1 is set. If you see it in normal operation, email support@onesource.io. |
auth_unavailable | 503 | The gateway couldn't reach the validation service. Transient. | Retry with backoff. |
invalid_payment | 400 | x402 / MPP payment header was present but malformed. | Re-sign with the current challenge. |
verification_failed | 502 | Payment header was well-formed but the facilitator couldn't verify it. | Re-sign and retry; if persistent, the facilitator is degraded. |
upstream_error | 502 | Gateway reached OneSource but OneSource returned an unrecoverable error. | Retry with backoff. |
not_found | 404 | Either no upstream is configured for this host, or the path is an unregistered /api/chain/* route. The gateway knows the complete /api/chain/* route set, so it can identify a typo there as a bad path. | Check the hostname, and check the path against the API Reference. |
Unknown paths outside /api/chain/* are not enumerated by the gateway, so they come back as api_key_required (401) rather than not_found. A 401 on a path you believe is real is worth re-checking for a typo before assuming it's an auth problem.
Settlement errors (after a paid call)
A paid call settles after OneSource has answered. The gateway holds the response, settles your payment, and releases the data only once settlement confirms. If the call itself fails upstream, you aren't charged. If settlement fails, you get one of these errors instead of the data, in the flat shape above, and the Payment-Response receipt header carries the same code:
error | HTTP | What happened | Recovery |
|---|---|---|---|
settlement_failed | 402 | The payment was rejected before anything moved. The response carries a fresh Payment-Required challenge. | Pay again with a new authorization. |
settlement_unavailable | 503 | Settlement is temporarily unavailable, and your payment was not charged. | Retry the request. Retry-After is set. |
settlement_unconfirmed | 503 | OneSource couldn't confirm whether your payment went through. It may have. | Don't create a new payment for this request. Resending the same payment authorization is safe. If your client can't do that, email support@onesource.io with your wallet address and the time of the request. |
settlement_pending | 402 | The transfer was broadcast but hasn't confirmed yet. No new challenge is attached. | Check the transaction in the Payment-Response receipt before paying again. |
authorization_used | 402 | The payment authorization you sent was already used, most likely by an earlier request whose reply you didn't get. No new challenge is attached. | Don't send a new payment for this request. Email support@onesource.io with the authorization nonce. |
MPP single payments follow the same rule. A clean rejection comes back as RFC 7807 problem+json (see below). If the transfer was broadcast but settlement didn't complete, you get 503 settlement_unconfirmed with a transaction field holding the Tempo transaction hash: check whether it confirmed, and if it did, email support@onesource.io with the hash.
settlement_unconfirmed is the one error where a blind retry can cost you money. Payment-aware clients such as @x402/fetch and MPP fetchers sign a new payment on every attempt, so retrying the call pays again. Check whether the payment went through before doing anything else.
Payment-required (402) and MPP RFC 7807
402 from an anonymous call
An unauthenticated call to a paid endpoint comes back with HTTP 402 and a third hybrid shape: error is a string (like gateway flat shape) but a meta block is present too:
{
"error": "Payment required",
"meta": {
"cost_usdc": "0.001",
"endpoint": "/api/chain/network-info",
"network": "eip155:8453"
}
}
The response also carries the protocol-specific challenge headers:
Payment-Required: <base64>: x402 challenge (price in USDC on Base, recipient address, expiry).WWW-Authenticate: Payment ...(one or more): MPP challenges (Tempo network, both one-shotchargeandsessionflavors).
A single 402 advertises every supported protocol; clients pick the one they speak and ignore the rest. Sign and retry; payment-aware HTTP clients (@x402/fetch v2 for x402, MPP-aware fetchers for Tempo) handle this loop for you. See x402 on Base or MPP on Tempo.
MPP credential errors (RFC 7807)
A malformed MPP Authorization: Payment ... credential comes back from the MPP SDK in RFC 7807 problem+json form, neither envelope nor gateway-flat:
{
"type": "https://mpp.dev/errors/malformed-credential",
"title": "Malformed Credential",
"status": 400,
"detail": "mpp: invalid credential encoding: json decode: invalid character ..."
}
Branch on type (a stable URL) when handling MPP-specific failures.
The gateway also exposes quota headers (X-Quota-Day-Limit, X-Quota-Day-Used, X-Quota-Month-Limit, X-Quota-Month-Used) on every API-key response, including on a 429.
Anatomy of recovery logic
A client that recovers cleanly branches on HTTP status. Normalize the three body shapes up front so the rest of the logic stays simple:
function parseError(status: number, body: any): { code: number | string; message: string } | null {
// RFC 7807 (MPP credential errors): { type, title, detail, status }
if (body?.type && body?.title) {
return { code: body.type, message: body.detail ?? body.title };
}
if (!body?.error) return null;
// Envelope shape: { error: { code: <int>, message }, meta }
if (typeof body.error === 'object') return body.error;
// Gateway flat shape: { error: "<snake_case>", message }
// Also handles the 402 hybrid: { error: "Payment required", meta }
return { code: body.error, message: body.message ?? '' };
}
async function callSkills(url: string, init: RequestInit, attempt = 0): Promise<unknown> {
const res = await fetch(url, init);
const body = await res.json();
const err = parseError(res.status, body);
if (!err) return body.data;
// Retryable: transient gateway/upstream failures + 5xx from OneSource.
const retryable =
err.code === 'auth_unavailable' ||
err.code === 'upstream_error' ||
err.code === 'verification_failed' ||
(typeof err.code === 'number' && err.code >= 500);
if (retryable && attempt < 3) {
await new Promise(r => setTimeout(r, 2 ** attempt * 500 + Math.random() * 250));
return callSkills(url, init, attempt + 1);
}
throw new APIError(err, res.status);
}
Note: don't branch on err.code === 404 to detect "resource not found": OneSource returns 200 + data.*: null for missing on-chain resources. See the "Resource not found" section above.
A few notes on what this sketch deliberately doesn't do:
- No
429branch. Quota rejections need handling that differs by cause, which the flat retryable test above can't express:rate_limitedclears in a second and setsRetry-After, whiledaily_limit_exceededandmonthly_limit_exceededwill not clear on any retry loop. See Usage quotas for a429-aware version. - No retry on
settlement_unconfirmed. It's a string code, so the numeric>= 500test above skips it, and that's deliberate: retrying through a payment-aware client pays again. See Settlement errors. - No 402 branch. Payment-aware HTTP clients (
@x402/fetch, MPP fetchers) handle the402 → sign → retryloop for you. If you're writing one of those clients yourself, branch on402and read thePayment-Required/WWW-Authenticateheaders.
Always log request_id
meta.request_id (envelope shape only; gateway errors don't carry one) is the single most useful field for support. It mirrors the server-side trace; including it in your error log lets the team find the corresponding entry in one query.
When in doubt
Email support@onesource.io with:
- The endpoint and parameters.
- The full response body and HTTP status.
meta.request_idif the failure came from OneSource (envelope shape).- The approximate time of the failure.
If you're using the OneSource MCP Server, the 1s_report_bug tool files the same details for you.