Work with this as data
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.
Call it yourself
curl for this page
This API
curl "https://apis.io/api/v1/apis/botmail-pro:botmail-pro-mail-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
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 Specification
openapi: 3.2.0
info:
title: Botmail Mail API
version: 1.0.0
summary: Gmail for AI agents.
description: 'Real email mailboxes for AI agents. Agents claim an address with
`POST /v1/claim`, a human claims it in the browser, then the same API key
reads, sends and manages mail.
**Errors** always look like `{"error": {"code", "message", "hint?", "details?"}}`.
`code` is stable and machine-readable; `hint` says what to do next.
429 responses carry `Retry-After` (seconds).
**IDs** are prefixed strings: `acct_`, `mbx_`, `thr_`, `msg_`, `key_`, `clm_`.
Times are RFC 3339 UTC.
**Scopes**: `read`, `send`, `manage`. Pending claim keys (`bm_pending_…`)
have `read` + `invite` until a human claims the mailbox, then they gain
full access in place. `send` is dropped while the account is not `active`.
Each operation lists its required scope.'
servers:
- url: https://botmail.pro
security:
- bearerAuth: []
- cookieAuth: []
tags:
- name: Mail
description: Threads, messages, attachments and labels.
paths:
/v1/threads:
get:
tags:
- Mail
operationId: listThreads
summary: List threads
description: 'Scope: read. Pending claim keys allowed. Newest first. Page by passing
`next_before` as `before` until it is null.'
parameters:
- $ref: '#/components/parameters/MailboxIdQuery'
- name: folder
in: query
schema:
type: string
enum:
- inbox
- sent
- archive
- starred
- spam
- trash
- held
- all
default: inbox
description: '`held` = threads with mail held because the account is over its storage quota.'
- name: label
in: query
schema:
type: string
- name: unread
in: query
description: '`1` = only threads with unread messages.'
schema:
type: string
enum:
- '1'
- name: q
in: query
description: Full-text search (websearch syntax, e.g. `invoice -draft "due date"`).
schema:
type: string
- name: from
in: query
description: Comma-separated. Only threads with a message whose sender address or name contains one of these (e.g. `github.com`). The message filters (q, from, to, has_attachment, since, until) must all match the same message.
schema:
type: string
- name: to
in: query
description: Comma-separated. A to or cc recipient of the message contains one of these.
schema:
type: string
- name: has_attachment
in: query
description: '`1` = the message has attachments.'
schema:
type: string
enum:
- '1'
- name: since
in: query
description: Message on or after this time. `2026-10-01`, an RFC 3339 time, or an age such as `30m`, `12h`, `7d`, `2w`.
schema:
type: string
- name: until
in: query
description: Message before this time; a `YYYY-MM-DD` date includes that whole day (UTC).
schema:
type: string
- name: before
in: query
description: Only threads whose last message is older than this.
schema:
type: string
format: date-time
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 25
responses:
'200':
description: Threads.
content:
application/json:
schema:
type: object
required:
- threads
- next_before
properties:
threads:
type: array
items:
$ref: '#/components/schemas/Thread'
next_before:
type:
- string
- 'null'
format: date-time
description: Pass as `before` for the next page; null when there are no more.
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
patch:
tags:
- Mail
operationId: updateThreads
summary: Update thread flags and labels
description: 'Scope depends on the change: `read` is enough for read/unread;
archived, starred and labels need `send` or `manage`; trashed and
spam need `manage`. Threads the credential can''t access are skipped.'
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- thread_ids
- update
additionalProperties: false
properties:
thread_ids:
type: array
minItems: 1
maxItems: 100
items:
type: string
update:
$ref: '#/components/schemas/ThreadUpdate'
responses:
'200':
description: Number of threads updated.
content:
application/json:
schema:
type: object
required:
- updated
properties:
updated:
type: integer
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/v1/threads/{id}:
get:
tags:
- Mail
operationId: getThread
summary: Get a thread with its messages
description: 'Scope: read. Pending claim keys allowed. Held messages (account over
storage quota) have `status: "held"` and an empty `text` until released.'
parameters:
- $ref: '#/components/parameters/IdPath'
- name: mark_read
in: query
description: '`1` = mark the thread read.'
schema:
type: string
enum:
- '1'
responses:
'200':
description: The thread and its messages, oldest first.
content:
application/json:
schema:
type: object
required:
- thread
- messages
properties:
thread:
$ref: '#/components/schemas/Thread'
messages:
type: array
items:
$ref: '#/components/schemas/Message'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/messages/{id}:
parameters:
- $ref: '#/components/parameters/IdPath'
get:
tags:
- Mail
operationId: getMessage
summary: Get a message
description: 'Scope: read. Pending claim keys allowed. With `format=html` the
response adds sanitized `html` and `blocked_images`.'
parameters:
- name: format
in: query
schema:
type: string
enum:
- html
description: '`html` = include sanitized HTML. Remote images are removed and counted in `blocked_images`; `cid:` images point at the attachment URL.'
- name: images
in: query
schema:
type: string
enum:
- '1'
description: '`1` = keep remote images in `html`.'
responses:
'200':
description: The message.
content:
application/json:
schema:
$ref: '#/components/schemas/MessageWithHTML'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
delete:
tags:
- Mail
operationId: deleteMessage
summary: Delete a message permanently
description: 'Scope: manage. Frees storage. Empty threads are removed.'
responses:
'200':
description: Deleted.
content:
application/json:
schema:
$ref: '#/components/schemas/Deleted'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/messages/{id}/raw:
get:
tags:
- Mail
operationId: getMessageRaw
summary: Raw MIME
description: 'Scope: read. Pending claim keys allowed. Exactly as received or sent.'
parameters:
- $ref: '#/components/parameters/IdPath'
responses:
'200':
description: RFC 822 message.
content:
message/rfc822:
schema:
type: string
format: binary
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/messages/{id}/attachments/{attachment_id}:
get:
tags:
- Mail
operationId: getAttachment
summary: Download an attachment
description: 'Scope: read. Pending claim keys allowed. Served with the attachment''s
Content-Type and an `attachment` Content-Disposition. 413
attachment_too_large over 25 MB.'
parameters:
- $ref: '#/components/parameters/IdPath'
- name: attachment_id
in: path
required: true
schema:
type: string
- name: disposition
in: query
description: '`inline` is honored only for png, jpeg, gif and webp.'
schema:
type: string
enum:
- inline
responses:
'200':
$ref: '#/components/responses/AttachmentBytes'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'413':
$ref: '#/components/responses/TooLarge'
/v1/attachments/signed:
get:
tags:
- Mail
operationId: getSignedAttachment
summary: Download an attachment by signed link
description: 'No auth. `t` is a one-time token issued by the MCP `get_attachment`
tool; it expires after 5 minutes. 410 link_expired, 404 not found.'
security:
- {}
parameters:
- name: t
in: query
required: true
schema:
type: string
responses:
'200':
$ref: '#/components/responses/AttachmentBytes'
'404':
$ref: '#/components/responses/NotFound'
'410':
$ref: '#/components/responses/Gone'
'413':
$ref: '#/components/responses/TooLarge'
/v1/labels:
get:
tags:
- Mail
operationId: listLabels
summary: List labels with thread counts
description: 'Scope: read. Pending claim keys allowed.'
parameters:
- $ref: '#/components/parameters/MailboxIdQuery'
responses:
'200':
description: Labels, alphabetical.
content:
application/json:
schema:
type: object
required:
- labels
properties:
labels:
type: array
items:
$ref: '#/components/schemas/LabelCount'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/v1/labels/{label}:
parameters:
- name: label
in: path
required: true
schema:
type: string
patch:
tags:
- Mail
operationId: updateLabel
summary: Rename or recolor a label
description: 'Scope: send or manage. `name` renames the label on every conversation
this credential can reach (merging into an existing label of that
name); the color moves with it. `color` sets a display color
(`#rrggbb`), or `""` clears it. Renaming a label no conversation has
returns 404 label_not_found.'
parameters:
- $ref: '#/components/parameters/MailboxIdQuery'
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
properties:
name:
type: string
maxLength: 64
color:
type: string
pattern: ^(#[0-9a-fA-F]{6})?$
responses:
'200':
description: The label after the change.
content:
application/json:
schema:
type: object
properties:
label:
type: string
color:
type: string
conversations_renamed:
type: integer
'400':
$ref: '#/components/responses/BadRequest'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
components:
schemas:
Message:
type: object
required:
- id
- thread_id
- mailbox_id
- uid
- direction
- status
- message_id_header
- from
- to
- subject
- snippet
- size_bytes
- has_attachments
- auto_submitted
- verdicts
- seen
- starred
- labels
- created_at
properties:
id:
type: string
thread_id:
type: string
mailbox_id:
type: string
uid:
type: integer
format: int64
description: Monotonic per-mailbox counter.
direction:
type: string
enum:
- in
- out
status:
type: string
description: 'Inbound: `received`, or `held` (account over storage quota; body
hidden until released). Outbound: `queued`, `held`, `blocked`,
`sent`, `delivered`, `bounced`, `complained`, `failed`.
'
examples:
- received
- held
- queued
- sent
- delivered
- bounced
- complained
- blocked
- failed
status_reason:
type: string
description: 'Outbound only, on GET /v1/messages/{id}: why the message is held,
blocked, bounced, complained or failed (e.g. the classifier verdict).
'
message_id_header:
type: string
description: Message-ID without angle brackets.
in_reply_to:
type: string
references:
type: array
items:
type: string
from:
type: string
from_name:
type: string
to:
type: array
items:
type: string
cc:
type: array
items:
type: string
bcc:
type: array
items:
type: string
reply_to:
type: string
subject:
type: string
snippet:
type: string
text:
type: string
description: Plain-text body (up to 64 KB; full body via `/raw`). Empty for held messages.
size_bytes:
type: integer
format: int64
has_attachments:
type: boolean
attachments:
type: array
items:
$ref: '#/components/schemas/AttachmentMeta'
auto_submitted:
type: boolean
description: Sent by an automated system.
verdicts:
$ref: '#/components/schemas/Verdicts'
seen:
type: boolean
starred:
type: boolean
labels:
type: array
items:
type: string
sent_at:
type: string
format: date-time
created_at:
type: string
format: date-time
MessageWithHTML:
allOf:
- $ref: '#/components/schemas/Message'
- type: object
properties:
html:
type: string
description: Sanitized HTML. Only with `format=html`.
blocked_images:
type: integer
description: Remote images removed from `html`. Only with `format=html`.
ErrorResponse:
type: object
required:
- error
properties:
error:
$ref: '#/components/schemas/Error'
LabelCount:
type: object
required:
- label
- threads
properties:
label:
type: string
threads:
type: integer
color:
type: string
description: '#rrggbb, when set with PATCH /v1/labels/{label}'
Deleted:
type: object
required:
- id
- deleted
properties:
id:
type: string
deleted:
type: boolean
const: true
Verdicts:
type: object
description: Inbound authentication and scanning results (PASS, FAIL, GRAY, …).
properties:
spam:
type: string
virus:
type: string
spf:
type: string
dkim:
type: string
dmarc:
type: string
AttachmentMeta:
type: object
required:
- id
- part_index
- filename
- content_type
- inline
- size_bytes
properties:
id:
type: string
part_index:
type: integer
filename:
type: string
content_type:
type: string
content_id:
type: string
inline:
type: boolean
size_bytes:
type: integer
format: int64
Error:
type: object
required:
- code
- message
properties:
code:
type: string
description: Stable machine-readable code, e.g. `not_found`, `name_taken`, `rate_limited`.
message:
type: string
hint:
type: string
description: What to do next.
details:
type: object
additionalProperties: true
Thread:
type: object
required:
- id
- mailbox_id
- subject
- snippet
- message_count
- unread_count
- last_message_at
- archived
- starred
- trashed
- spam
- labels
- participants
properties:
id:
type: string
mailbox_id:
type: string
subject:
type: string
snippet:
type: string
message_count:
type: integer
unread_count:
type: integer
last_message_at:
type: string
format: date-time
archived:
type: boolean
starred:
type: boolean
trashed:
type: boolean
spam:
type: boolean
labels:
type: array
items:
type: string
participants:
type: array
description: Sender addresses (up to 6).
items:
type: string
ThreadUpdate:
type: object
additionalProperties: false
minProperties: 1
properties:
read:
type: boolean
description: 'Scope: read.'
archived:
type: boolean
description: 'Scope: send or manage.'
starred:
type: boolean
description: 'Scope: send or manage.'
trashed:
type: boolean
description: 'Scope: manage.'
spam:
type: boolean
description: 'Scope: manage.'
add_labels:
type: array
description: 'Scope: send or manage.'
items:
type: string
remove_labels:
type: array
description: 'Scope: send or manage.'
items:
type: string
responses:
BadRequest:
description: Invalid request (`bad_request` or a more specific code).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Unauthorized:
description: Missing or invalid credentials (`unauthorized`, `invalid_token`).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Gone:
description: No longer available (expired link or claim).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
Forbidden:
description: 'The credential can''t do this (`forbidden`, `csrf`, `account_inactive`, …).
Missing scopes are listed in `details.required_scope`; pending keys get
a hint to wait for the claim.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
TooLarge:
description: Too large (`message_too_large` over 10 MB, `attachment_too_large` over 25 MB).
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
AttachmentBytes:
description: Attachment bytes with the stored Content-Type.
headers:
Content-Disposition:
schema:
type: string
example: attachment; filename="invoice.pdf"
content:
application/octet-stream:
schema:
type: string
format: binary
'*/*':
schema:
type: string
format: binary
NotFound:
description: Not found or not accessible with this credential.
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
parameters:
MailboxIdQuery:
name: mailbox_id
in: query
description: Limit to one mailbox. Omit for every mailbox the credential can access.
schema:
type: string
IdPath:
name: id
in: path
required: true
schema:
type: string
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: '`Authorization: Bearer <key>` with an API key (`bm_live_…`) or a
pending claim key (`bm_pending_…`). OAuth 2.1 access tokens
(`bm_oat_…`) are audience-bound to the MCP endpoint `/mcp` and are
rejected on `/v1`; MCP clients discover OAuth via
`/.well-known/oauth-protected-resource/mcp`.
'
cookieAuth:
type: apiKey
in: cookie
name: bm_session
description: 'Web session for signed-in humans. Non-GET requests must send a JSON
`Content-Type` or `X-Botmail-CSRF: 1` (403 csrf otherwise).
'