Get StartedDocs

Errors

All non-2xx responses use RFC 9457 problem details JSON, with Content-Type: application/problem+json.

HTTP/1.1 401 Unauthorized
Content-Type: application/problem+json

{
  "type": "https://api.statmuse.com/errors/auth-key-missing",
  "status": 401,
  "title": "API key is required",
  "instance": "/sports/nba/v1/ask",
  "traceId": "550e8400-e29b-41d4-a716-446655440000"
}

Match on the slug

The type URI’s last segment is the slug, a stable identifier clients branch on. The HTTP status code and human-readable title may be refined over time; slugs will not be renamed without bumping the API version.

Match on type (or its slug). Do not match on title, status or detail.

The traceId field

Every problem response includes a traceId on the response body, not as a header. This is the request identifier used in StatMuse’s logs. Include it when opening a support ticket so we can correlate the failure.

Slug taxonomy

Authentication & authorization

Slug Status When
auth-key-missing 401 No x-statmuse-api-key header.
auth-key-invalid 401 Header present but doesn’t match a known key (or is longer than 256 characters).
auth-key-disabled 403 Key was revoked from the dashboard.
account-disabled 403 Account has been disabled. Contact support.
auth-scope-insufficient 403 Rare. Fires only if your key was provisioned without access to this endpoint. Contact us.

Quota & rate limiting

Slug Status When
rate-limited 429 Burst rate limit exceeded. Honor Retry-After and try again.
quota-exceeded 402 Period quota exhausted. See Rate Limits & Quotas.

Over MCP, quota exhaustion doesn’t arrive as this slug at all. A tools/call made over quota returns a tool error instead; see Errors over MCP.

Billing & subscription

These fire when your access is not in an active state. During the beta, access comes with StatMuse+. Manage or renew your membership on statmuse.com to restore it.

Slug Status When
subscription-past-due 402 Payment failed and is being retried. Update your payment method to restore access.
subscription-canceled 402 Subscription was canceled. Re-subscribe to restore access.
subscription-incomplete 402 Initial checkout payment hasn’t completed.
subscription-unpaid 402 Subscription is unpaid after retries. Update your payment method.
subscription-paused 402 Subscription is paused. Resume it to restore access.
channel-not-entitled 402 Your membership is active but does not include this channel (REST vs MCP).

Over MCP the subscription-* slugs and channel-not-entitled arrive as 403 instead of 402; see Errors over MCP.

Request validation & routing

Slug Status When
validation-failed 400 The request failed validation (e.g. a missing or invalid input parameter). Schema failures include per-field errors in the body.
not-understood 400 We couldn’t parse your input into a sports question.
not-supported 400 We parsed the question but it isn’t supported on this domain.
unsupported-version 400 The version in the path (/v{n}) isn’t supported for this domain.
route-not-found 404 The URL didn’t match any known route.
payload-too-large 413 Request body exceeded the size limit.

Server-side

Slug Status When
request-timeout 504 The request exceeded the end-to-end processing deadline. Retry with backoff.
client-closed-request 499 You disconnected before we could respond.
internal-error 500/503/504 Unexpected server error, including upstream faults. Include traceId if you report it.

Errors over MCP

Errors over MCP are handled by your MCP client and your agent, not by code you write. They surface in two layers:

Refusals are problem details, at different statuses. Anything that refuses a request before a tool runs (authentication, rate limiting, subscription state) returns the same problem-details JSON with the same slugs, and slug matching works exactly as above. One divergence: MCP defines no 402, so the subscription-* family and channel-not-entitled arrive as 403. Most clients render a refusal as a failed connection or a generic tool failure rather than showing the body.

A few slugs exist only over MCP:

Slug Status When
auth-token-missing 401 No bearer token. Normal during connect; this is what triggers the client’s sign-in.
auth-token-invalid 401 Token expired or failed verification. The client should re-authenticate.
account-not-provisioned 403 Signed in, but no API account exists. Get StatMuse+ first, then reconnect.

Tool errors are not problem details. A tool call that fails on your own input (an unanswerable stats query, an unknown ID) returns an MCP tool error carrying a plain-text message; your agent sees it directly and typically adjusts and retries on its own. An exhausted quota travels the same channel: a tools/call made over quota returns a tool error carrying the quota numbers, the reset date, and where to upgrade, instead of running the tool. Connecting and listing tools keep working regardless of quota state. How tool errors count against your quota is covered in Rate Limits & Quotas.

Handling errors in TypeScript

Branch on the slug derived from type. Special-case rate-limited and quota-exceeded so you can honor Retry-After; log everything else with traceId for correlation.

type ProblemDetails = {
  type: string;
  status: number;
  title: string;
  detail?: string;
  traceId?: string;
};

const PROBLEM_BASE = "https://api.statmuse.com/errors/";

const slugFromType = (type: string): string =>
  type.startsWith(PROBLEM_BASE) ? type.slice(PROBLEM_BASE.length) : type;

async function ask(input: string): Promise<unknown> {
  const url = new URL("https://api.statmuse.com/sports/nba/v1/ask");
  url.searchParams.set("input", input);

  const res = await fetch(url, {
    headers: { "x-statmuse-api-key": process.env.STATMUSE_API_KEY! },
  });

  if (res.ok) return res.json();

  const problem = (await res.json()) as ProblemDetails;
  const slug = slugFromType(problem.type);

  switch (slug) {
    case "rate-limited":
    case "quota-exceeded": {
      const retryAfter = Number(res.headers.get("Retry-After") ?? "0");
      throw new RetryableError(slug, retryAfter);
    }
    case "not-understood":
    case "not-supported":
      throw new BadQuestionError(slug, problem.detail);
    default:
      console.error("StatMuse API error", { slug, traceId: problem.traceId });
      throw new ApiError(slug, problem.title);
  }
}

The Retry-After header is in seconds. For rate-limited it is typically a small integer; for quota-exceeded it can be days. Decide whether to wait, queue the work for later or surface the failure to the end user.