Every API here is available over the APIs.io API and to AI agents over MCP.
MCP server
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
Tools for apis
7 MCP tools reach this
find_apisBrowse and filter every API in the catalog.
get_api_artifactsOne API's artifacts, grouped by type.
get_openapiThe primary OpenAPI for this API.
find_similar_apisAPIs that look like this one.
apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
resolveTurn a domain, URL or GitHub org into the provider it belongs to.
find_cohortsEvery scored population of providers in the catalog.
All 92 tools →
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/anjal-app:anjal-app-inboxes-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Discovery needs no key. Ratings and market analysis are Pro.
Get an API key
Free tier, no form to fill in. Signing in shares your email address with us — we
store it to create your key and to recognise you if you sign in with another
provider. See our Privacy Policy and
Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Anjal Inboxes API
version: 1.0.0
description: 'Anjal is a multi-tenant email API for AI agents.
Create inboxes on the default `@send.anjal.app` domain or your own custom
domains, send and receive mail, manage drafts, subscribe to real-time
events over WebSocket, and isolate resources across workspaces so your
reseller customers can serve their own end-customers under a single
billing relationship.
Install the TypeScript SDK: `npm install anjal`
(npm)
MCP server for Claude Desktop / Cursor: `npx -y anjal-mcp`
(npm)
## Authentication
Every request is authenticated with the `X-API-Key` header (or
`Authorization: Bearer `). Four key prefixes, each with increasing
scope:
| Prefix | Scope | Use it for |
|----------|-----------------------------------------|-----------------------------------------|
| `sa_...` | Super admin | Tenant lifecycle (platform operator) |
| `tk_...` | Tenant master | Everything in your tenant across workspaces |
| `wk_...` | Workspace master | One workspace only |
| `ak_...` | Inbox (agent) scoped | One inbox — its messages, drafts, lists |
## Resource hierarchy
```
Tenant (billing root)
└── Workspace (isolation container)
├── Inboxes
│ ├── Messages / Threads / Attachments
│ ├── Drafts
│ └── Lists
└── Domains
```
## Sending mail
Mail is sent via `POST /v1/inboxes/{id}/send`. Replies go through
`/reply/{messageId}`, reply-all through `/reply-all/{messageId}`, and
forwards through `/forward/{messageId}`. The `From` header always uses
the inbox''s **primary alias** — add a custom-domain address with
`POST /v1/inboxes/{id}/addresses` to change it.
### Safe retries (Idempotency-Key)
Every send endpoint (`/send`, `/reply`, `/reply-all`, `/forward` and
`POST /v1/inboxes/{id}/drafts/{did}/send`) accepts an `Idempotency-Key`
header. Send a unique value (a UUID works) and retry freely: the first
request sends and stores its response; a retry with the same key and the
identical request returns that stored response with the header
`Idempotent-Replayed: true` and sends nothing.
| Situation | Result |
|---|---|
| Same key, different request (body, inbox or endpoint) | `409 IDEMPOTENCY_KEY_REUSED` |
| Same key while the first request is still running | `409 IDEMPOTENCY_IN_PROGRESS` |
| Header present but blank, or longer than 255 characters | `400 IDEMPOTENCY_KEY_INVALID` |
| The first send failed (non-2xx) | The key is freed; retry with the same key |
Keys are scoped to your account and expire after 24 hours.
### Labels, open tracking, scheduled send
- **Labels**: pass `labels` (array, or a comma-separated string) on any
send, reply, reply-all, forward or draft. Change them later with
`PATCH /v1/inboxes/{id}/messages/{messageId}` or
`PATCH /v1/inboxes/{id}/threads/{threadId}`, and filter lists with
`?labels=a,b`. Labels are stored lowercase and deduplicated; at most 32
per message, each at most 64 characters, no commas.
- **Open tracking**: `trackOpens: true` on a send with an HTML body adds a
1x1 pixel (the stored copy never contains it). The first open fires
`message.opened`. Opens are a hint, not proof: Apple Mail Privacy
Protection fetches images on delivery, and clients that block images
never report an open.
- **Scheduled send**: set `sendAt` on a draft and Anjal sends it at that
time. See the Drafts endpoints.
- **Delivery status**: sent messages carry `deliveryStatus`
(`delivered`, `bounced`, `complained` or `rejected`; null until known)
and `deliveredAt`.
## Receiving mail
Inbound mail arrives via SMTP on the default domain or any verified
custom domain. Subscribe to delivery events either by registering a
webhook (`POST /v1/webhooks`) or by opening a WebSocket
(`wss://anjal.app/v1/ws`) — useful for in-line 2FA flows where
the agent doesn''t have a public webhook receiver.'
servers:
- url: https://anjal.app
description: Production
security:
- apiKeyHeader: []
tags:
- name: Inboxes
description: Email mailboxes that can send, receive, and store threads.
paths:
/v1/inboxes:
post:
tags:
- Inboxes
summary: Create an inbox (idempotent on requested username)
description: 'Requires tenant master or workspace master key.
## Username allocation
- If `username` is set and your tenant **already** has an inbox at
`username@domain`, returns that inbox (HTTP **200**, body
contains `existed: true`). The call is idempotent — safe to
retry.
- Otherwise tries to create `username@domain`. If the global
username is taken by another tenant, falls back to
`username1`, `username2`, … through `username99`. The actual
allocated username is in the response body.
- Pass `?strict=true` to disable auto-fallback. With strict mode,
a global collision returns HTTP **409** with the conflicting
resource info instead of suffixing.
IMAP/SMTP credentials — `apiKey` and `imapPassword` — are only
returned on first creation, never on idempotent returns.'
operationId: createInbox
parameters:
- name: strict
in: query
description: 'When `true`, the request fails with `409 USERNAME_TAKEN` if
the requested `username@domain` is already taken by another
tenant. When omitted (default), we auto-fallback to
`username1`, `username2`, etc.
'
schema:
type: boolean
default: false
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
username:
type: string
description: 'Lowercase alphanumeric + `.-_`. Auto-generated from
the tenant name if omitted. **No tenant-id suffix is
appended** — collisions are resolved via sequential
numeric fallback (e.g. `scout`, `scout1`, `scout2`).
'
displayName:
type: string
domain:
type: string
description: Defaults to anjal.app
workspaceId:
type: string
format: uuid
description: (Tenant master only) target workspace. Defaults to the tenant's default workspace.
responses:
'200':
description: 'Idempotent return — your tenant already has an inbox at the
requested `username@domain`. `existed: true` is set on the
body. `apiKey` and `imapPassword` are NOT re-issued.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Inbox'
'201':
description: Newly created. `apiKey` and `imapPassword` are returned once — store them now.
content:
application/json:
schema:
$ref: '#/components/schemas/Inbox'
'403':
description: Plan limit reached
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: '`USERNAME_TAKEN` — only returned when `?strict=true` is set
and `username@domain` is taken globally. Body includes
`requestedUsername`, `attemptedUsername`, and `domain` for
single-round-trip recovery. Default behavior auto-falls
back without ever returning 409.
'
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
get:
tags:
- Inboxes
summary: List inboxes
operationId: listInboxes
parameters:
- name: workspace
in: query
description: Tenant master only — filter to a single workspace.
schema:
type: string
format: uuid
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
inboxes:
type: array
items:
$ref: '#/components/schemas/Inbox'
/v1/inboxes/{id}:
parameters:
- $ref: '#/components/parameters/InboxId'
get:
tags:
- Inboxes
summary: Get an inbox
description: Returns the inbox detail including IMAP/SMTP credentials and the full address list.
operationId: getInbox
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Inbox'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags:
- Inboxes
summary: Update inbox display name or metadata
description: Username, domain, and canonical identity are immutable. Use `POST /v1/inboxes/{id}/addresses` to change the outbound From.
operationId: updateInbox
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
displayName:
type: string
metadata:
type: object
additionalProperties: true
responses:
'200':
description: Updated
content:
application/json:
schema:
type: object
properties:
updated:
type: boolean
id:
type: string
format: uuid
delete:
tags:
- Inboxes
summary: Delete an inbox
description: Soft-deletes the inbox, hard-deletes its alias rows, and removes the Stalwart principal. Prior threads and messages are preserved but hidden from listings.
operationId: deleteInbox
responses:
'200':
description: Deleted
content:
application/json:
schema:
type: object
properties:
deleted:
type: boolean
id:
type: string
format: uuid
components:
schemas:
ImapCreds:
type: object
properties:
host:
type: string
example: imap.anjal.app
port:
type: integer
example: 993
tls:
type: boolean
username:
type: string
password:
type: string
InboxAddress:
type: object
properties:
address:
type: string
example: scout-abc123@delegated.cc
isPrimary:
type: boolean
SmtpCreds:
type: object
properties:
host:
type: string
example: smtp.anjal.app
port:
type: integer
example: 587
starttls:
type: boolean
username:
type: string
password:
type: string
Inbox:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
description: Current primary outbound address — may be a custom-domain alias.
example: scout-abc123@delegated.cc
canonicalEmail:
type: string
description: Stable identity used for IMAP/SMTP auth. Never changes after creation.
example: scout-abc123-10deac42@send.anjal.app
username:
type: string
domain:
type: string
displayName:
type: string
workspaceId:
type: string
format: uuid
addresses:
type: array
items:
$ref: '#/components/schemas/InboxAddress'
imapPassword:
type: string
description: Only returned on creation and GET /inboxes/{id}. Stored plaintext so IMAP clients can log in.
imap:
$ref: '#/components/schemas/ImapCreds'
smtp:
$ref: '#/components/schemas/SmtpCreds'
createdAt:
type: string
format: date-time
Error:
type: object
properties:
error:
type: string
code:
type: string
example: VALIDATION_ERROR
required:
- error
parameters:
InboxId:
name: id
in: path
required: true
schema:
type: string
format: uuid
description: Inbox UUID
responses:
NotFound:
description: Resource not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
securitySchemes:
apiKeyHeader:
type: apiKey
in: header
name: X-API-Key
description: 'Any of `sa_`, `tk_`, `wk_`, `ak_`, or `mam_` keys. Scope is enforced
per-endpoint — see individual operation descriptions.
'