Get the app Map (opens in a new tab)List your business

Developers

Errors, limits and idempotency

The error shape, every error code with its HTTP status, when to retry, rate limits, pagination, and the Idempotency-Key that stops duplicates.

Coming soonThe Partner API is not open yet. These docs are here so you can plan your integration; hosts and keys are issued at launch.Register as a developer →

On this page

The error shape

json
{
  "error": {
    "code": "VERSION_CONFLICT",
    "message": "The menu is at version 16.",
    "request_id": "req_01JA9Z",
    "details": { "current_version": 16 }
  }
}
  • Rely on code in your code, not on message. Messages may change.
  • Every response carries X-Request-Id. Send it to us when you ask for support.

Codes

HTTP Code Meaning
400 VALIDATION_FAILED a field is missing or wrong, or an id contains :; details in details[] with path
400 URL_NOT_ALLOWED a webhook or quote URL is not public https
400 IDEMPOTENCY_KEY_MISSING a changing call without Idempotency-Key
401 KEY_UNKNOWN the key id does not exist
401 KEY_REVOKED the key was revoked
401 KEY_MODE_MISMATCH a test key on the live address, or the reverse
401 TIMESTAMP_SKEW the time is more than 300 seconds from ours
401 NONCE_INVALID the nonce is invalid
401 NONCE_REUSED the nonce was used before
401 SIGNATURE_INVALID the signature does not match
403 SCOPE_MISSING the link lacks the scope; its name is in details.scope
403 LINK_REQUIRED you have no link to this place
403 PARTNER_NOT_CERTIFIED a live call before certification
403 PARTNER_SUSPENDED the partner account is suspended
404 NOT_FOUND the id does not exist, or is not yours
404 MENU_NOT_FOUND this place has no menu from you
404 MENU_VERSION_GONE the requested version is no longer kept; the current one is in details.current_version
409 VERSION_CONFLICT the menu changed after the version you sent
409 IDEMPOTENCY_KEY_REUSED the same key with a different body
409 IDEMPOTENCY_IN_PROGRESS the first request with that key has not finished
409 INVALID_TRANSITION the order cannot move to that status
409 ALREADY_ACCEPTED the order was already accepted, e.g. when rejecting it
409 ORDER_CLOSED the order is final; its status is in details.status
409 LOCATION_ALREADY_LINKED this branch of yours is linked to another place
409 LINK_CODE_EXPIRED the link code expired
409 LINK_CODE_USED the link code was used
410 LINK_REMOVED the merchant removed the link
413 PAYLOAD_TOO_LARGE the body is over 2 MB
422 MENU_INVALID the menu breaks the CM v1 rules
422 PRODUCT_INVALID the product breaks the CP v1 rules Coming soon
429 RATE_LIMITED over the limit; wait for Retry-After
500 INTERNAL our fault
503 UNAVAILABLE maintenance or temporary load

When to retry

Answer What to do
5xx, 429, network failure retry with growing delays and the same Idempotency-Key
409 IDEMPOTENCY_IN_PROGRESS wait a second and retry
409 VERSION_CONFLICT read the menu or publish it in full, then retry
other 4xx do not retry; fix the request

Idempotency-Key

Every POST, PUT, PATCH and DELETE needs an Idempotency-Key header: 8 to 128 chars of [A-Za-z0-9_.:-].

  • We keep the key for 24 hours with a hash of the body, per account and environment, not per API key. So a retry is still recognised after you rotate keys.
  • Same key, same body: we return the stored answer with Idempotent-Replayed: true. The command does not run again.
  • Same key, different body: 409 IDEMPOTENCY_KEY_REUSED.

The key is not the nonce. A retry takes a new nonce and signature, and keeps the key.

Pick a key that describes the command, like accept-ord_7m1x9c or menu-plc_9q3w7e-<your version number>.

js
async function withRetry(fn, tries = 5) {
  for (let i = 0; ; i++) {
    try { return await fn(); }
    catch (e) {
      const retryable = !e.status || e.status >= 500 || e.status === 429 || e.error?.code === 'IDEMPOTENCY_IN_PROGRESS';
      if (!retryable || i >= tries - 1) throw e;
      await new Promise((r) => setTimeout(r, Math.min(30000, 500 * 2 ** i)));
    }
  }
}
// the key is fixed across attempts; the signature is new on each one
await withRetry(() => aurmap('POST', `/v1/orders/${id}/accept`, { prep_minutes: 15 }, `accept-${id}`));
php
function with_retry(callable $fn, int $tries = 5) {
    for ($i = 0; ; $i++) {
        try {
            return $fn();
        } catch (RuntimeException $e) {
            $status = $e->getCode();
            $retryable = $status === 0 || $status >= 500 || $status === 429 || $e->getMessage() === 'IDEMPOTENCY_IN_PROGRESS';
            if (!$retryable || $i >= $tries - 1) {
                throw $e;
            }
            usleep(min(30000, 500 * (2 ** $i)) * 1000);
        }
    }
}
with_retry(fn () => aurmap('POST', "/v1/orders/{$id}/accept", ['prep_minutes' => 15], "accept-{$id}"));

Rate limits

Kind Limit
all calls 600 a minute per account and environment
full menu publish 12 an hour per place
menu patch, state and stock 120 a minute per place
link codes 60 an hour
order commands 300 a minute
events a daily limit per account and environment

Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. With 429 we send Retry-After in seconds.

Limits apply from day one, during the free beta too. They are per account and environment, not per key, so creating a new key does not change them. If you need a higher limit, ask support.

Pagination

Lists use a cursor:

bash
aurmap GET '/v1/orders?status=open&limit=100'
aurmap GET '/v1/orders?status=open&limit=100&cursor=c_8Jk2'
json
{ "data": [ { "order_id": "ord_7m1x9c", "…": "…" } ], "next_cursor": "c_8Jk2" }
  • limit from 1 to 100, 50 by default.
  • A cursor is valid for 24 hours. Do not read or build it.
  • An empty next_cursor means the end.