Error Codes and Response Shape

Every Macha API error returns a stable code your code can switch on. Reference for every code we emit.

Every Macha API error follows the same shape, with a stable code string you can switch on. We never repurpose codes, if a new error condition arises, we give it a new code rather than overload an existing one.

Error response shape

{
  "error": {
    "code": "agent_handle_taken",
    "message": "Agent with handle @ticketTriage already exists",
    "request_id": "req_a0b88aa084bac0f7"
  }
}

Field semantics

FieldStabilityUse for
codeStable. Never changes between releases without a v-bump.Branching logic. Switch on this to decide what to do.
messageHuman-friendly. May be reworded between releases.Logging, surfacing to end users. Never machine-parse.
request_idPer-request. Also returned in the X-Request-ID header.Log it. Macha support needs it to trace anything.

HTTP status semantics

Macha follows RFC 7231 strictly. We don't return 200 on errors. The HTTP status alone tells you the rough category; the code tells you the exact reason.

StatusCategoryShould you retry?
400Bad requestNo, fix the request.
401Authentication failedNo, check the key.
403Authorization failed (scope or policy)No, the key needs different scopes.
404Resource doesn't existNo, check the ID.
409State conflict (uniqueness, idempotency reuse)Sometimes, depends on the code. See below.
422Validation failedNo, the input is invalid.
429Rate limitedYes, honor Retry-After.
500Server errorYes, with exponential backoff. Use an Idempotency-Key.
503Service unavailableYes, with exponential backoff.

Error code registry

These are the codes Macha v1 currently emits. Adding new codes is non-breaking (additive). Removing or renaming a code is breaking and would require v2.

Authentication & authorization

CodeHTTPMeaning
unauthorized401Missing, malformed, revoked, or unknown API key. Verify the Authorization: Bearer ... header.
insufficient_scope403Valid key, lacks the required scope for this endpoint.
forbidden403Org-level block, e.g. deletion-scheduled org, suspended subscription.

Validation & not-found

CodeHTTPMeaning
bad_request400Malformed input that isn't a domain-validation issue (e.g. invalid JSON).
validation_failed422Required field missing, value out of range, enum mismatch, etc. message names the offending field.
not_found404Generic. Used for unknown nested resources.
agent_not_found404Agent ID doesn't exist (or has been soft-deleted past the trash window).
conversation_not_found404Conversation ID unknown or belongs to another org.
custom_tool_not_found404Custom tool deleted or never existed.
source_not_found404Source deleted or never existed.

Conflicts

CodeHTTPMeaning
agent_handle_taken409Trying to create or rename an agent to a handle that already exists in the org (case-insensitive).
custom_tool_name_taken409Custom tool with this generated name already exists.
upload_source_exists409POST /sources with type=upload, the Uploads source is a per-org singleton and is already provisioned.
idempotency_key_reused409Same Idempotency-Key seen within 24h with a different request body. Generate a fresh key for a different operation.
plan_limit_exceeded422The action would exceed your plan's resource cap. Returned at write time, never read time.

Throttling & server

CodeHTTPMeaning
rate_limited429Per-key rate limit exceeded. Inspect Retry-After and X-RateLimit-* headers.
internal_error500Unhandled server exception. Log the request_id, if it persists, contact support.
service_unavailable503Provider unreachable, queue full, or planned maintenance. Retry with backoff.

Retry guidance

The boring rule: retry 5xx and 429, never retry 4xx.

Slightly less boring rule: retries on write endpoints should always carry an Idempotency-Key header. Otherwise you may end up with duplicate resources if the original request actually succeeded but the response got lost on the wire.

Recommended retry pattern

async function callMacha(path, init) {
  const url = `https://dashboard.getmacha.com/api/v1${path}`;
  const headers = {
    'Authorization': `Bearer ${process.env.MACHA_API_KEY}`,
    ...(init?.headers || {}),
  };

  let lastError;
  for (let attempt = 0; attempt < 5; attempt++) {
    const res = await fetch(url, { ...init, headers });
    if (res.status < 500 && res.status !== 429) return res; // success or non-retryable

    // 429 honors Retry-After; 5xx uses exponential backoff with jitter.
    const retryAfter = parseFloat(res.headers.get('Retry-After'));
    const backoff = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : Math.min(1000 * 2 ** attempt, 30_000) + Math.random() * 250;

    await new Promise(r => setTimeout(r, backoff));
    lastError = res;
  }
  return lastError; // give up after 5 tries
}
📘
Never retry without an Idempotency-Key on POST/PATCH

If the original POST actually succeeded but the response was lost, a naive retry creates a duplicate. With Idempotency-Key set, Macha returns the cached original response on the retry, no duplicate. See Idempotency.

Logging for support

When something goes wrong and you need Macha to look at it, capture:

  • The request URL (with query string)
  • The HTTP method
  • The response status code
  • The response error.code
  • The request_id (from response body or X-Request-ID header)
  • The timestamp

That's enough for us to trace the exact request through our logs in seconds. Without the request_id, support takes minutes-to-hours.

© 2026 AGZ Technologies Private Limited