Errors
Every Triport error — REST or JSON-RPC — uses one predictable JSON envelope with a machine-readable
errorcode, a humanmessage, and arequest_idyou can quote in support tickets.
| Method / Endpoint | n/a — error reference |
| Network | Solana | Ethereum | Polygon |
| Authentication | Bearer header / x-token header / ?api-key query / path-form |
| Required scope | — |
| Tier / rate limit | — |
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.
JSON-RPC requests (the /sol, /eth, /polygon RPC surfaces) return errors in
the standard JSON-RPC error object instead, with a numeric code, a
message, and a data payload carrying the same extra fields. The mapping
between HTTP error codes and JSON-RPC wire codes is given 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
| Field | Type | Description |
|---|---|---|
error | string | Machine-readable error code. One of the values in the error code table. Branch on this. |
message | string | Human-readable explanation. Safe to log; do not parse. |
request_id | string | Server-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 code | HTTP status | Extra fields | When it happens |
|---|---|---|---|
unauthorized | 401 | — | No credentials supplied, or the key/token is invalid or revoked. |
trial_expired | 401 | expired_at, upgrade_url | The free 7-day trial period has ended. Upgrade to a paid tier to continue. |
subscription_expired | 401 | expired_at, renew_url | A previously active paid subscription has lapsed. Renew to restore access. |
tier_insufficient | 403 | current_tier, required_tier, method, category, upgrade_url | Your tier is below the minimum required for this method. Also sets the X-Required-Tier response header. |
method_unknown | 403 | chain, method | The method is not part of the product on this chain. |
rate_limited | 429 | retry_after_sec, limit_rps, burst_capacity, category, current_tier | Sustained RPS for your tier and the request's category was exceeded. Sets Retry-After and X-RateLimit-* headers. |
invalid_params | 400 | — | The request was malformed: bad/missing parameters or an unparseable body. |
internal_error | 500 | — | 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:
| Field | Type | Description |
|---|---|---|
current_tier | string | Your key's tier — one of free, basic, pro, business, enterprise. |
required_tier | string | Minimum tier needed for this method. |
method | string | The method that was gated. |
category | string | The tier-matrix category bucket that gates this method. |
upgrade_url | string (uri) | Link to upgrade your subscription. |
Also returned as the X-Required-Tier response header.
rate_limited (429) — adds:
| Field | Type | Description |
|---|---|---|
current_tier | string | Tier of the API key that triggered the limit. |
retry_after_sec | integer (≥1) | Seconds to wait before retrying. Mirrors the Retry-After header. |
limit_rps | integer (≥1) | Sustained RPS allowed for your tier and category. |
burst_capacity | integer | Short burst ceiling. Equals 2 × limit_rps (global burst multiplier). |
category | string | The 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:
| Field | Type | Description |
|---|---|---|
chain | string | Chain the method was attempted on — sol, eth, or polygon. |
method | string | The method name that was not recognised. |
Response headers
On 429 rate_limited, Triport sets:
| Header | Description |
|---|---|
Retry-After | Seconds to wait before retrying. |
X-RateLimit-Limit | Total allowed RPS for the matching tier + category. |
X-RateLimit-Remaining | Remaining capacity in the current 1-second window. |
X-RateLimit-Reset | UNIX timestamp when the window resets. |
X-RateLimit-Category | Tier-matrix category bucket charged for this request. |
On 403 tier_insufficient, Triport sets:
| Header | Description |
|---|---|
X-Required-Tier | Minimum tier required for the method. |
Examples
tier_insufficient — 401/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 exceeded: 20 RPS sustained on sol_read_rpc (free tier)",
"request_id": "req_2c7b0e44a1f94d83",
"current_tier": "free",
"category": "sol_read_rpc",
"limit_rps": 20,
"burst_capacity": 40,
"retry_after_sec": 1
}JSON-RPC wire codes
When you call an RPC method, errors arrive inside the standard JSON-RPC error
object. The numeric code maps to a Triport error code as follows; the extra
fields move into error.data.
JSON-RPC code | Triport code | HTTP status |
|---|---|---|
-32002 | tier_insufficient | 403 |
-32003 | rate_limited | 429 |
-32601 | method_unknown | 403 |
-32601is the standard JSON-RPC "method not found" code, so generic JSON-RPC clients recognise it without special handling.
tier_insufficient (-32002)
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32002,
"message": "Method 'getAssetsByOwner' requires basic tier or higher",
"data": {
"current_tier": "free",
"required_tier": "basic",
"method": "getAssetsByOwner",
"category": "sol_das",
"upgrade_url": "https://triport.io/upgrade/basic"
}
}
}rate_limited (-32003)
Returned with HTTP status 429 and a Retry-After: 1 header.
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32003,
"message": "Rate limit exceeded: 20 RPS sustained on sol_read_rpc (free tier)",
"data": {
"current_tier": "free",
"category": "sol_read_rpc",
"limit_rps": 20,
"burst_capacity": 40,
"retry_after_sec": 1
}
}
}method_unknown (-32601)
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Method \"bogus_nonexistent_method_42\" on chain \"polygon\" is not recognised",
"data": {
"chain": "polygon",
"method": "bogus_nonexistent_method_42"
}
}
}Handling errors
JavaScript (fetch)
const res = await fetch("https://api.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 (body.error) {
// JSON-RPC error object
switch (body.error.code) {
case -32003: {
const wait = Number(res.headers.get("Retry-After") ?? body.error.data.retry_after_sec);
await new Promise((r) => setTimeout(r, wait * 1000));
// ...then retry
break;
}
case -32002:
console.error("Upgrade required:", body.error.data.upgrade_url);
break;
case -32601:
console.error("Unknown method:", body.error.data.method);
break;
default:
throw new Error(body.error.message);
}
}TypeScript SDK (@triport/sdk)
import { Triport, TierInsufficientError, RateLimitedError } from "@triport/sdk";
const client = new Triport({ token: process.env.TRIPORT_API_KEY! });
try {
await client.sol.getAssetsByOwner("So11111111111111111111111111111111111111112");
} catch (err) {
if (err instanceof RateLimitedError) {
await new Promise((r) => setTimeout(r, err.retryAfterSec * 1000));
// ...retry
} else if (err instanceof TierInsufficientError) {
console.error(`Need ${err.requiredTier}; upgrade at ${err.upgradeUrl}`);
} else {
throw err;
}
}Python (triport-sdk)
import time
from triport import Triport
from triport.errors import RateLimitedError, TierInsufficientError
client = Triport(token=os.environ["TRIPORT_API_KEY"])
try:
client.sol.get_assets_by_owner("So11111111111111111111111111111111111111112")
except RateLimitedError as e:
time.sleep(e.retry_after_sec)
# ...retry
except TierInsufficientError as e:
print(f"Need {e.required_tier}; upgrade at {e.upgrade_url}")Notes
- Branch on codes, not messages. The
errorcode (REST) and JSON-RPC numericcodeare stable;messagetext may change. - Respect
Retry-After. On429, wait at leastretry_after_secseconds (or read theRetry-Afterheader) before retrying.burst_capacityis2 × limit_rps. See Rate limits for the full model. 401vs403. A401(unauthorized,trial_expired,subscription_expired) is about who you are; a403(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 therequest_id.- See also: Getting started · Authentication · Rate limits.