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-webhooks-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 Webhooks 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: Webhooks
description: HTTP callbacks fired on every inbox event.
paths:
/v1/webhooks:
post:
tags:
- Webhooks
summary: Create a webhook
description: Subscribe a URL to one or more events. See `WebhookEvent` for the full list and the newer payloads.
operationId: createWebhook
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- url
- events
properties:
url:
type: string
format: uri
events:
type: array
minItems: 1
items:
$ref: '#/components/schemas/WebhookEvent'
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Webhook'
get:
tags:
- Webhooks
summary: List webhooks
operationId: listWebhooks
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
webhooks:
type: array
items:
$ref: '#/components/schemas/Webhook'
/v1/webhooks/{id}:
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
get:
tags:
- Webhooks
summary: Get a webhook
operationId: getWebhook
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Webhook'
patch:
tags:
- Webhooks
summary: Update a webhook
description: Change url, events, or enabled state.
operationId: updateWebhook
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
url:
type: string
format: uri
events:
type: array
minItems: 1
items:
$ref: '#/components/schemas/WebhookEvent'
isActive:
type: boolean
responses:
'200':
description: Updated
delete:
tags:
- Webhooks
summary: Delete a webhook
operationId: deleteWebhook
responses:
'200':
description: Deleted
components:
schemas:
Webhook:
type: object
properties:
id:
type: string
format: uuid
url:
type: string
format: uri
events:
type: array
items:
$ref: '#/components/schemas/WebhookEvent'
secret:
type: string
description: HMAC-SHA256 secret. Only returned on creation.
isActive:
type: boolean
createdAt:
type: string
format: date-time
WebhookEvent:
type: string
enum:
- message.received
- message.received.spam
- message.sent
- message.delivered
- message.bounced
- message.complained
- message.rejected
- message.opened
- draft.send_failed
- domain.verified
- message.suspicious
- message.quarantined
- calendar.invite.received
- calendar.invite.replied
- calendar.invite.cancelled
- calendar.invite.countered
- account.suspended
- account.payment_failed
- account.subscription_ended
description: "Payloads for the newer events:\n\n- `message.received.spam`: inbound mail the spam filter flagged. It is\n stored but does not fire `message.received`.\n- `message.sent`: includes `labels`.\n- `message.delivered`: `{ messageId, inboxId, recipients, timestamp, smtpResponse }`.\n The recipient's mail server accepted it; not a guarantee of inbox placement.\n- `message.rejected`: `{ messageId, inboxId, reason, recipients, timestamp }`.\n The provider refused to send it (e.g. a suppressed address).\n- `message.opened`: `{ messageId, inboxId, threadId, to, subject, openedAt, userAgent }`.\n First open of a send with `trackOpens: true`. A hint, not proof.\n- `draft.send_failed`: `{ draftId, inboxId, error, code }`. A scheduled\n draft could not be sent; it is kept with `sendError` set.\n- `domain.verified`: `{ domainId, domain, workspaceId, sharedWith, verifiedAt }`.\n Fired when `GET /v1/domains/{domain}/verify` first sees the domain verified.\n"
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.
'