aov-post-purchase-upsell
🔌 Store API
Platform
Errors

Errors

Success and failure share one envelope, so a client parses one shape.

{ "success": true, "data": { "...": "..." } }
{ "success": false, "data": null, "error": "Date range cannot exceed 90 days" }

success is the field to branch on. error is a short human-readable sentence — display it, log it, but do not parse it: the wording can change, the status code will not.

Status codes

StatusMeaningRetry?
200The call succeeded
401Token missing, invalid, or revokedNo. Re-issue the token
422A parameter is invalid — a date range over 90 days, a missing required parameterNo. Fix the request
429Over the rate limitYes, after Retry-After
500Something failed on our sideYes, with backoff
503Your token could not be checked — a transient failure, not a credential problemYes, after Retry-After

No endpoint returns 403, 404 for a missing offer, or a partial success. A path that is not one of the eleven is not an API answer at all — it never reaches the envelope, so parse success only on the paths documented here.

⚠️
A malformed date is not a 422. start and end that cannot be parsed are ignored and the default window runs instead, returning 200 — see Date ranges. The only parameter errors that reach 422 are a range over 90 days and a missing required parameter.

An offer that does not exist

GET /data/offer/{id} with an id that is not yours — or no longer exists — returns 200 with data: null, not 404.

{ "success": true, "data": null }
This is deliberate. An id belonging to another store and an id that was deleted answer identically, so the API cannot be used to discover whether an offer exists somewhere else. Treat data: null as "not found", and branch on it before reading fields.

A deleted offer inside a ranking

/data/analytics/top ranks from claim history, so an offer deleted after it earned still appears. Those rows carry isDeleted: true and no url, because there is no page left to open.

Retrying safely

Every endpoint is a GET and nothing here changes state, so retries are always safe — no idempotency key, no duplicate-write risk.

That said, retry only what can succeed:

async function read(path, params, attempt = 1) {
  const response = await call(path, params);
  const body = await response.json();
 
  if (body.success) return body.data;
  if (response.status === 429 && attempt <= 3) {
    const wait = Number(response.headers.get('Retry-After') || 60);
    await new Promise(r => setTimeout(r, wait * 1000));
    return read(path, params, attempt + 1);
  }
  if (response.status >= 500 && attempt <= 3) {
    await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
    return read(path, params, attempt + 1);
  }
  throw new Error(body.error || `HTTP ${response.status}`);
}

A 401 or 422 retried is a 401 or 422 again. Surface it instead.

Messages you may meet

MessageStatusCause
Missing access token401No Authorization header, or not Bearer <token>
Invalid access token401Token is wrong, rotated away, or revoked
Service unavailable503The token could not be checked. Retry after Retry-After — do not rotate
Date range cannot exceed 90 days422Shorten the range
placement is required — priority is ranked within one placement422/data/offers/by-priority called without placement
Rate limit exceeded429Wait for Retry-After
read_failed500A read failed on our side. This one is a fixed code, not a sentence — the detail stays in our logs so a message can never leak a host or a query. Retry with backoff

Related

Product
Install AppWebsiteAvada Apps
Resources
DocumentationFAQPrivacy Policy
Company
Avada GroupContact
© 2026 Avada Group. All rights reserved.