Agent REST API v1
AgenticMailCenter is an email inbox for external agents. An external agent runs in a client you choose; the dashboard is a companion for mailbox owners. This page documents the small, tenant-bound REST surface available to agents. The machine-readable contract is available at /openapi. It contains exactly the supported 20 operations; browser, administration, mailbox lifecycle, audit, and internal routes are not part of this API.
Authentication and scopes
Create a tenant API key in the authenticated operator workspace, store it in a secret manager, and send it as Authorization: Bearer $AGENT_API_KEY. Keys are tenant-bound and independently revocable. The required scope is recorded per operation in OpenAPI as x-agent-scope.
The safe read example below is deliberately side-effect free:
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer ${AGENT_API_KEY}" \
"${AGENTICMAILCENTER_ORIGIN}/v1/accounts"
The same request in TypeScript:
const response = await fetch(`${process.env.AGENTICMAILCENTER_ORIGIN}/v1/accounts`, {
headers: { Authorization: `Bearer ${process.env.AGENT_API_KEY}` },
});
if (!response.ok) throw new Error(`Agent API failed: ${response.status}`);
const accounts = await response.json();
console.log(accounts);
And Python using only the standard library:
import os
from urllib.request import Request, urlopen
request = Request(
f"{os.environ['AGENTICMAILCENTER_ORIGIN']}/v1/accounts",
headers={"Authorization": f"Bearer {os.environ['AGENT_API_KEY']}"},
)
with urlopen(request) as response:
print(response.read().decode("utf-8"))
Read message text
GET /v1/messages/{message_id}/content reads a message's text from the connected email account when your agent needs it. It requires gateway:read, and each read appears in the audit trail. Hosted deployments do not store message bodies, so use this route rather than the body fields on GET /v1/messages/{message_id}:
curl --fail-with-body --silent --show-error \
-H "Authorization: Bearer ${AGENT_API_KEY}" \
"${AGENTICMAILCENTER_ORIGIN}/v1/messages/${MESSAGE_ID}/content"
The response contains content.plain and content.html. Each read returns up to 1 MB of text, and content.truncated is true when some of it was left out.
Thread sorting and pagination
GET /v1/threads accepts sort=received_at|subject|sender_name|sender_email and direction=asc|desc, defaulting to newest received first. Received means provider mailbox arrival, not the Date header or import time. Sender fields come from the latest message in the conversation; missing sender names use the address. Unknown values sort last, with stable ID ties.
Use limit (1-100, default 50) and pass the returned next_cursor as cursor to continue. Keep the account, filters, sort, direction, and compact option unchanged while following a cursor. Start a new first page when any of them changes. include_matched_message_ids=false omits the per-thread message-ID arrays while retaining matching counts and bounded representative metadata; the default remains true for existing callers.
Draft and send boundary
Agents can create and update drafts with gateway:write, then submit them for review. The no-send journey is: POST /v1/drafts, PATCH /v1/drafts/{draft_id}, then POST /v1/drafts/{draft_id}/submit. Submitting does not send. For send_with_policy, sending requires gateway:send, human approval, and passing policy checks. The separate send_direct mode can send a policy-allowed draft before review only when AMG_DIRECT_SEND_ENABLED=true; rejected drafts remain blocked. The dashboard's eligibility check does not send or change draft state. Use OpenAPI as the source of truth for paths, scopes, and parameters; examples use the runtime draft payload and recipient shape.
curl --fail-with-body --silent --show-error -X POST \
-H "Authorization: Bearer ${AGENT_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"account_id":"acct_example","agent_identity_id":"ident_example","idempotency_key":"draft-example-001","subject":"Example subject","body_text":"Example body","to_addresses":[{"address":"recipient@example.invalid"}]}' \
"${AGENTICMAILCENTER_ORIGIN}/v1/drafts"
curl --fail-with-body --silent --show-error -X PATCH \
-H "Authorization: Bearer ${AGENT_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"subject":"Updated example subject","body_text":"Updated example body","to_addresses":[{"address":"recipient@example.invalid"}]}' \
"${AGENTICMAILCENTER_ORIGIN}/v1/drafts/draft_example"
curl --fail-with-body --silent --show-error -X POST \
-H "Authorization: Bearer ${AGENT_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"submitted_by":"agent_example"}' \
"${AGENTICMAILCENTER_ORIGIN}/v1/drafts/draft_example/submit"
Never put a key in a URL, commit it, paste it into a ticket, or use a production key while testing examples. The placeholders above are inert and use no real mailbox data. Verify reads locally or against a non-production tenant and do not send mail during documentation checks.