Armada Armada API v2

Errors & status codes

Every v2 error response is JSON with a small, consistent shape. This page catalogs the codes you'll see, what triggers each, and how to recover. When something's wrong, start at the status code and walk the page.

Error shape

{
  "error": "not_retryable",
  "message": "Cannot retry order in completed status. Must be failed."
}
  • error — short machine-readable code. Safe to branch on in code.
  • message — human-readable sentence. Safe to log, include in support tickets, and surface in internal dashboards. Do not surface raw to end users — wording can change.

Status code table

Status
Meaning
What to do
200
OK
Success. For most endpoints.
201
Created
Success on a resource creation (branches, a few others).
204
No Content
Success with empty body. Branch delete returns this.
400
Bad Request
Your request body failed validation. The message names the bad field. Fix + retry.
401
Unauthorized
Auth failed — key, secret, timestamp, or signature. Don't retry blindly — see Debugging 401.
403
Forbidden
Auth worked, but the key lacks the required permission (e.g. invoices:read). Toggle in the API Keys page.
404
Not Found
The resource doesn't exist, or it's not owned by this merchant. Both return 404 (on purpose — so you can't enumerate).
409
Conflict
Request is valid but the resource is in a state that forbids the action — e.g. retrying a completed order.
422
Unprocessable
Request is well-formed but semantically impossible — e.g. wallet balance too low when forceWalletPayment is on.
500
Server Error
Something broke on our side. Safe to retry with backoff. If it persists, ping support with the response body + timestamp.
502/503/504
Upstream
Transient infrastructure error. Retry with backoff (e.g. 1s / 4s / 16s).

Debugging 401

Nine times out of ten a 401 is one of: bad key, bad secret, timestamp format, or path mismatch. Walk this checklist top-down — stop at the first hit.

  1. Fresh copy the key and secret from the business-app API Keys page. A trailing newline (shell copy-paste) or a leading space breaks the header.
  2. Timestamp format. Must be milliseconds since epoch, as a decimal string. Not ISO-8601, not seconds. Your clock must be within 30 s of Armada's.
  3. Path in the signed payload. The signed path must exactly match what arrives on the wire — including any query string. If axios/Guzzle/etc. serializes an empty params object as a trailing ?, sign that.
  4. Body byte-identical. If you pretty-print the JSON for logging and then send a compact copy, the hash won't match. Stringify once, sign + send the same bytes.
  5. Secret rotation. If someone rotated the secret recently, old clients still using the cached value will 401. Grab the new one.

See Authentication for the full signing walkthrough with worked examples.

Common 400s by endpoint

A non-exhaustive list of the errors you'll bump into while integrating, in rough order of frequency.

POST /v2/deliveries

  • destination.latitude: is required for location_format — you set destination_format: "location_format" but didn't provide lat/lng. Either include them or switch to a structured format.
  • branch_id is required when merchant has multiple branches — you sent origin_format: "location_format" on a multi-branch merchant. Use branch_format with an explicit branch_id, or associate the merchant with a single branch.
  • Origin: Branch not found or does not belong to this merchant — the branch_id is wrong or is another merchant's. Fetch the list with GET /v2/branches.
  • payment.amount: must be a positive number — zero or negative. Minimum is anything > 0.
  • reference: is required — always send a partner-side unique id for idempotency.

POST /v2/deliveries/:id/cancel

  • 409 not_cancellable — the order is already in a terminal state (completed/canceled/failed). Fetch with GET to see current status.

POST /v2/deliveries/:id/retry

  • 409 not_retryable — the order is not in failed. Only failed can be retried; for canceled or completed, create a new delivery.

Auth / permissions (any endpoint)

  • 403 with a message about permissions — the key lacks the specific cap. Example: invoices:read required. Edit the key in the API Keys page.

Retry strategy

When you see a transient error — network timeout, 5xx, connection reset — retry with exponential backoff. The SDKs don't retry automatically (to avoid double-dispatching on ambiguous responses), so this is on you. A reasonable baseline:

async function withRetry(fn, { maxAttempts = 5 } = {}) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const s = err.status;
      // Retry transient server errors, not client errors.
      if (s >= 500 || s === 429 || s === undefined /* network */) {
        if (attempt === maxAttempts) throw err;
        const wait = Math.min(16_000, 500 * Math.pow(2, attempt - 1));
        await new Promise((r) => setTimeout(r, wait));
        continue;
      }
      throw err;   // 4xx → don't retry
    }
  }
}

const { data } = await withRetry(() => armada.deliveries.create(body));