Skip to main content
When a request fails, the API returns a JSON body with an error object alongside the appropriate HTTP status code. Every error response follows the same envelope: check code for a machine-readable identifier, message for a human-readable explanation, and hint for a suggested fix when one is available.

Error envelope

string
A short, stable identifier for the error type. Use this in code to branch on specific failures.
string
A human-readable description of what went wrong.
string
A suggested action to resolve the error, when one is applicable.

HTTP status codes

Common scenarios and fixes

The Authorization header is missing, the token value is malformed, or the token has been revoked.Fix: Confirm the header is present and formatted as Bearer <token>. Open the agent’s Tokens tab in the dashboard and verify the token is still active. If it was revoked, issue a new one.
The token is valid and the resource exists, but the token was not issued with the capability required by this endpoint: for example, trying to send mail with a token that only has email:read.Fix: Check which agent the token is bound to and which capabilities were granted at creation time. Either re-issue the token with the missing scope or use an existing token that already includes it.A 403 with details.reason set to organization_suspended means an operator suspended the workspace: every token, key and sign-in of it answers this way, and the message carries the operator’s explanation. Write to support@loadout.email to appeal. The public sign-up endpoint answers 403 with details.reason signup_blocked for an owner address that may not sign up (a disposable mail domain, or an address an operator blocked).
An allowance or usage cap for outbound recipients, inbound messages, machine time, or mailbox storage has been reached, or the prepaid balance is exhausted.Fix: Open Billing & usage in the dashboard. You may need to add agent seats, top up the prepaid balance, raise an owner-set usage cap, or wait for the monthly quota to reset. Processed mail data remains a hard monthly cap.
A send, reply or forward names an address that bounced permanently or reported your mail as spam earlier. The answer is validation_failed with details.reason recipient_suppressed; details.addresses lists every suppressed recipient of the request. Nothing is queued or charged.Fix: Remove the listed addresses and send again. An organization key can list all suppressed addresses with GET /api/v1/suppressions.
The request conflicts with the current state, such as a draft already being sent or an inbox that is not ready. Repeating a successful send with its original idempotency key and the same request returns the original result rather than a conflict. Reusing a key for a different request (another recipient, subject, body or text) answers 409.Fix: Read the error message and check the resource state. Keep the same idempotency_key when retrying the same action after an uncertain outcome; use a new key only for a distinct action.
A reply was refused to stop a mail loop: the message is an automatic reply or a delivery status report (details.reason mail_loop_risk), or the conversation already took 10 replies from this inbox within the hour (details.reason thread_send_cap, with Retry-After).Fix: Do not answer automated mail. Check the message’s automated field before replying, and stop replying in a conversation where two automated mailboxes may be answering each other.
You passed expected_updated_at and the draft was saved by someone else since you read it. details.reason is draft_changed and details.draft holds the current draft.Fix: Apply your change to the current draft and save again with its updated_at.

Idempotency

Sending, replying, forwarding, sending SMS, creating machines, buying phone numbers, social posts and replies, and connector calls (call_connector, call_connector_tool) accept an optional idempotency_key string. If your request times out or you receive a network error before seeing a response, resend the exact same request body with the same idempotency_key: the API returns the original result instead of acting twice.
Use a new, unique idempotency_key every time you intend a genuinely new action. Reusing a key for a different request is a logic error, not a retry: sends, replies, forwards, SMS, social posts and replies, and connector calls refuse it with 409.

Connector calls

Pass an idempotency_key with every connector call that changes state in the connected app, such as a payment, a refund or a message.
  • Repeating the same request with the same key within 24 hours returns the first response with replayed: true, without calling the app again.
  • Another request under the key answers 409 with details.reason idempotency_key_reused.
  • A repeat while the first call is still running answers 409 with details.reason idempotency_in_progress. Retry in a minute.
  • A first call that failed, or whose app answered 401, 408, 429 or 5xx, is not stored: the same request under the key runs again (after a 401, once the account is reconnected).
  • REST connector calls send the key to the app as the Idempotency-Key header, so apps that support it (Stripe, for example) also deduplicate on their side.
Sending with an idempotency key

Rate limits

The 429 response includes a Retry-After header with the number of seconds until the request fits again, and the same value in details.retryAfterSeconds. Wait at least that long; for automated retry loops, add exponential back-off on top. The REST API and the MCP server share these limits. The token and organization limits are counted across all of Agent Loadout’s servers: Operations that cost money have their own limit per organization, shared by the API, MCP and the dashboard: starting and resuming machines (20 per minute each), opening a machine desktop (30 per minute), searching for phone numbers (30 per minute), buying them (10 per minute), sending SMS (60 per minute) and adding custom domains (10 per hour). Retrying a request with its original idempotency_key does not count again. These refusals carry details.reason too_many_requests and, where known, details.retryAfterSeconds.