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
A short, stable identifier for the error type. Use this in code to branch on specific failures.
HTTP status codes
Common scenarios and fixes
403 Forbidden
403 Forbidden
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).402 Payment Required
402 Payment Required
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.
400 Suppressed recipient on send
400 Suppressed recipient on send
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.409 Conflict on send
409 Conflict on send
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.409 mail_loop_risk or 429 thread_send_cap on reply
409 mail_loop_risk or 429 thread_send_cap on reply
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.409 Conflict when saving a draft
409 Conflict when saving a draft
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 anidempotency_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
409withdetails.reasonidempotency_key_reused. - A repeat while the first call is still running answers
409withdetails.reasonidempotency_in_progress. Retry in a minute. - A first call that failed, or whose app answered
401,408,429or5xx, is not stored: the same request under the key runs again (after a401, once the account is reconnected). - REST connector calls send the key to the app as the
Idempotency-Keyheader, so apps that support it (Stripe, for example) also deduplicate on their side.
Sending with an idempotency key
Rate limits
The429 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.