Errors
Every error is an RFC 9457 problem document with the content type application/problem+json:
{ "type": "https://status302.com/problems/plan-required", "title": "Plan upgrade required", "status": 402, "detail": "% change filters requires plan Elite (you are on Pro)", "requiredPlan": "elite", "currentPlan": "pro", "feature": "percent_change_filters"}typeidentifies the kind of problem. Branch ontypeandstatus, never on the wording oftitleordetail.titleis a short, human summary of the type;detailexplains this occurrence and is safe to log or show to an operator.- Some types add fields (see problem types).
Errors without a more specific type use about:blank, whose meaning is the HTTP status itself, for example a 404 for an event id that
doesn’t exist or a 400 for an invalid parameter. Validation errors name the offending parameter in detail.
#!/usr/bin/env bash# Errors are RFC 9457 problem+json: a `type` URI to branch on, a human `title`,# the HTTP `status`, and a `detail` for this occurrence.set -euo pipefailAPI="${STATUS302_API_URL:-https://api.status302.com}"
curl -sS -w '\nHTTP %{http_code} %{content_type}\n' "$API/v1/events/999999999" \ -H "Authorization: Bearer $STATUS302_API_KEY"// Every non-2xx answer becomes a typed error carrying the problem+json body.import { AuthenticationError, NotFoundError, PermissionError, PlanRequiredError, RateLimitError, Status302,} from "@status302/sdk";
const status302 = new Status302({ apiKey: process.env.STATUS302_API_KEY, baseUrl: process.env.STATUS302_API_URL });
try { await status302.get("/v1/events/{id}", { path: { id: 999_999_999 } });} catch (err) { if (err instanceof NotFoundError) { console.log(`404 ${err.type}: ${err.problem.detail ?? err.problem.title}`); } else if (err instanceof PlanRequiredError) { console.log(`needs the ${err.requiredPlan} plan (you have ${err.currentPlan}) for ${err.feature}`); } else if (err instanceof PermissionError) { console.log(`forbidden: ${err.requiredScope ? `the key needs the ${err.requiredScope} scope` : err.message}`); } else if (err instanceof RateLimitError) { console.log(`quota exhausted, retry in ${err.retryAfter}s`); } else if (err instanceof AuthenticationError) { console.log("the API key is missing, revoked or expired"); } else { throw err; }}# Every non-2xx answer becomes a typed error carrying the problem+json body.import os
from status302 import ( AuthenticationError, NotFoundError, PermissionDeniedError, PlanRequiredError, RateLimitError, Status302,)
with Status302(os.environ["STATUS302_API_KEY"], base_url=os.environ.get("STATUS302_API_URL", "https://api.status302.com")) as status302: try: status302.get_event(999_999_999) except NotFoundError as err: print(f"404 {err.type}: {err.problem.get('detail') or err.problem['title']}") except PlanRequiredError as err: print(f"needs the {err.required_plan} plan (you have {err.current_plan}) for {err.feature}") except PermissionDeniedError as err: print(f"forbidden: the key needs the {err.required_scope} scope" if err.required_scope else f"forbidden: {err}") except RateLimitError as err: print(f"quota exhausted, retry in {err.retry_after}s") except AuthenticationError: print("the API key is missing, revoked or expired")The SDKs raise one error class per status (NotFoundError, PlanRequiredError, RateLimitError, …) carrying the parsed problem.
Problem types
Section titled “Problem types”Status302’s own problem types are URIs under https://status302.com/problems/; the table names them by their last segment, so plan-required
stands for https://status302.com/problems/plan-required. Your code should match the whole URI.
type |
Status | Extra fields | Meaning |
|---|---|---|---|
invalid-api-key |
401 | The key is malformed, unknown, revoked, expired or past its rotation grace. See authentication. | |
insufficient-scope |
403 | requiredScope |
The key lacks the scope this operation needs. |
ip-not-allowed |
403 | The request came from outside the key’s IP allowlist. | |
plan-required |
402 | requiredPlan, currentPlan, feature, limit |
The organization’s plan doesn’t include this, or a count limit is reached. |
rate-limited |
429 | limit |
A quota is exhausted; retry after Retry-After. See rate limits. |
about:blank |
any | The HTTP status says it all: 400 invalid input, 403 not allowed, 404 not found, 5xx our side. |
Plan required
Section titled “Plan required”A 402 names the cheapest plan that allows the request (requiredPlan), the organization’s plan (currentPlan, free without one) and
the gated capability (feature). For count limits (lists, list items, alert rules, compare, export rows) limit is the current plan’s
limit that was reached. feature is one of:
feature |
Gate |
|---|---|
market_data |
Metrics, series, sales, the feed, movers, compare, inventory and overlays |
secondary_sources |
Secondary marketplace data |
sales_history, series_history |
How far back sales and charts go |
ended_events |
Market history of events that are over |
percent_change_filters |
minChg24h / maxChg24h catalog filters |
map_prices |
Price layers of map overlays |
compare |
Events per compare |
csv_export |
List CSV export and async exports |
lists, list_items |
Saved lists and events per list |
alert_rules |
Alert rules |
api |
API access (keys and the stream) |
firehose |
The sales and onsales stream topics |
webhooks |
Webhook endpoints |
History windows don’t always fail: reads that start before your plan’s window are clamped to it when part of the range is visible, and
answer 402 only when the whole range is outside it. See entitlements by plan.
Retrying
Section titled “Retrying”| Status | Retry? |
|---|---|
400, 401, 402, 403, 404 |
No: fix the request, the key or the plan first. |
429 |
Yes, after Retry-After seconds. |
500, 502, 503, 504 |
Yes, with exponential backoff, for reads and idempotent writes (GET, PUT, DELETE). |
Include the response’s X-Request-Id header when you report an error to support.
Statuses
Section titled “Statuses”Generated from the 39 paths of the public spec, version 1.1.0.
| Status | Operations | Documented causes |
|---|---|---|
400 | 30 |
|
401 | 50 |
|
402 | 18 |
|
403 | 50 |
|
404 | 34 |
|
429 | 50 |
|
503 | 1 |
|
Schemas
Section titled “Schemas”Problem
RFC 9457 problem details.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | |
title | string | yes | |
status | integer | yes | |
detail | string | no | |
instance | string | no |
PlanRequiredProblem
A 402 problem naming the plan that allows the request.
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | |
title | string | yes | |
status | integer | yes | |
detail | string | no | |
instance | string | no | |
requiredPlan | PlanId | yes | |
currentPlan | PlanId | yes | |
feature | string | yes | The gated capability, e.g. `market_data`, `percent_change_filters`, `alert_rules`. |
limit | integer | no | The exhausted count limit of the current plan (count features only). |
RateLimitedProblem
| Field | Type | Required | Description |
|---|---|---|---|
type | string | yes | |
title | string | yes | |
status | integer | yes | |
detail | string | no | |
instance | string | no | |
limit | "minute" | "day" | "concurrent" | "stream_connections" | "user" | "ip" | "stream_tickets" | yes | The exhausted limit. |

