Skip to main content

Integrations

API Errors and Retries

What the Pingara Developer API returns when a request fails or is refused, how its request limits and per-resource brakes work, and how to retry a write safely with an Idempotency-Key.

3 min readUpdated October 31, 2026

apierrorsrate-limitsretriesidempotencyapi-reference

This article covers what the Developer API returns when a request fails or is refused, how its rate limits and per-resource brakes work, and how to retry a write safely with an Idempotency-Key. The endpoints themselves are in the Developer API reference, and scopes are in API Keys and Scopes.

Errors

Errors use standard HTTP status codes, and every error body has the same shape:

{
  "error": "Monitor not found",
  "code": "not_found",
  "retryable": false,
  "requestId": "6f0c2a4e-1b7d-4c55-9e2f-3a8d0b1c7e94"
}
FieldMeaning
errorA human-readable sentence. Show it to a person, but don't branch on it, since the wording can change
codeA stable, machine-readable reason, from the table below. Branch on this. Each code always comes with the same HTTP status
retryabletrue when sending the identical request again can succeed without anything changing on your side
requestIdThe id of this request. Quote it if you contact support
detailsPresent only on some codes, with any of field, maxLength, requiredScope, retryAfterSeconds and planLimit. It never repeats a value you sent

Every response from an API operation, success or error, also carries an X-Request-Id header with the same value as requestId. Pingara makes the id itself and ignores any X-Request-Id you send.

CodeStatusRetryableMeaning
invalid_request400NoA malformed query parameter or body
invalid_field400NoOne field is invalid, such as a text field over its length limit. details.field names it, and details.maxLength gives the limit where there is one
invalid_cursor400NoA pagination cursor that isn't one this API returned
invalid_idempotency_key400NoAn Idempotency-Key header that is empty or outside the allowed length or characters
unauthorized401NoMissing, malformed, revoked or expired API key, or, on a read, a key whose creator is no longer a member of the organization
insufficient_scope403NoThe key lacks the scope this endpoint requires, see Key scopes. details.requiredScope names it
scope_not_in_plan403NoThe key holds the scope, but the organization's current plan does not allow it. details.requiredScope names it
role_forbidden403NoThe key's creator doesn't have the role this action needs, such as a viewer's key deleting a monitor, or (on GET /api/v1/audit-logs) a creator who is no longer an owner or admin
key_creator_not_member403NoOn a write, the key's creator is no longer a member of the organization
key_creator_unverified403NoOn a write, the key's creator has not verified their email address
not_found404NoThe resource doesn't exist, or isn't in your organization
conflict409NoThe request conflicts with current state, such as an address the monitor may not point at
plan_limit_reached409NoA limit of your organization's plan, such as its monitor count. details.planLimit names the limit
incident_resolved409NoAcknowledging an incident that is already resolved
resolve_refused_monitor_down409NoResolving an incident while its monitor is still down
resolve_refused_monitor_degraded409NoResolving an incident while its monitor is still degraded
idempotency_in_progress409YesThe original request with this Idempotency-Key is still running
idempotency_response_unavailable409NoThis Idempotency-Key was used and its original request was performed, but its response can't be replayed. Don't retry it
cursor_unavailable409YesCursor pagination on this endpoint is briefly unavailable. Retry without the cursor
idempotency_key_reused422NoThis Idempotency-Key was used with a different request
rate_limited429YesA request rate limit, see Rate limiting
rate_brake429YesA per-resource brake, see Per-resource brakes
internal500On a GET onlyAn unexpected server error
feature_disabled503YesThe feature has been switched off on the platform, such as webhook subscriptions
service_unavailable503YesWrites, or the feature, are temporarily unavailable. Reads are unaffected

A 429 carries details.retryAfterSeconds with the same value as its Retry-After header.

A 500 on a write is never marked retryable, even when you sent an Idempotency-Key: the write may already have taken effect. Check, for example by listing your monitors, before sending it again. A 500 on a GET is safe to retry with backoff.

The Public Status Page API uses the same error shape and the same X-Request-Id header.

Rate limiting

There are two independent limits on the transport, and a request has to clear both. Some write endpoints add a third kind of limit, on the resource itself, see Per-resource brakes below.

LimitApplies toCeiling
Per key, Free planEach API key in an organization on Free60 requests per minute
Per key, Pro planEach API key in an organization on Pro600 requests per minute
Per client IPThe calling IP address, checked before the key is looked up1,200 requests per minute

The per-key limit follows your organization's plan. Organizations with a Lifetime or Comped plan get the Pro limit. Reads and writes share one per-key budget, so a key that polls heavily has less room to write. The limit is read from your plan on every request: upgrade, and the next request gets 600; downgrade, and the next request gets 60. A Free key that makes more than 60 requests in a minute gets 429 responses until its oldest requests are more than 60 seconds old.

The per-IP limit exists to make guessing at API keys expensive. It is normally invisible, but it does bind on shared infrastructure: a CI runner, a NAT gateway, or one host driving several keys can reach 1,200 while no individual key is anywhere near its own limit.

Rate-limit headers

Every response to a successfully authenticated request carries the key's current budget, not only a 429:

HeaderMeaning
X-RateLimit-LimitThe per-key ceiling for your plan: 60 on Free, 600 on Pro
X-RateLimit-RemainingRequests left in the current window. This is what the next request will actually find. The request you just made is already counted

For example, on a Pro key:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 587

Three things to know about them.

They describe the per-key limit only. There is no header for the per-IP limit, deliberately. Publishing the remaining budget of a limit whose purpose is to slow down callers who don't hold a valid key would tell an attacker exactly how much room they have left.

They appear only once a key has authenticated. A 401 carries none, and neither does a 429 raised by the per-IP limit. Read together with X-RateLimit-Remaining, that tells you which 429 you hit:

  • No X-RateLimit-* headers at all - the per-IP limit. Look at what else is calling from the same address, not at your own request rate.
  • X-RateLimit-Remaining: 0 - your key's own per-minute limit for your plan (60 on Free, 600 on Pro).
  • X-RateLimit-* present with budget still on it - a per-resource brake. Your request rate is fine; the particular thing you were creating is what is capped, and Retry-After can be far longer than a minute.

There is no X-RateLimit-Reset. Pingara's limiter is a sliding window: each request expires 60 seconds after it was made, individually, so there is no single moment at which your quota "resets" and no honest value to put in that header. Use Retry-After instead.

Per-resource brakes

A few write endpoints carry a further limit of their own, on the resource rather than on the transport. Today there is exactly one: creating monitors is capped at 100 per hour, per organization, and separately at 300 per hour per human across every organization they belong to. Since an API key belongs to exactly one organization, the org limit, 100/hour, is the one a single key can actually reach. It exists to bound how fast rows can accumulate, not to pace your requests. In practice it's rarely the binding constraint: every create through this API pays your plan's monitor-count check the same way the UI does (1 on Free, 50 on Pro), so a Free-plan key can never get anywhere near 100 in the first place, and a Pro-plan key hitting 100 creates in an hour needs roughly 50 deletions inside that same hour to keep making room. The plan cap bounds how many monitors you can have; this brake bounds how fast you can create and delete them.

A request refused by one of these is also a 429, and it is still safe to retry. The identical request succeeds once the window rolls, with nothing for you to change. Two things make it different from the limits above: your key's own budget is usually untouched, so X-RateLimit-Remaining still shows room; and the wait is measured in minutes or hours rather than seconds. Read Retry-After for the wait and the message in the body for what was capped.

Do not confuse this with a 409. A 409 from a write endpoint is a refusal that will not clear on its own, such as a plan limit or a resource whose current state forbids the change, and retrying it unchanged will be refused again.

Retry-After

Every 429 carries Retry-After, in seconds, and it is computed rather than fixed.

For the two transport limits it is the time until your oldest counted request falls out of the sliding window, which is the earliest moment a slot actually frees, and it is never more than 60. Waiting that long and retrying once is the correct behavior; a fixed sleep of a full minute is safe but usually longer than necessary.

For a per-resource brake it is that brake's own window instead, so it can be up to an hour. Treat it the same way, since it is still the answer to "when may I retry", but do not assume a ceiling of 60 seconds.

Build clients that respect Retry-After and back off further if a retry is also rejected.

Idempotency-Key

Every write endpoint accepts an optional Idempotency-Key header: 8–255 characters from A-Za-z0-9._:+/=- (UUIDs, ULIDs, nanoids, and base64/base64url all fit). Send the same key on a retry of the same request and you get back the original response rather than a second write, for up to one hour.

curl -X POST https://api.pingara.io/api/v1/monitors \
  -H "Authorization: Bearer $PINGARA_API_KEY" \
  -H "Idempotency-Key: create-marketing-site-2026-05-14" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Marketing site", "url": "https://www.example.com", "method": "GET", "environment": "production", "keywordCheckEnabled": false, "followRedirects": true, "maxRedirects": 5 }'

Creating a monitor is the genuinely unsafe write to retry without one. A network failure after Pingara processed your request but before you saw the response leaves you not knowing whether it succeeded, and blindly retrying a bare POST /monitors makes a second monitor, silently, with no error to tell you it happened. On the other writes an unkeyed retry is comparatively cheap: acknowledging an incident twice is a no-op (see POST /api/v1/incidents/{id}/acknowledge), and pausing, resuming, updating, or deleting something that's already in that state is either a no-op or a 404/409 you'll notice. If a create returns a 5xx, don't retry blind, even with an idempotency key: the key is released when a write fails, so a retry can make a second monitor (see Errors). List your monitors and check whether the one you meant to create is already there before sending it again.

Next steps

Related Articles

Integrations32 min

Developer API

Create and manage monitors, acknowledge incidents, and read uptime, checks, status page state, and the audit log from Pingara programmatically using the v1 REST Developer API.

Integrations6 min

API Keys and Scopes

Create a Pingara Developer API key, understand its scopes and its expiry, rotate it without an outage, and understand why a request returns 401 or 403.