# Inbin quickstart for agents

You are an agent, or a developer handing this page to one. Follow it top to bottom and you will have an inbox that turns email into facts you can act on, and a send path where a human approves anything new. Every response below is real; the shapes are what the API returns today.

Base URLs: REST `https://api.inbin.dev/v1` (header `Authorization: Bearer <api key>`), MCP `https://api.inbin.dev/mcp` (Streamable HTTP; the same tools with an API key or an OAuth connection).

Inbin is infrastructure. It does not run an agent, decide what your agent says, or reply to anyone. It enforces one rule at the boundary: your agent's credential cannot create a recipient on its own. A human establishes who it may write to.

## 0. Get in

Two ways. Pick the one that matches what you have.

**A. Nothing yet (no account, no key).** Call the one tool that needs no credentials:

```
POST https://api.inbin.dev/mcp
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"create_trial_inbox","arguments":{"purpose_label":"signups"}}}
```

Real response:

```json
{ "status": "created", "inbox_address": "hkc7yj@in.inbin.dev", "expires_at": "2026-11-13T16:58:42.296Z" }
```

A runnable walkthrough of this path (create, wait for the code with a retry loop, claim): https://inbin.dev/guides/ai-agent-email-address-verification-codes

A trial inbox receives mail for about two weeks and can be used for signups and confirmation codes right away (`await_trial_inbox` blocks for the confirmation). A trial inbox cannot send. To keep the address and to send, the human has to claim it: call `claim_trial_inbox` with the address, share the `claim_url` it returns in chat, and the human enters their email and the one-time code Inbin mails them. That creates an application the human owns, enables sending on it, and issues a credential for the client they install (the claim page shows the config). Then call `await_trial_inbox_claim`; it answers `claim_pending`, `claimed` or `client_installed`, and only `client_installed` means a client is actually using the connection. From then on you are on path B with that connection.

**B. A human has an account.** They create an application in the dashboard at `https://inbin.dev` (sign in is one email), create an API key there, and give it to you, or they add Inbin as a connector in their MCP client (Claude Desktop: Settings, Connectors, add custom connector, URL `https://api.inbin.dev/mcp`, sign in; Claude Code: `claude mcp add --transport http inbin https://api.inbin.dev/mcp --header "Authorization: Bearer <key>"`). With an API key you act as the owner. With an OAuth connection you act as the agent: you can read and send, and you can never approve.

## 1. Receive

Create an inbox:

```
POST /inboxes   {"name": "support"}
```

The response carries `id` and the address `<slug>@in.inbin.dev`. Give that address to whoever should write to it, or enter it in a signup form. Every email that arrives becomes an event: typed fields extracted against the application's schema, every value checked against the message (a value that is not in the email is dropped, never invented), and the sender authenticated. Read them with `GET /events`, wait for the next one with `POST /events/await`, or over MCP with `list_events`, `await_event`, `get_event`. `sender_auth.verified` says whether the sender is authenticated. `field_trust` says whether a value is typed or free text a sender wrote. `field_origin` says, per value, whether it is in the sender's own words (`sender`) or came from forwarded or quoted text (`relayed`). Act only on typed values in the sender's own words from a verified sender. A forwarded IBAN is `relayed`, whoever forwarded it.

## 2. Send

```
POST /sends   {"to": "...", "subject": "...", "body": "...", "reply_to_event_id": "<optional>", "inbox_id": "<optional>", "hold_for_review": false}
```

MCP: `send_email` with the same fields. The answer is one of `sent`, `held`, `refused`, `failed`.

**A reply to someone who wrote first.** Set `reply_to_event_id` to the received event; `to` defaults to its sender and `subject` to `Re: <original>`, with threading headers. If that sender is a known counterpart (confirmed continuity or good standing, the original passed DMARC), it goes out on its own:

```json
{ "status": "sent", "id": "snd_...", "resend_id": "01a18812-...", "recipient": "lily@example.com" }
```

**Anyone new.** A first contact, or any address you chose yourself, is held. This is a real held response (the recipient was an address read from an email; nothing in the owner's policy established it):

```json
{ "status": "held",
  "id": "snd_hqxaumpf6drxqdbt",
  "reason": "recipient_not_established",
  "recipient": "mark.black-2134@gmail.com",
  "derivation": {
    "recipient": ["principal:authenticated", "capability:none matched", "class:PROPOSING by default"],
    "content":   ["principal:authenticated", "channel:chan:agent-self in binding", "...", "class:CORROBORATING via capa_sendpol_agentcontent_..."]
  },
  "how_to_release": "A human must approve this send. Inbin has already emailed the owner a 6-digit code and a one-click link for it. Tell the human what was held and why, ask them for the code, and pass it to approve_send (MCP) or POST /api/v1/sends/approve {id, code}. The owner can also click the link in that email, approve with an API key, or use the dashboard's Held sends panel. If the code expired (10 minutes), request_send_approval (MCP) or POST /api/v1/sends/approval-request {id} sends a fresh one." }
```

A hold is the expected outcome, not an error. The moment a send is held, Inbin emails the application's owner (their real address, never an Inbin inbox) a 6-digit code and a one-click link naming that exact send. Nothing is sent until a human acts.

**A draft, on purpose.** Pass `"hold_for_review": true` and the message is held even if the policy would let it go, reason `agent_requested_review`, same email to the owner, same approval paths.

**Secrets.** A body that looks like a card number, an IBAN, an API key or an ID number is held on every plane, reason `sensitive_content`, until a human approves.

## 3. Get the approval

Tell the human what was held, to whom, and why (`reason`, and the `derivation` if they ask). Then one of:

- The human tells you the code from the email: `POST /sends/approve {"id": "snd_...", "code": "482913"}` (MCP `approve_send`). Right code, the send goes out and you get the `sent` shape; five wrong attempts and the code is dead.
- The human clicks the link in the same email and chooses Approve and send, or Reject.
- The owner (API key, or the dashboard's Held sends panel) approves without a code: `POST /sends/approve {"id": "snd_..."}`.

Approving a reply allows replies to that sender for 30 days; it never becomes a general allowance, so a new email to the same address is held again. `GET /sends?status=held` (MCP `list_sends`) shows what is waiting.

## 4. Make people reachable without a hold

The owner, not the agent, adds contacts:

```
POST /policy   {"target": "alice@acme.com"}      one address
POST /policy   {"target": "@acme.com"}           a whole domain
```

From then on the agent writes to them on its own, new mail and replies. `GET /policy` lists the rows, `POST /policy/revoke {capability_id}` removes one. An agent connection can ask for these too, and the owner approves by code or link; it can never add a contact without the owner saying yes.

## 4b. When the owner limited the agent

In the dashboard the owner can tick what the agent does on its own (Read, Correspond, Transact, Administer). Beyond that, and for owner settings (webhook URL, inbox access, contacts), the call is not refused. It answers:

```json
{ "status": "approval_required", "id": "act_...", "reason": "beyond_agent_limits", "message": "..." }
```

The owner is emailed a code and a link for that exact call. Over MCP, call `confirm_agent_action {"id": "act_...", "code": "123456"}` and Inbin runs the call once, as you. Over REST, `POST /agent-actions/confirm {"id", "code"}`, then send the same request again with the header `X-Inbin-Approval: act_...`. If the owner clicked the link instead, skip the code.

## 5. Route a hold anywhere (developers)

Every hold fires a `send.held` event on the subscription system, signed with Standard Webhooks. Subscribe over MCP:

```
{"jsonrpc":"2.0","id":1,"method":"events/subscribe","params":{"name":"send.held","arguments":{},"delivery":{"mode":"webhook","url":"https://your.app/hook"}}}
```

Your endpoint must answer the verification handshake once: Inbin POSTs `{"type":"verification","challenge":"<uuid>"}` and expects `{"challenge":"<the same uuid>"}` back. After that each hold arrives as `{"name":"send.held","data":{"send_id","inbox_id","inbox_address","recipient","subject","reason","requested_via","derivation","created_at"}}` with `webhook-id`, `webhook-timestamp` and `webhook-signature` headers. `events/list` shows the full catalog (`email.received`, `verification.received`, `send.held`, `email.held`). `email.held` fires when Inbin holds a received message as a possible takeover: `{event_id, inbox_id, inbox_address, from, subject, continuity_status, held, received_at}`, never the body. `email.received` fires only for senders the owner's wake policy admits (default: contacts and known counterparts); its arguments `sender`, `predicates` and `min_class` can narrow that, never widen it (a wider request gets Forbidden). Its payload is `{event_id, inbox_id, inbox_address, from_address, subject, message_id, sender_class, continuity_status, received_at}`, never anything from the body.

## 6. If you are a platform

Give each of your customers their own application. Path A does this with no dashboard: your agent creates a trial inbox for the customer, the customer claims it with their own email, and the application is theirs. Their approvals go to their address, their contacts are theirs, and your agent connects to it with its own revocable connection; several agents can connect to the same application independently. Nothing crosses between customers because capabilities are per application.

## Limits that matter

- A trial inbox cannot send; a claimed application can.
- Sends: 30 per application per minute; monthly caps per plan (2,500 on Free). A held send uses no quota.
- Approval codes expire after 10 minutes, allow 5 attempts, and each works once; 5 requests per send over its lifetime.
- The approval email goes only to the account's human email. An agent cannot read it, so it cannot make up the code.

Everything else: `https://inbin.dev/docs` and, for agents, `https://inbin.dev/llms.txt`.
