10FI Pilot API Error Catalog & Response Contract

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.

---

Standard Error Response Shape

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

Optional fields

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

---

Error Codes

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

---

Authentication Errors (401)

Missing credentials

{
  "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.

Invalid credentials

{
  "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.

---

Product Entitlement Errors (403)

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.

Example: swap_api-only merchant trying cross-chain

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.

Example: gasless_api disabled

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.

Malformed chain/toChain — fail-closed behavior

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.)

---

Invalid Request Errors (400)

Missing required parameter

{
  "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.

Unsupported chain

{
  "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.

Slippage out of range

{
  "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%).

---

Rate Limit Errors (429)

Per-key rate limit

{
  "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.

Keyless IP rate limit

{
  "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.

---

Upstream / Provider Errors (502)

{
  "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.

---

Route Unavailable

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:

---

Cross-Chain Transfer Status

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 |

---

Gasless API — Shadow/Mock Status

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.

---

Internal Error (500)

{
  "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.

---

Summary: Error Handling Flowchart

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

---

Contact — what to send to 10FI support

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.

See also