API for developers
Errors and rate limits
Every status code the API returns, what causes it, and the rate limit you are working within.
Errors return a matching HTTP status and a JSON body with a single error string. There is no error code enum; the status plus the message is the contract.
{ "error": "Package already used" } Status codes
| Status | Meaning | What to do |
|---|---|---|
200 | Success | — |
201 | Campaign created | Returned only by POST /campaigns |
400 | Bad request | A required field is missing, the chain is unknown, or the package group does not match the chain. Nothing was consumed. |
401 | Authentication failed | Key missing, malformed, or revoked. Check the header. |
403 | Account suspended | Contact support. |
404 | Not found | The package does not belong to your account, or the token address does not resolve on that chain. |
405 | Method not allowed | The Allow header lists what the endpoint accepts. |
409 | Conflict | The package was already used, most often by a duplicate request. Nothing was double-spent. |
429 | Rate limit reached | Back off and retry. |
500 | Server error | Retry once. If it persists, send the time and endpoint to support. |
Rate limit
120 requests per minute per account, across all endpoints and all of your keys. Exceeding it returns:
{ "error": "Rate limit reached, 120 requests per minute" } The limit is generous for what the API does — campaigns change state over hours, not seconds. If you are hitting it, you are almost certainly polling harder than you need to.
Duplicate launches
A package can only be consumed once. If a launch request is retried after a timeout and the first one actually succeeded, the second returns 409 rather than creating a second campaign.
If a launch times out, do not blindly retry. Call GET /campaigns first and check whether the campaign exists.
Handling errors
async function dt(path, options = {}) {
const res = await fetch(BASE + path, {
...options,
headers: { "X-API-Key": KEY, "Content-Type": "application/json", ...(options.headers || {}) }
});
const body = await res.json().catch(() => ({}));
if (res.status === 429) {
await new Promise(r => setTimeout(r, 60000));
return dt(path, options);
}
if (!res.ok) {
const err = new Error(body.error || `HTTP ${res.status}`);
err.status = res.status;
throw err;
}
return body;
} Retry 429 and 500. Do not retry 400, 401, 404 or 409 — those describe a request that will fail identically the second time.