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/groundmail-dev:groundmail-dev-inbound-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: Groundmail Inbound API
version: 0.16.0
description: 'Transactional email API for developers.
Auth: `Authorization: Bearer gm_…` with an API key, or `Bearer gm_oat_…` with an OAuth
access token from the device flow (`groundmail login`). Dashboard session cookies work
for integrations.
Azure managed identity (keyless): an Azure workload can send a Microsoft Entra access
token (a JWT) as the Bearer credential instead of a key. Register the identity''s tenant
ID, object ID and token audience in the dashboard (API keys → Azure managed identity);
it then has the same access options as an API key (scopes, domain limit, tenant).
App-only tokens only. Any authentication failure is the same 401 `unauthorized`.
See https://docs.groundmail.dev/authentication/azure-managed-identity.
Test mode: a test key (`gm_test_…`, `POST /api-keys` with `test: true`) behaves
like a live key, but its sends are never delivered: they are stored with
`test: true`, get simulated events and webhooks (`data.test: true`), and do not
count toward plan limits, the sending ramp, stats or billing. Simulator
addresses (`delivered@`, `bounce@`, `block@`, `complaint@`, `suppressed@`,
`delayed@simulator.groundmail.dev`, plus addressing allowed) produce one outcome
each with any key. See https://docs.groundmail.dev/testing.
Tenants: a platform that sends email for its own customers creates one tenant per
customer (`/tenants`), each with its own domains, suppression list, stats, sending
ramp and pause switch. Name the tenant with `tenant` on sends, domains and
suppressions, or send from a domain the tenant owns. API keys and webhook
endpoints can be limited to tenants. Included on pay as you go and the Dedicated plan;
elsewhere a request that names a tenant gets 403 `tenants_not_enabled`. See
https://docs.groundmail.dev/tenants.
Data retention: sent emails (content and events) and received emails are kept for
your plan''s window, then deleted: 7 days on Free, 30 on Pro, 90 on Scale, up to 365
on Dedicated. Past it, `GET /emails/{id}` and `GET /received-emails/{id}` return
404 `not_found` and lists no longer include them. Aggregate stats
(`GET /deliverability`) are kept. Store webhook events to keep history longer. See
https://docs.groundmail.dev/emails#data-retention.
Scopes: every credential carries scopes (`full`, `emails:send`, `emails:read`,
`domains:read`, `domains:write`, `webhooks:write`, `templates:write`,
`suppressions:write`, `account:read`). Each operation lists the scope it needs under
`x-required-scope` (space-separated: all of them; `a | b`: either); a credential
without it gets 403 `insufficient_scope` with `required_scope`. `full` grants everything; `domains:write` includes `domains:read`.
Legacy `sending` API keys have `emails:send` + `emails:read`.
Production base URL: https://api.groundmail.dev'
servers:
- url: https://api.groundmail.dev
description: Production
tags:
- name: Inbound
description: Receive domains, inbound ingestion, and received email retrieval
paths:
/received-emails:
get:
operationId: listReceivedEmails
x-required-scope: emails:read
summary: List received emails
tags:
- Inbound
security:
- bearerAuth: []
parameters:
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 25
- name: cursor
in: query
schema:
type: string
- name: domain
in: query
schema:
type: string
description: Filter by receiving domain name
- name: in_reply_to_email
in: query
schema:
type: string
description: 'Only replies to this sent email (`msg_…`): received emails whose `in_reply_to_email.id` is this id.'
responses:
'200':
description: Received email list
content:
application/json:
schema:
$ref: '#/components/schemas/ListReceivedEmailsResponse'
'401':
description: Missing or invalid API key / session
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
/received-emails/{id}:
get:
operationId: getReceivedEmail
x-required-scope: emails:read
summary: Retrieve a received email with attachment data
tags:
- Inbound
security:
- bearerAuth: []
parameters:
- name: id
in: path
required: true
description: Received email public id (`inb_…`)
schema:
type: string
responses:
'200':
description: Received email detail
content:
application/json:
schema:
$ref: '#/components/schemas/ReceivedEmailResponse'
'401':
description: Missing or invalid API key / session
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
'404':
description: 'Received email not found, or past your plan''s log retention and deleted.
See https://docs.groundmail.dev/emails#data-retention.
'
content:
application/json:
schema:
$ref: '#/components/schemas/ErrorResponse'
components:
schemas:
InReplyToEmail:
type: object
description: The sent email a received email replies to, as it was when the reply arrived.
required:
- id
- subject
- from
- to
- sent_at
- tags
- tenant
properties:
id:
type: string
description: The sent email's id (`msg_…`). Pass it to `GET /emails/{id}` while it is within data retention.
subject:
type: string
from:
type: string
to:
type: array
items:
type: string
sent_at:
type: string
format: date-time
tags:
type: array
description: The email's tags as sent (`[]` when it had none).
items:
$ref: '#/components/schemas/EmailTag'
tenant:
type: string
nullable: true
description: The `external_id` of the tenant it was sent for; null when it was not sent as a tenant.
ErrorResponse:
type: object
required:
- error
properties:
error:
type: object
required:
- message
properties:
message:
type: string
description: What went wrong, in plain English. Wording can change; branch on `code`.
code:
$ref: '#/components/schemas/ErrorCode'
hint:
type: string
description: 'How to fix the error. Specific to the failure when known (for example a sandbox recipient), otherwise the default for `code`.
'
docs_url:
type: string
format: uri
description: Link to this code on the errors page, `https://docs.groundmail.dev/errors#<code>`.
example: https://docs.groundmail.dev/errors#validation_error
upgrade_url:
type: string
description: 'Dashboard path to upgrade when code is plan_limit or domain_limit. Absent for `daily_ramp_limit`, which upgrading does not lift.
'
required_scope:
type: string
description: 'With `insufficient_scope`: the scope (or space-separated scopes) the operation needs. Also sent as `WWW-Authenticate: Bearer error="insufficient_scope", scope="…"`.
'
details:
description: 'Optional structured details. For send blocks this may include `suppressed` (global suppression list) or `category_unsubscribed` plus `category` and `email_id` (category opt-out). A `daily_ramp_limit` carries `stream` (`transactional` or `product`), `daily_limit` (today''s recipient limit for that stream), `used_today`, `recipients` (in this send), `ramp_stage` and `resets_at` (next 00:00 UTC, ISO 8601), plus `tenant` when the tenant''s own limit refused the send.
'
EmailTag:
type: object
required:
- name
- value
properties:
name:
type: string
value:
type: string
ReceivedEmailAttachment:
allOf:
- $ref: '#/components/schemas/ReceivedEmailAttachmentMeta'
- type: object
properties:
data:
type: string
format: byte
description: Base64-encoded attachment content. Present on retrieve responses, omitted from list responses.
ErrorCode:
type: string
description: 'Stable, machine-readable error code. Each code is documented at
`https://docs.groundmail.dev/errors#<code>`. New codes may be added;
existing codes keep their meaning.
- `validation_error` (400, 422): the request failed validation, or every recipient is suppressed or opted out (422). Fix the field named in `message`/`details`.
- `unauthorized` (401): missing, malformed or revoked API key. Send `Authorization: Bearer gm_…`.
- `forbidden` (403): the key is valid but the action is not allowed (unverified `from` domain, sandbox send to someone other than the account email, no payment method on file, sending restricted).
- `insufficient_scope` (403): the API key''s scope does not cover this endpoint.
- `domain_not_allowed` (403): the API key is limited to other sending domains than the one in `from`, or `from` is on another tenant''s domain.
- `tenants_not_enabled` (403): the request names a tenant, but tenants are not enabled for this account. They are included on pay as you go and the Dedicated plan.
- `newsletter_not_enabled` (403): the send uses `category: "newsletter"`, but newsletter sending isn''t enabled on this account. Newsletter email is accepted only for approved accounts.
- `not_found` (404): no resource with that id for this team.
- `conflict` (409): the resource already exists or its state blocks the change.
- `plan_limit` (429): the plan''s monthly or daily sending limit was reached. See `upgrade_url`.
- `daily_ramp_limit` (429): the send would go over a new account''s daily ramp-up limit for its stream, or its tenant''s own ramp or daily limit (`details.tenant`). Upgrading does not lift it; wait for `details.resets_at`.
- `domain_limit` (403): the plan''s domain limit was reached. See `upgrade_url`.
- `idempotency_conflict` (409): the `Idempotency-Key` was reused with a different body, or the first request is still running.
- `rate_limited` (429): too many requests. Wait `Retry-After` seconds.
- `timeout` (408): a long-poll (GET /test-inbox/wait) ended before a matching email arrived. Safe to call again.
- `upstream_error` (502, 503, 504, or a passed-through 4xx): the mail server did not accept the request. 502/503 are safe to retry with the same `Idempotency-Key`; 504 means the outcome is unknown.
- `not_configured` (503): the feature is temporarily unavailable.
- `internal_error` (500): unexpected server error. Retry with backoff.
'
enum:
- validation_error
- unauthorized
- forbidden
- insufficient_scope
- not_found
- conflict
- plan_limit
- daily_ramp_limit
- domain_limit
- idempotency_conflict
- rate_limited
- timeout
- upstream_error
- not_configured
- internal_error
- domain_not_allowed
- tenants_not_enabled
- newsletter_not_enabled
ReceivedEmailAttachmentMeta:
type: object
required:
- filename
- content_type
- size
properties:
filename:
type: string
content_type:
type: string
size:
type: integer
content_id:
type: string
nullable: true
ReceivedEmailResponse:
type: object
required:
- received_email
properties:
received_email:
$ref: '#/components/schemas/ReceivedEmail'
ReceivedEmail:
type: object
required:
- id
- domain_id
- domain_name
- status
- rcpt_to
- mail_from
- from
- to
- cc
- subject
- spam_status
- spam_score
- bounced
- received_with_ssl
- message_id
- in_reply_to
- references
- in_reply_to_email
- attachment_count
- attachments
- text_body
- html_body
- auto_submitted
- received_at
- created_at
properties:
id:
type: string
description: Received email public id (`inb_…`)
domain_id:
type: string
domain_name:
type: string
status:
type: string
enum:
- received
rcpt_to:
type: string
format: email
description: Envelope recipient as received by Groundmail.
mail_from:
type: string
nullable: true
description: SMTP envelope sender.
from:
type: string
to:
type: array
items:
type: string
format: email
cc:
type: array
items:
type: string
format: email
subject:
type: string
spam_status:
type: string
enum:
- clean
- suspicious
- spam
nullable: true
description: 'Content scanning verdict: `spam` at or above the domain''s spam threshold (default 10), `suspicious` from 5, else `clean`. Null when the message was not scanned.'
spam_score:
type: number
nullable: true
description: Content scanning score. Higher is more likely spam; most clean mail scores under 5. Null when not scanned.
bounced:
type: boolean
received_with_ssl:
type: boolean
nullable: true
message_id:
type: string
nullable: true
in_reply_to:
type: string
nullable: true
references:
type: array
items:
type: string
in_reply_to_email:
allOf:
- $ref: '#/components/schemas/InReplyToEmail'
nullable: true
description: The email you sent through Groundmail that this one replies to, matched by its `In-Reply-To` header, then `References`. A copy taken when the reply arrived, so it is still here after the sent email passes your plan's data retention. Null when the reply matches none of your sent emails.
attachment_count:
type: integer
attachments:
type: array
items:
$ref: '#/components/schemas/ReceivedEmailAttachment'
text_body:
type: string
nullable: true
html_body:
type: string
nullable: true
auto_submitted:
type: string
nullable: true
received_at:
type: string
format: date-time
created_at:
type: string
format: date-time
ListReceivedEmailsResponse:
type: object
required:
- received_emails
- has_more
- next_cursor
properties:
received_emails:
type: array
items:
$ref: '#/components/schemas/ReceivedEmail'
has_more:
type: boolean
next_cursor:
type: string
nullable: true
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'Groundmail API key (`gm_…`), OAuth access token (`gm_oat_…`), or a Microsoft Entra access token from an Azure managed identity registered in the dashboard (keyless; see https://docs.groundmail.dev/authentication/azure-managed-identity). All carry scopes; see the API description.
'