---
title: "Forward a raw JMAP method-call request to the mailbox's own Stalwart principal"
method: POST
path: "/v1/jmap"
tags: ["jmap"]
---

# Forward a raw JMAP method-call request to the mailbox's own Stalwart principal

`POST /v1/jmap`

## Request

- Base URL: `https://mbag.ai/api`
- URL: `https://mbag.ai/api/v1/jmap`
- Auth: HTTP bearer

A thin tenant-isolation proxy: resolves which of the caller's
mailboxes to act as, then forwards the request to Stalwart's JMAP
endpoint authenticated as that mailbox (HTTP Basic), rewriting any
client-supplied `accountId` in each method call to the resolved
principal's own account id (defense in depth — Stalwart's own
per-principal auth is the real isolation boundary).

## Request body

- object: A raw JMAP request object per RFC 8620 (`using`, `methodCalls`, optional `createdIds`) — not modeled here; see RFC 8620 (core) and RFC 8621 (mail). Each `methodCalls` entry may be given as JMAP's native `[name, arguments, id]` triple, or as `{"name":...,"arguments":...,"id":...}` objects, which are transformed into triples before forwarding. An optional top-level `_mailbuttons` object — `{"account": "<email or mailbox id>"}` — selects which of the caller's mailboxes to act as when the API key owns more than one; it is stripped before forwarding and never reaches Stalwart.

## Response `200`

The JMAP response forwarded verbatim from Stalwart on success
(`methodResponses` etc., per RFC 8620) — not modeled here.

- object

## Other responses

- `400`: The API key owns multiple mailboxes and no `_mailbuttons.account` hint was given to disambiguate. `error: "ambiguous_account"`.
- `401`: Missing or invalid credential, raised by the `ApiKeyAuth` extractor (`backend/src/api_keys.rs`) before the handler runs: no `Authorization` header, a non-`Bearer` scheme, a key that doesn't hash-match any row, or a key whose status isn't `active`. A header that isn't valid UTF-8, has no scheme token, or has `Bearer` with no key value returns 400 with this same JSON shape instead of 401.
- `403`: The `_mailbuttons.account` hint doesn't match any mailbox this API key owns. `error: "account_not_found_or_not_owned"`.
- `404`: This API key has no active mailbox. `error: "no_mailbox"`.
- `409`: The selected mailbox has no JMAP password yet (not provisioned in Stalwart). `error: "mailbox_not_provisioned"`.
- `500`: Database error while looking up the caller's mailboxes. Body has no `detail` key (unlike the other error responses here).
- `502`: Failed to reach Stalwart's JMAP endpoint. `error: "Failed to reach upstream JMAP"`.
- `default`: Any non-2xx status Stalwart itself returns is passed through verbatim (its status code and JSON body, or `{"error":"Invalid JSON from upstream"}` if the body didn't parse as JSON).

---

[API](https://skmtc.dev/mailbuttons/apis/mailbuttons-agent-mail-api.md) · [All operations](https://skmtc.dev/mailbuttons/apis/mailbuttons-agent-mail-api/llms.txt) · [OpenAPI document](https://skmtc.dev/mailbuttons/apis/mailbuttons-agent-mail-api/revisions/fe2761823978?raw)
