Skip to content
Docs
Open Escouade
Documentation contents

Docs › Mail

HTTP API

View as .md

Every MCP tool can also be called over HTTP: POST https://api.subagent.fr/v1/{operation}, with the same arguments in JSON and the same object in return. Under /v1, every route is a tool.

In short

PropertyValue
Addresshttps://api.subagent.fr/v1/{operation}, where {operation} is the name of a tool: send_email, list_threads…
MethodPOST, for every operation, reads included
BodyA JSON object: the tool’s arguments. An empty body counts as {}. 1 MiB at most.
AuthenticationAuthorization: Bearer sak_… header. Only sign_up is called without a key.
ResponseStatus 200 and the tool’s structured object
RefusalAn HTTP status and {"error": {"code", "message", "hint"}}
Schemashttps://api.subagent.fr/openapi.json (OpenAPI 3.1), readable without a key

A call

Terminal
curl -X POST https://api.subagent.fr/v1/inbox_status \
  -H "Authorization: Bearer $SUBAGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

The response is the object the tool returns over MCP in structuredContent. The readable text only exists over MCP.

Signing up and verifying over HTTP

Terminal
curl -X POST https://api.subagent.fr/v1/sign_up \
  -H "Content-Type: application/json" \
  -d '{"username": "atelier-exemple", "human_email": "[email protected]"}'

The response carries api_key: the key, shown once only. The following calls present it.

Terminal
curl -X POST https://api.subagent.fr/v1/verify \
  -H "Authorization: Bearer $SUBAGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"otp_code": "482913"}'

Refusals

JSON
{
  "error": {
    "code": "space_unverified",
    "message": "…",
    "hint": "…"
  }
}
  • code: stable. It is what a program tests.
  • message: a sentence in French.
  • hint: the lead, when there is one. It says what to do.
StatusMeaning
400An argument is missing, unknown or malformed: invalid_argument.
401The key is missing, unknown or revoked: unauthorized.
402The space has no active plan: payment_required.
403The key does not carry the permission (forbidden), the space is not verified (space_unverified), or the call comes from a web page (origin_refused).
404The object or the operation does not exist: not_found, unknown_operation.
409The state does not allow the action: duplicate_recipient, not_draft, inbox_suspended…
413The body exceeds 1 MiB: payload_too_large.
422The request is understood but refused: recipient_suppressed, own_domain_required, unresolved_placeholder…
429A limit is reached: rate_limited, key_daily_cap, queue_full…
500, 502, 503The service, or a service it depends on, did not answer: internal, provisioning_failed, dns_unavailable, billing_unavailable…

Every code, with what to do: Errors.

A message held back is not a refusal. Daily quota reached, send outside working hours, month’s volume reached: the response is a success (200), and the message is queued (queued). estimated_send_at gives its earliest departure, based on the sending window and the daily quota.

Going through a list

Lists return items and next_cursor.

  • limit goes from 1 to 100. 20 by default.
  • As long as next_cursor is not null, pass it back as it is in cursor for the next page.
  • A cursor is never made up: an unreadable cursor is refused with the code invalid_argument.
Terminal
curl -X POST https://api.subagent.fr/v1/list_threads \
  -H "Authorization: Bearer $SUBAGENT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"unanswered": true, "limit": 50}'

get_thread follows the same rule for a thread’s messages: 50 per call, then next_cursor. list_domains returns all the space’s domains at once.

Retrying without duplicates

send_email and reply_to_thread accept an idempotency_key, 1 to 255 characters. Over HTTP, the Idempotency-Key header is equivalent to the parameter.

  • Same key, same content: the server returns the message already created, with replayed = true.
  • Same key, different content: refusal idempotency_conflict. Nothing is sent.

After a 500 error or a network cut, retry with the same idempotency_key.

The shapes

  • Identifiers: inb_… (inbox), thr_… (thread), msg_… (message), dom_… (domain), followed by 32 hexadecimal characters. Pass them exactly as the tools return them.
  • Dates: ISO 8601 with an offset, in Paris time: 2026-10-13T09:00:00+02:00. As input, a Paris time without an offset is accepted: 2026-10-13T09:00.
  • Arguments: an unknown argument is refused, with the list of accepted arguments in the lead.

Rate

60 requests per minute without a key, 600 with a key, per client address. Beyond that: status 429, code rate_limited, and the Retry-After header gives the number of seconds to wait. See Limits and quotas.

The API cannot be called from another site’s web page: a request carrying a foreign Origin header is refused with the code origin_refused. Call it from your agent or your server.

The server’s reference

  • https://api.subagent.fr/openapi.json: every operation, its input and output schemas, its permissions (x-subagent-permissions).
  • https://api.subagent.fr/llms.txt: the rules and the list of tools, as text.
  • The service’s pages that are not tools: an assistant’s sign-in (/oauth/…, see Connector), the payment page and the invoices (see Pricing and payment), the unsubscribe page.
  • Tool reference: one page per tool, produced from this registry.
This page in Markdown: /en/mail/docs/api-http.md All the docs for an agent: /en/mail/docs/llms.txt Legal notice · Terms · Privacy · Service status