Errors

Every Triport error — REST or JSON-RPC — uses one predictable JSON envelope with a machine-readable error code, a human message, and a request_id you can quote in support tickets.

Network
Solana | Ethereum | Polygon | Tron | BSC | Stellar | TON
Authentication
x-token header (preferred) · Authorization: Bearer · ?api-key query · path-form

Description

Triport returns a consistent error shape across all surfaces. The HTTP status line tells you the broad class of problem, the error field gives you a stable machine-readable code to branch on, and the message is a human-readable explanation safe to log. Some errors carry extra fields (for example, which tier you'd need to upgrade to, or how many seconds to wait before retrying).

Branch your client logic on the error code, not on the message string — messages may be reworded over time, while codes are part of the contract. When you contact support, always include the request_id: it lets us correlate your call with our server-side logs.

Authentication, tier, and rate-limit rejections are decided before the request reaches the RPC layer, so they are plain HTTP errors using the envelope above — not JSON-RPC error objects, on any surface including /rpc/bsc. The JSON-RPC error objects you do receive come either from the upstream node or from the two batch-level checks Triport performs itself; both are listed in JSON-RPC wire codes below.

The error envelope

Every REST error response has this shape:

{
  "error": "tier_insufficient",
  "message": "Method 'getAssetsByOwner' requires basic tier or higher",
  "request_id": "req_9f3c1a8e2b6d4f10"
}

Envelope fields

FieldTypeDescription
errorstringMachine-readable error code. One of the values in the error code table. Branch on this.
messagestringHuman-readable explanation. Safe to log; do not parse.
request_idstringServer-side correlation id. Quote it in support tickets. May be absent on errors generated before a request is fully routed.

Specific error codes add extra top-level fields on top of this base envelope — see the per-code rows below.

Error code table

error codeHTTP statusExtra fieldsWhen it happens
unauthorized401—No credentials supplied, or the key/token is invalid or revoked.
trial_expired401expired_at, upgrade_urlThe free 7-day trial period has ended. Upgrade to a paid tier to continue.
subscription_expired401expired_at, renew_urlA previously active paid subscription has lapsed. Renew to restore access.
tier_insufficient403current_tier, required_tier, method, category, upgrade_urlYour tier is below the minimum required for this method. Also sets the X-Required-Tier response header.
method_unknown403chain, methodThe method is not part of the product on this chain.
rate_limited429retry_after_sec, limit_rps, burst_capacity, category, current_tierSustained RPS for your tier and the request's category was exceeded. Sets Retry-After and X-RateLimit-* headers.
invalid_params400—The request was malformed: bad/missing parameters or an unparseable body.
internal_error500—An unexpected server-side fault. Safe to retry with backoff; quote request_id if it persists.

Note: there is no daily quota and no daily-quota error. Rate limiting is sustained-RPS-per-tier with a burst allowance — see Rate limits.

Extra-field reference

tier_insufficient (403) — adds:

FieldTypeDescription
current_tierstringYour key's tier — one of free, basic, pro, business, enterprise.
required_tierstringMinimum tier needed for this method.
methodstringThe method that was gated.
categorystringThe tier-matrix category bucket that gates this method.
upgrade_urlstring (uri)Link to upgrade your subscription.

Also returned as the X-Required-Tier response header.

rate_limited (429) — adds:

FieldTypeDescription
current_tierstringTier of the API key that triggered the limit.
retry_after_secinteger (≥1)Seconds to wait before retrying. Mirrors the Retry-After header.
limit_rpsinteger (≥1)Sustained RPS allowed for your tier and category.
burst_capacityintegerShort burst ceiling. Equals 2 × limit_rps (global burst multiplier).
categorystringThe tier-matrix category the offending request belongs to.

Also accompanied by the response headers below.

trial_expired (401) — adds expired_at (date-time) and upgrade_url (uri).

subscription_expired (401) — adds expired_at (date-time) and renew_url (uri).

method_unknown (403) — adds:

FieldTypeDescription
chainstringChain the method was attempted on — sol, eth, polygon, tron, or bsc.
methodstringThe method name that was not recognised.

Response headers

On 429 rate_limited, Triport sets:

HeaderDescription
Retry-AfterSeconds to wait before retrying.
X-RateLimit-LimitTotal allowed RPS for the matching tier + category.
X-RateLimit-RemainingRemaining capacity in the current 1-second window.
X-RateLimit-ResetUNIX timestamp when the window resets.
X-RateLimit-CategoryTier-matrix category bucket charged for this request.

On 403 tier_insufficient, Triport sets:

HeaderDescription
X-Required-TierMinimum tier required for the method.

Examples

tier_insufficient — 403 envelope

{
  "error": "tier_insufficient",
  "message": "Method 'getAssetsByOwner' requires basic tier or higher",
  "request_id": "req_9f3c1a8e2b6d4f10",
  "current_tier": "free",
  "required_tier": "basic",
  "method": "getAssetsByOwner",
  "category": "sol_das",
  "upgrade_url": "https://triport.io/upgrade/basic"
}

rate_limited — 429 envelope

{
  "error": "rate_limited",
  "message": "Rate limit reached: your free plan allows 20 requests/s for sol_read_rpc. Retry in 1s (see the Retry-After header) or upgrade for a higher limit at https://triport.io/app/pricing",
  "current_tier": "free",
  "category": "sol_read_rpc",
  "limit_rps": 20,
  "burst_capacity": 40,
  "retry_after_sec": 1,
  "upgrade_url": "https://triport.io/app/pricing",
  "docs_url": "https://triport.io/errors/http-429-rate-limit"
}

JSON-RPC wire codes

Triport's product errors — unauthorized, trial_expired, subscription_expired, tier_insufficient, method_unknown, rate_limited — are not returned as JSON-RPC error objects. The gate decides them before the request reaches the RPC layer, so they arrive as the plain HTTP envelope documented above, with the status from the error code table. Branch on the HTTP status and the error field; there is no numeric wire code for them to branch on.

Triport emits exactly two JSON-RPC error codes of its own, both for a batch body it cannot process:

JSON-RPC codeMeaningHTTP statusWhen it happens
-32700Parse error400The body opened as a batch array ([) but is not valid JSON.
-32600Invalid Request200An empty batch []. Per JSON-RPC 2.0 §6 this is answered with a single error object, and the request itself arrived intact — hence HTTP 200.

For a batch that parses, responses can arrive in any order and each item succeeds or fails on its own — see JSON-RPC batches: order, ids and partial errors.

Every other JSON-RPC code you see is the upstream node's own and is passed through unchanged — most commonly -32602 (invalid params) and -32601 (method not found). The chain-specific boundaries below cover the upstream codes worth special handling.

BSC upstream -32000

BSC nodes also use upstream code -32000 for more than one condition. A message containing header not found means that the requested block is not available, while pruned/unsupported-state wording means historical state is unavailable on the selected node. Branch on the pair code + message for this upstream error. It is distinct from Triport's stable product codes above.

Trace/debug on BSC is best-effort: its only public source has a shared quota and no SLA. A quota response must not be rewritten as method_unknown.

Stellar retention and Horizon page limit

Stellar uses an explicit boundary outcome for history outside the selected node's current window. Horizon returns HTTP 410 Gone with beyond_retention and oldest=N; Soroban returns JSON-RPC -32602 with beyond_retention and oldest N. These responses describe the retained window and are not internal failures.

Horizon rejects a page size above the measured maximum with limit must not exceed 200. Retry only after reducing limit to 200 or less.

TON retention and unavailable writes

TON returns JSON-RPC -32602 with beyond_retention and oldest N when a historical seqno is below the selected liteserver's numeric floor. Upstream texts such as not in db, block not in db, and state not in db are treated as this boundary for old requests, not as empty data. sendBoc is outside the read-only TON catalog, so the gate rejects it as an unclassified method with HTTP 403 method_unknown — it never reaches an upstream node.

method_unknown — 403 envelope

{
  "error": "method_unknown",
  "message": "Method \"bogus_nonexistent_method_42\" on chain \"polygon\" is not recognised",
  "chain": "polygon",
  "method": "bogus_nonexistent_method_42"
}

The tier_insufficient and rate_limited bodies are shown under Examples above. All three are plain HTTP responses: there is no JSON-RPC envelope and no error.data — the extra fields sit at the top level of the body, and the HTTP status carries the class.

Handling errors

JavaScript (fetch)

const res = await fetch("https://triport.io/sol", {
  method: "POST",
  headers: {
    "x-token": process.env.TRIPORT_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    jsonrpc: "2.0",
    id: 1,
    method: "getBalance",
    params: ["So11111111111111111111111111111111111111112"],
  }),
});


const body = await res.json();


if (!res.ok) {
  // Product errors are plain HTTP responses, not JSON-RPC error objects:
  // branch on the `error` field, with the extra fields at the top level.
  switch (body.error) {
    case "rate_limited": {
      const wait = Number(res.headers.get("Retry-After") ?? body.retry_after_sec);
      await new Promise((r) => setTimeout(r, wait * 1000));
      // ...then retry
      break;
    }
    case "tier_insufficient":
      console.error("Upgrade required:", body.upgrade_url);
      break;
    case "method_unknown":
      console.error("Unknown method:", body.method);
      break;
    default:
      throw new Error(body.message ?? `HTTP ${res.status}`);
  }
} else if (body.error) {
  // A JSON-RPC error object from the upstream node, e.g. -32602 invalid params.
  throw new Error(`${body.error.code}: ${body.error.message}`);
}

Notes

  • Branch on codes, not messages. The error code (REST) and JSON-RPC numeric code are stable; message text may change.
  • Respect Retry-After. On 429, wait at least retry_after_sec seconds (or read the Retry-After header) before retrying. burst_capacity is 2 × limit_rps. See Rate limits for the full model.
  • 401 vs 403. A 401 (unauthorized, trial_expired, subscription_expired) is about who you are; a 403 (tier_insufficient, method_unknown) is about what your tier may call. See Authentication for credential setup.
  • internal_error (500) is the only error safe to blindly retry with exponential backoff. If it persists, open a support ticket with the request_id.
  • See also: Getting started · Authentication · Rate limits.
IDNetworkRequired scopeErrors
solSolanasolana:rpcErrors
ethEthereumeth:rpcErrors
polygonPolygonpolygon:rpcErrors
bscBNB Smart Chainbsc:rpcErrors
baseBasebase:rpcErrors
tronTrontron:rpcErrors
robinhoodRobinhood Chainrobinhood:rpcErrors
stellarStellarstellar:rpcErrors
tonTONton:rpcErrors