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.
{ "error": "Rate limit exceeded. Maximum 10 pings per minute." }Status codes
| Field | Type | Description |
|---|---|---|
200 | OK | Request succeeded. Inspect the response body for the structured result. |
400 | Bad request | Validation failed — required field missing, invalid enum, or payload size exceeded. |
401 | Unauthorized | Missing or invalid X-Webhook-Signature. |
403 | Forbidden | Team subscription is inactive or expired. |
404 | Not found | Unknown ping URL or webhook URL, or the resource is paused/disabled. |
429 | Too many requests | Rate limit or monthly usage quota exceeded. The body explains which. |
5xx | Server error | Unexpected 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.
Quota checks return allowed when our analytics backend is briefly unreachable — by design. A transient outage on our side should never drop your pings or alerts on the floor.
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".
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;
}