Skip to main content

Error codes

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.code is the HTTP status as an integer (400, 402, 500, 502), mirroring the status line. It is not a stable string identifier.
  • error.message is human-readable English produced by the handler. Wording can change between releases; don't parse it.
  • meta.request_id mirrors the server-side trace. Always log it.
  • data is omitted on error (not present as null).

Branch on the HTTP status (or equivalently error.code), and use error.message for diagnostics.

What you'll see by status

HTTPWhenExample error.message
400Parameter 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"
404Path doesn't match any route on OneSource (the gateway forwarded an unknown path)."404 page not found"
500Unhandled internal failure.Captured handler message.
502Upstream 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:

EndpointUnknown / 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.

errorHTTPCauseRecovery
invalid_token401Authorization: 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_required401The endpoint requires a Bearer key but x402/MPP isn't accepted, or both x402 and MPP are disabled. In current production, an anonymous call to a paid endpoint returns 402 with a payment challenge instead (see the next section).Add Authorization: Bearer sk_….
plan_required403Your key is on the sandbox tier but the endpoint requires a paid plan.Upgrade your subscription at app.onesource.io or call the endpoint with x402 / MPP instead.
auth_unavailable503The gateway couldn't reach the validation service. Transient.Retry with backoff.
invalid_payment400x402 / MPP payment header was present but malformed.Re-sign with the current challenge.
verification_failed502Payment header was well-formed but the facilitator couldn't verify it.Re-sign and retry; if persistent, the facilitator is degraded.
upstream_error502Gateway reached OneSource but OneSource returned an unrecoverable error.Retry with backoff.
not_found404No upstream configured for this host. (404s for unknown paths on api.onesource.io return the envelope shape above, not this one.)Check the hostname.

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-shot charge and session flavors).

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 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 rate-limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) on every authenticated response.

Anatomy of recovery logic

A robust client 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 RATE_LIMITED / Retry-After handling. OneSource currently emits rate-limit headers (X-RateLimit-*) but does not return 429 responses today. If you exceed the cap, the gateway logs it and the headers will trend toward zero, but calls are not actively blocked at the application layer. Future enforcement will add 429 + Retry-After; at that point a 429 branch with Retry-After becomes the right pattern.
  • No 402 branch. Payment-aware HTTP clients (@x402/fetch, MPP fetchers) handle the 402 → sign → retry loop for you. If you're writing one of those clients yourself, branch on 402 and read the Payment-Required / WWW-Authenticate headers.

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

Open a bug report including:

  1. The endpoint and parameters.
  2. The full response body and HTTP status.
  3. meta.request_id if the failure came from OneSource (envelope shape).
  4. The approximate time of the failure.

Filed via the 1s_report_bug MCP tool (if you're using the OneSource MCP Server) or as a GitHub issue.