Skip to main content

Errors & limits

Every refusal has the same shape and a code a program can act on. Here is each code, what each plan allows, and the headers that say where you are.

The shape of an error​

Every refusal is JSON with an error: a code for your program and a message for a person. Some add a field that says more, such as the source that is locked or the fields of a 422.

  • errorErrorRequired
    2 fields
    • error.codestringRequired

      What was refused, for a program: source_locked, not_found, too_many_calls... A code keeps its meaning.

    • error.messagestringRequired

      The same, for a person.

A refusal
{
"error": {
"code": "bad_key",
"message": "That API key is not valid."
}
}

Error codes​

StatuscodeWhen
400bad_requestThe request asks for two things that cannot go together, such as after and before.
400unknown_event_typeA type that is not one of the ten kinds.
400currency_neededmin_price or max_price without currency: prices are never compared across currencies.
400no_webhookA test send for an alert that has no webhook address.
401not_signed_inNo key was sent.
401bad_keyThe key is wrong, revoked, or its account is blocked.
402alert_limitThe account has as many alerts as its plan allows. A larger plan allows more; at 20 it is a 400.
402source_lockedA source your plan does not open. The error names it in source.
403account_blockedThe account was blocked.
404not_foundNo such product, alert or delivery for this account.
422invalid_requestA parameter or field is the wrong type or out of range. The message names it.
429too_many_callsToday's calls are used up. Retry-After says how long until midnight UTC.
429too_many_testsA test send less than 5 seconds after the last one.
503unavailableA part of the service is not running. Try again later.

What to do​

  • 4xx: the request needs changing. Trying it again unchanged gives the same answer, except 429, which clears when the day turns.
  • 5xx: the problem is ours. Wait and try again, a little longer each time.
  • 402: a store (source) your plan does not open. Choose it on your Stores page if your plan has room, or see the plans.
Handling errors
import time, requests

def get(url, key, **params):
for attempt in range(5):
r = requests.get(url, params=params, timeout=30,
headers={"Authorization": f"Bearer {key}"})
if r.status_code == 429: # today's calls are used
raise RuntimeError(r.json()["error"]["message"])
if r.status_code >= 500: # ours: try again shortly
time.sleep(2 ** attempt)
continue
if not r.ok: # yours: fix the request
error = r.json()["error"]
raise ValueError(f'{error["code"]}: {error["message"]}')
return r.json()
raise RuntimeError("CarrySpec is not answering; try again later")

Daily limits and headers​

Calls made with a key count against your plan's calls a day. The count starts again at midnight UTC. While a limit applies, every answer to a key says where you are:

HeaderWhat it says
X-RateLimit-LimitCalls the plan allows a day.
X-RateLimit-RemainingCalls left today.
X-RateLimit-ResetWhen the count starts again, in Unix seconds (midnight UTC).
Retry-AfterSeconds until the count starts again. Only on a 429.
Response headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4987
X-RateLimit-Reset: 1793145600
note

Past the limit, calls are answered 429 too_many_calls until the day turns. GET /api/v1/me says your limits and api_calls_today.

What each plan allows​

PlanStoresAlertsAPI calls a day
Free1 (included)3200
Starter5 you choose + 1 included205,000
ProEvery source2050,000

Every feature is on every plan. Only calls made with a key are counted.

Other limits​

WhatLimit
Working keys per account5
Products per page (page_size)100 (50 by default)
Events per answer (limit)500 (50 by default)
Test sendsOne every 5 seconds
Answering a webhook20 seconds
Webhook timestamp tolerance (on your side)5 minutes