Base URL: https://api.10fi.org
This document defines every error shape a pilot merchant may encounter, the meaning of each error code, and the recommended merchant action. All responses use a consistent JSON structure.
---
Every /v1 error response follows this structure:
{
"code": "ERROR_CODE",
"message": "Human-readable description of the error.",
"error": "Human-readable description of the error.",
"requestId": "a1b2c3d4",
"reqId": "a1b2c3d4"
}
| Field | Type | Description |
|-------|------|-------------|
| code | string | Machine-readable error code (see table below). Use this for programmatic branching. |
| message | string | Human-readable explanation. Safe to log and display to end users. |
| error | string | Deprecated alias for message. Always identical. Prefer message. |
| requestId | string | 8-character request identifier. Include this when contacting 10FI support. |
| reqId | string | Deprecated alias for requestId. Always identical. Prefer requestId. |
| Field | When present | Description |
|-------|-------------|-------------|
| retryAfterSec | 429 RATE_LIMITED | Seconds to wait before retrying. |
| usage | 429 RATE_LIMITED (authenticated) | Hint string pointing you at GET /v1/usage / GET /v1/limits to read your actual RPM/RPD/RPMo counters — not an inline counters object. |
| hint | 429 RATE_LIMITED (keyless) | Suggestion to obtain an API key for higher limits. |
| getAccess | 401 UNAUTHORIZED (keyless) | URL or message for obtaining API credentials. |
---
| Code | HTTP Status | When it occurs | Retryable? | Merchant action |
|------|-------------|---------------|------------|-----------------|
| INVALID_REQUEST | 400 | Missing or malformed required parameter (chain, from, to, amount, userAddress, etc.) | No — fix the request | Check the message field for which parameter is missing or invalid. |
| UNAUTHORIZED | 401 | Missing X-Client-Id/X-API-Key headers, or invalid/revoked credentials | No — fix credentials | Send both X-Client-Id and X-API-Key headers. Verify your key is active in the merchant dashboard. |
| FORBIDDEN | 403 | Your API key is valid but the requested product is not enabled, or your merchant account is not approved | No — request product enablement | Contact 10FI to enable the required product on your key. See Product Entitlements below. |
| NOT_FOUND | 404 | The requested order, transfer, or resource does not exist | No | Verify the order ID or tx hash is correct. Transfers may take time to propagate. |
| QUOTE_UNAVAILABLE | 400/500 | No routes found for the requested token pair, chain, or amount | Possibly — try again later or adjust parameters | Try a different token pair, adjust the amount, or switch chains. |
| RATE_LIMITED | 429 | Per-key or per-IP rate limit exceeded | Yes — wait for retryAfterSec | Respect the Retry-After header and retryAfterSec field. Upgrade your tier for higher limits. |
| UPSTREAM_ERROR | 502 | An upstream liquidity provider returned an error or timed out | Yes — retry after a short delay | Retry with the same parameters. If persistent, contact 10FI support. |
| INTERNAL_ERROR | 500 | Unexpected server-side error | Yes — retry after a short delay | Retry. If persistent, contact 10FI support with the requestId. |
> Note (as of this pilot): NOT_FOUND and UPSTREAM_ERROR are part of the API's error-code contract and pilot merchants should still handle them defensively, but no current code path returns them through the standard envelope above — an upstream provider failure today surfaces as QUOTE_UNAVAILABLE (400) or INTERNAL_ERROR (500), and the one live 404 case (an unrecognized gasless order id) currently returns a minimal {error, reqId} body without a code field. Treat any unrecognized code value, or a response missing code entirely, the same way you'd treat INTERNAL_ERROR — log the requestId/reqId and retry with backoff.
---
{
"code": "UNAUTHORIZED",
"message": "API credentials required. Send X-Client-Id and X-API-Key headers.",
"error": "API credentials required. Send X-Client-Id and X-API-Key headers.",
"requestId": "a1b2c3d4",
"reqId": "a1b2c3d4",
"getAccess": "Free tier..."
}
Cause: Request sent without X-Client-Id or X-API-Key headers.
Fix: Add both headers to every /v1 request.
{
"code": "UNAUTHORIZED",
"message": "Invalid credentials: check X-Client-Id and X-API-Key pair, or key may be revoked",
"error": "Invalid credentials: check X-Client-Id and X-API-Key pair, or key may be revoked",
"requestId": "b2c3d4e5",
"reqId": "b2c3d4e5"
}
Cause: The X-Client-Id and X-API-Key pair does not match, or the key has been revoked.
Fix: Verify both values in your merchant dashboard. Generate a new key if needed.
---
Your API key can be entitled to one or more products: swap_api, gasless_api, cross_chain_api. If your key calls a product that is not enabled, you receive a 403 FORBIDDEN.
A merchant key entitled to swap_api only, requesting a cross-chain quote (toChain differs from chain):
curl "https://api.10fi.org/v1/quote?chain=bsc&toChain=base&from=USDC&to=USDC&amount=100000000" \
-H "X-Client-Id: ci_swap_only_merchant" \
-H "X-API-Key: sk_swap_only_key"
Response:
{
"code": "FORBIDDEN",
"message": "This product is not enabled for this API key.",
"error": "This product is not enabled for this API key.",
"requestId": "c3d4e5f6",
"reqId": "c3d4e5f6"
}
Cause: Your key has swap_api but not cross_chain_api enabled.
Fix: Contact 10FI to enable cross_chain_api on your key. Same-chain requests (chain only, no toChain) will continue to work.
A merchant key without gasless_api entitlement, calling a gasless endpoint:
curl -X POST "https://api.10fi.org/v1/gasless/prepare" \
-H "Content-Type: application/json" \
-H "X-Client-Id: ci_no_gasless" \
-H "X-API-Key: sk_no_gasless_key" \
-d '{"chain":"bsc","from":"USDT","to":"WBNB","amount":"100000000000000000000","userAddress":"0xTest"}'
Response:
{
"code": "FORBIDDEN",
"message": "This product is not enabled for this API key.",
"error": "This product is not enabled for this API key.",
"requestId": "d4e5f6a7",
"reqId": "d4e5f6a7"
}
Cause: Your key does not have gasless_api enabled.
Fix: Contact 10FI to enable gasless_api. The Gasless API is currently shadow/mock for pilot merchants unless explicitly enabled.
If chain or toChain is provided as a non-string value (e.g., an array from a duplicated query key) — or as a string that collides with an inherited Object.prototype member name (constructor, __proto__, toString, ...) — the API rejects the request outright, before checking any product entitlement:
curl "https://api.10fi.org/v1/quote?chain=bsc&toChain=base&toChain=ethereum&from=USDC&to=USDC&amount=100000000" \
-H "X-Client-Id: ci_your_client_id" \
-H "X-API-Key: sk_your_api_key"
Response (regardless of which products your key has enabled):
{
"code": "INVALID_REQUEST",
"message": "chain and toChain must be plain chain identifier strings.",
"error": "chain and toChain must be plain chain identifier strings.",
"requestId": "e5f6a7b8",
"reqId": "e5f6a7b8"
}
Behavior: malformed chain/toChain is invalid input, not a same-chain-or-cross-chain question — there is no safe way to guess which product such a shape should require, so the API returns 400 INVALID_REQUEST outright rather than checking either swap_api or cross_chain_api. This holds even for a key with every product enabled. (An earlier design tried to fail closed to the stricter product instead; that approach was found to be exploitable by a cross_chain_api-only key and was replaced with outright rejection — see PHASE-79-PILOT-LAUNCH-SECURITY-REVIEW.md.)
---
{
"code": "INVALID_REQUEST",
"message": "Missing required parameter: chain",
"error": "Missing required parameter: chain",
"requestId": "f6a7b8c9",
"reqId": "f6a7b8c9"
}
Fix: Include all required parameters. See the Quickstart for endpoint-specific requirements.
> Note: the shape above is the target contract. As of this pilot, a few endpoints' "missing required parameter" checks (e.g. /v1/quote, /v1/swap) return a minimal {"error": "chain, from, to, amount are required"} body without code/requestId — do not rely on code being present for this specific case yet; branch on HTTP status 400 and the presence/absence of code defensively.
{
"code": "INVALID_REQUEST",
"message": "Unsupported chain: avocado",
"error": "Unsupported chain: avocado",
"requestId": "a7b8c9d0",
"reqId": "a7b8c9d0"
}
Fix: Use a supported chain: bsc, ethereum, polygon, base, arbitrum, optimism, tron, solana.
{
"code": "INVALID_REQUEST",
"message": "slippageBps must be between 0 and 1000",
"error": "slippageBps must be between 0 and 1000",
"requestId": "b8c9d0e1",
"reqId": "b8c9d0e1"
}
Fix: Set slippageBps to a value between 0 (exact) and 1000 (10%).
---
{
"code": "RATE_LIMITED",
"message": "Rate limited",
"error": "Rate limited",
"requestId": "c9d0e1f2",
"reqId": "c9d0e1f2",
"retryAfterSec": 42,
"usage": "Check /v1/usage or /v1/limits for your current usage stats"
}
usage is a plain hint string pointing you at the usage endpoints, not a structured counters object — call GET /v1/usage or GET /v1/limits with your key to read actual rpm/rpd/rpmo numbers.
Cause: You have exceeded your per-key rate limit (RPM, RPD, or RPMo).
Fix: Wait for retryAfterSec seconds, then retry. The Retry-After response header also contains this value. Upgrade your tier for higher limits.
{
"code": "RATE_LIMITED",
"message": "Too many requests. Get a free API key at https://business.10fi.org for higher limits.",
"error": "Too many requests. Get a free API key at https://business.10fi.org for higher limits.",
"requestId": "d0e1f2a3",
"reqId": "d0e1f2a3",
"hint": "Add header: X-API-Key: <your-key>",
"retryAfterSec": 60
}
Cause: Too many requests from this IP without an API key.
Fix: Obtain an API key for authenticated, higher-rate-limit access.
---
{
"code": "UPSTREAM_ERROR",
"message": "The routing engine returned an error. Try again shortly.",
"error": "The routing engine returned an error. Try again shortly.",
"requestId": "e1f2a3b4",
"reqId": "e1f2a3b4"
}
Cause: An upstream liquidity provider or bridge service returned an error or timed out. The message is scrubbed — internal provider names are never exposed to merchants.
Fix: Retry the same request after a short delay. If the error persists for more than a few minutes, contact 10FI support with the requestId.
> Note: All error messages are sanitized to remove internal provider, route, and mechanism names. The full details are logged server-side only.
---
When no routes are found for the requested swap:
{
"code": "QUOTE_UNAVAILABLE",
"message": "No routes available for the requested swap.",
"error": "No routes available for the requested swap.",
"requestId": "f2a3b4c5",
"reqId": "f2a3b4c5"
}
Cause: No liquidity route exists for the requested token pair, chain, amount, or combination.
Fix:
routePreference value---
Cross-chain transfers are asynchronous. Use /v1/transfer-status to poll:
curl "https://api.10fi.org/v1/transfer-status?txHash=0xYourSourceTxHash&fromChain=bsc&toChain=base" \
-H "X-Client-Id: ci_your_client_id" \
-H "X-API-Key: sk_your_api_key"
Possible transfer states:
| Status | Meaning | Merchant action |
|--------|---------|-----------------|
| PENDING | Source transaction confirmed, bridge is processing | Continue polling (30s intervals recommended) |
| COMPLETED | Destination transaction confirmed | Transfer is done |
| FAILED | Bridge failed to deliver | Check source tx; contact 10FI support with requestId |
| EXPIRED | Bridge window expired | The transfer must be re-initiated |
| NOT_FOUND | Source tx hash not recognized | Verify the tx hash and chain parameters |
---
The Gasless API is a separate product (gasless_api). Unless explicitly enabled for your account by 10FI, gasless endpoints return shadow/mock responses. These responses are clearly marked and do not execute real transactions.
If gasless_api is not enabled:
{
"code": "FORBIDDEN",
"message": "This product is not enabled for this API key.",
"requestId": "a3b4c5d6",
"reqId": "a3b4c5d6"
}
If gasless_api is enabled but the route is not available for gasless execution, the response will include a fallback to a standard on-chain route with a clear indication that gasless is not available for this specific swap.
---
{
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please retry or contact support.",
"error": "An unexpected error occurred. Please retry or contact support.",
"requestId": "b4c5d6e7",
"reqId": "b4c5d6e7"
}
Fix: Retry. If persistent, contact 10FI support with the requestId.
---
Request arrives
|
v
[Missing/invalid X-Client-Id or X-API-Key?] --> 401 UNAUTHORIZED
|
v
[Merchant not approved (KYC gate)?] ---------> 403 FORBIDDEN
|
v
[Rate limit exceeded?] ----------------------> 429 RATE_LIMITED (check retryAfterSec)
|
v
[Product not enabled?] ----------------------> 403 FORBIDDEN
| swap_api: /v1/quote, /v1/swap (same-chain)
| cross_chain_api: /v1/quote, /v1/swap (toChain differs), /v1/transfer-status
| gasless_api: /v1/gasless/*, /v1/submit, /v1/status/:orderId
v
[Malformed chain/toChain?] -----------------> 403 FORBIDDEN (fail-closed to cross_chain_api)
|
v
[Missing/invalid parameter?] ----------------> 400 INVALID_REQUEST
|
v
[No route available?] -----------------------> 400/500 QUOTE_UNAVAILABLE
|
v
[Upstream provider error?] -----------------> 502 UPSTREAM_ERROR (message scrubbed)
|
v
[Unexpected server error?] -----------------> 500 INTERNAL_ERROR
---
When you hit an error and need help, send:
1. The requestId from the error response (e.g., "a1b2c3d4") — this is the fastest way for 10FI to trace your request.
2. The full error JSON — include code, message, and requestId.
3. Your X-Client-Id (the ci_ value) — NOT your X-API-Key.
4. A description of what you were trying to do (e.g., "BSC USDT → WBNB swap for 100 USDT").
Never send your X-API-Key (sk_) to support. The ci_ client ID is sufficient to identify your account.
Error messages are sanitized for your safety — internal provider details are logged server-side only and never included in API responses.