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-websocket-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 WebSocket 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: Websocket
description: Real-time event stream. The agentmail.to spec-compatible wire protocol.
paths:
/v1/ws:
get:
tags:
- Websocket
summary: Open a WebSocket connection
description: 'Upgrade this GET to a WebSocket. After the handshake, the server
sends a `connected` frame and the client sends a `subscribe`
frame to start receiving events.
This protocol is wire-compatible with agentmail.to''s WebSocket
API — `workspace_ids` also accepts `pod_ids` as an alias.
### Client → server frames
```json
{ "type": "subscribe",
"event_types": ["message.received", "message.sent"],
"inbox_ids": ["..."],
"workspace_ids": ["..."] }
```
### Server → client frames
```json
{ "type": "connected", "inboxId": "...", "email": "..." }
{ "type": "subscribed", "event_types": [...], "inbox_ids": [...], "workspace_ids": [...] }
{ "type": "event",
"event_type": "message.received",
"event_id": "evt_...",
"message": { "inbox_id": "...", "thread_id": "...", "from": "...", "to": ["..."], "subject": "...", "plain_body": "...", "html_body": "...", "timestamp": "..." },
"thread": { "thread_id": "...", "subject": "..." } }
{ "type": "ping" }
{ "type": "error", "message": "..." }
```
Use `?api_key=` as a query parameter when your client
(e.g. browser `new WebSocket`) can''t set the `X-API-Key`
header.'
operationId: openWebSocket
responses:
'101':
description: Switching protocols
'401':
$ref: '#/components/responses/Unauthorized'
components:
responses:
Unauthorized:
description: Missing or invalid API key
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
schemas:
Error:
type: object
properties:
error:
type: string
code:
type: string
example: VALIDATION_ERROR
required:
- 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.
'