Errors & rate limits

What the API returns when things go wrong, and what to expect under throttling or backend pressure.

Error envelope

All 4xx and 5xx responses share the same shape: a JSON body with a single error field.

json
{ "error": "Rate limit exceeded. Maximum 10 pings per minute." }

Status codes

FieldTypeDescription
200OKRequest succeeded — a heartbeat ping, or an alert filtered by rules. Inspect the response body for the structured result.
201CreatedThe trigger endpoint created an alert, reopened one, or added an occurrence.
400Bad requestValidation failed — required field missing, invalid enum, or payload size exceeded.
401UnauthorizedMissing or invalid X-Webhook-Signature.
403ForbiddenTeam subscription is inactive or expired.
404Not foundUnknown ping URL or webhook URL, or the resource is paused/disabled.
429Too many requestsRate limit or monthly usage quota exceeded. The body explains which.
5xxServer errorUnexpected failure on our end. Retry with backoff; if persistent, contact support.

Rate limits

  • Pings — 10 requests per minute per ping URL. Bursting above that returns 429 for the rest of the minute.
  • Alerts — limited primarily by the webhook's configured rate cap and the team monthly quota. Identical alerts are deduplicated server-side, so high-volume failure modes don't immediately hit the limit.

Monthly quotas

Each plan includes a monthly cap on heartbeats and alerts. When the cap is exhausted, further requests return 429 with a reason like "Monthly heartbeat quota exceeded". Usage resets at the start of each billing period; usage and cap are visible in Settings → Usage.

Retry guidance

  • 401 / 403 / 404 — do not retry. The request is fundamentally rejected; fix the cause.
  • 429 — retry with exponential backoff. The SDKs do not retry automatically, so add retry logic if your workload warrants it.
  • 5xx — retry with exponential backoff, capped at a few attempts. Beyond that, surface the failure to your logs.

Errors from the SDKs

Both SDKs surface failures as OpShiftError with statusCode / status_code. Network failures (DNS, connection refused, timeout) surface with code 0 so you can distinguish "couldn't reach the API" from "the API said no".

typescript
try {
  await opshift.alert("deploy-failures", { title: "..." });
} catch (error) {
  if (error instanceof OpShiftError) {
    if (error.statusCode === 0) {
      // network-level failure: queue locally, retry later
    } else if (error.statusCode === 429) {
      // back off
    } else {
      // unrecoverable: surface to your log pipeline
    }
  }
  throw error;
}