Error handling
The API uses conventional HTTP status codes and returns a consistent error body.
{
"error": {
"code": "not_found",
"message": "Brand not found.",
"request_id": "req_01J8Z3KC02"
}
}| Field | Use it for |
|---|---|
code | Branching in your code. Stable within a version. |
message | Human-readable explanation. Do not match on it; wording changes. |
request_id | Logging and support. Quote it when you contact us. |
Error responses have no data key. Check the status code, or the presence of error, rather than assuming a shape.
Status codes
| Status | Meaning | Your move |
|---|---|---|
200 | Success | — |
304 | Not modified, for a conditional request | Use your cached copy |
400 | The request is malformed | Fix the request. Retrying will not help. |
401 | Authentication failed | Check the key. See API keys. |
403 | Authenticated, but not permitted | Check scopes or plan access |
404 | Not found, or not visible to this key | Verify the id and the key’s reach |
405 | Method not allowed | v1 is read-only; use GET |
429 | Rate limited | Back off and retry |
500 | Something failed on our side | Retry with backoff; contact us with the request_id if it persists |
503 | Temporarily unavailable | Retry after Retry-After |
Full list of codes on the error codes page.
Why 404 and not 403
Requesting a resource in another workspace, or a brand your key is not permitted to reach, returns 404 not_found, the same as an id that does not exist.
That is deliberate. A 403 would confirm the resource exists, which turns the API into a way to discover what else is out there. If you are certain an id is right and still get a 404, check GET /v1/me to see the workspace and brand access your key actually has.
Retries
Retry 429, 500, 502, 503, and 504. Use exponential backoff with jitter, and honour Retry-After when present.
Do not retry any other 4xx. A 400, 401, 403, 404, or 405 will fail identically every time. A retry loop over them wastes your rate limit and delays you noticing the real problem.
const RETRYABLE = new Set([429, 500, 502, 503, 504])
async function requestWithRetry(url, key, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch(url, { headers: { Authorization: `Bearer ${key}` } })
if (res.ok || !RETRYABLE.has(res.status)) return res
if (attempt === maxAttempts - 1) return res
const retryAfter = Number(res.headers.get('Retry-After') ?? 0)
const backoff = retryAfter || Math.min(2 ** attempt, 30)
const jitter = Math.random() * 0.3 * backoff
await new Promise(r => setTimeout(r, (backoff + jitter) * 1000))
}
}Because v1 is read-only, retrying is always safe. Repeating a GET has no side effects.
Timeouts
Set a client timeout of about 30 seconds. Most responses return in well under a second; the slowest are wide date ranges over large brands.
If you time out regularly, narrow the date range or reduce limit rather than raising the timeout. A request that takes 30 seconds is usually asking for more data than it needs.
Log the request id
Log request_id for every failure, alongside the status, the code, and the endpoint.
It is the one field that lets us find your exact request in our logs. Without it, a support conversation starts with narrowing down which of many requests you mean. With it, we can look directly at what happened.
Do not log the API key itself, and do not include it in error reports or issue trackers.
Surfacing errors to people
If your integration is user-facing, translate rather than passing our message through.
| Code | Better phrasing |
|---|---|
token_expired, token_revoked | ”The Asky connection needs to be reconnected.” |
plan_required | ”Asky API access is not enabled for this workspace.” |
rate_limited | ”Syncing is taking longer than usual.” Then retry silently. |
internal_error | ”Asky is temporarily unavailable.” Include the request_id in your own diagnostics, not in the message. |
Errors that need someone to act, particularly 401 and 403 on a scheduled job, should raise an alert rather than only appearing in a log. An expired key on a nightly sync is otherwise discovered by someone noticing stale numbers a week later.