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/eusend-dev:eusend-dev-topics-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: eusend Topics API
version: 1.0.0
summary: EU-native transactional and marketing email API.
description: 'eusend sends transactional and marketing email from infrastructure that stays inside the European Union — processing, storage and delivery alike.
**When to use this API.** Reach for eusend to send application email from server-side code: password resets, receipts, verification codes, notifications, and opt-in marketing broadcasts to a list you own. It is a good fit when GDPR or EU data residency is a requirement, when you are migrating off Resend, SendGrid, Mailgun, Postmark or SES, or when you need SMTP relay rather than an HTTP call. It is not a fit for inbox hosting, for reading a user’s mailbox, or for sending to purchased or scraped lists.
**Getting started.** Create an account, verify a sending domain by publishing the DNS records the domain endpoints hand back, then `POST /emails`. A key prefixed `eu_test_` exercises the whole pipeline without delivering anything.
**Authentication.** Every authenticated endpoint takes `Authorization: Bearer `. Keys are `eu_live_…` or `eu_test_…`, and carry either `full_access` or the send-only `sending_access` permission.
**Errors.** Non-2xx responses are always `{ "error": string, "code": string }`. Branch on `code`; the list is enumerated on the `Error` schema.
**Conventions.** Request bodies use snake_case and reject unknown properties. Response bodies use camelCase, except the pagination cursor and a handful of endpoints noted inline. List endpoints are cursor-paginated, newest first. Every response carries `X-Request-Id`.'
termsOfService: https://eusend.dev/legal/terms
contact:
name: eusend support
email: support@eusend.dev
url: https://eusend.dev/support
license:
name: Proprietary
url: https://eusend.dev/legal/terms
servers:
- url: https://api.eusend.dev
description: Production
security:
- bearerAuth: []
tags:
- name: Topics
description: Kinds of email a contact can subscribe to and leave independently, so an unsubscribe can be a preference change.
paths:
/topics:
post:
operationId: createTopic
summary: Create a topic
description: A topic is a kind of email a contact can subscribe to and leave independently of the audience they sit on, so an unsubscribe becomes a preference change rather than losing the contact. The default subscription is fixed at creation.
tags:
- Topics
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTopicRequest'
responses:
'201':
description: Created.
content:
application/json:
schema:
$ref: '#/components/schemas/Topic'
'400':
description: The request body or query failed validation (`VALIDATION_ERROR`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Missing, malformed, or revoked API key (`UNAUTHORIZED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: The key exists but may not reach this resource — a `sending_access` key outside the send paths, a test-mode key writing live state, or an account on hold (`FORBIDDEN`, `ACCOUNT_RESTRICTED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: A topic with that name already exists (`CONFLICT`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit exceeded (`RATE_LIMITED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Unexpected server error (`INTERNAL_ERROR`). Carries `request_id`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
get:
operationId: listTopics
summary: List topics
description: Every topic the organization has defined, by name, with the number of contacts explicitly opted in to each. Unpaginated — the list is capped at 100 entries.
tags:
- Topics
responses:
'200':
description: The topics.
content:
application/json:
schema:
$ref: '#/components/schemas/ListTopicsResponse'
'401':
description: Missing, malformed, or revoked API key (`UNAUTHORIZED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: The key exists but may not reach this resource — a `sending_access` key outside the send paths, a test-mode key writing live state, or an account on hold (`FORBIDDEN`, `ACCOUNT_RESTRICTED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit exceeded (`RATE_LIMITED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Unexpected server error (`INTERNAL_ERROR`). Carries `request_id`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/topics/{id}:
get:
operationId: getTopic
summary: Get a topic
description: One topic by id.
tags:
- Topics
parameters:
- name: id
in: path
required: true
description: The topic id.
schema:
type: string
format: uuid
responses:
'200':
description: The topic.
content:
application/json:
schema:
$ref: '#/components/schemas/Topic'
'401':
description: Missing, malformed, or revoked API key (`UNAUTHORIZED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: The key exists but may not reach this resource — a `sending_access` key outside the send paths, a test-mode key writing live state, or an account on hold (`FORBIDDEN`, `ACCOUNT_RESTRICTED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: No such resource on this organization (`NOT_FOUND`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit exceeded (`RATE_LIMITED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Unexpected server error (`INTERNAL_ERROR`). Carries `request_id`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
patch:
operationId: updateTopic
summary: Update a topic
description: 'Name, description and visibility. `default_subscription` is rejected: it is a consent decision, and changing it would start or stop mail for every contact who never chose.'
tags:
- Topics
parameters:
- name: id
in: path
required: true
description: The topic id.
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateTopicRequest'
responses:
'200':
description: Updated.
content:
application/json:
schema:
$ref: '#/components/schemas/Topic'
'400':
description: The request body or query failed validation (`VALIDATION_ERROR`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Missing, malformed, or revoked API key (`UNAUTHORIZED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: The key exists but may not reach this resource — a `sending_access` key outside the send paths, a test-mode key writing live state, or an account on hold (`FORBIDDEN`, `ACCOUNT_RESTRICTED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: No such resource on this organization (`NOT_FOUND`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: Another topic already has this name (`CONFLICT`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit exceeded (`RATE_LIMITED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Unexpected server error (`INTERNAL_ERROR`). Carries `request_id`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
operationId: deleteTopic
summary: Delete a topic
description: Deletes the topic and every stored preference for it. A draft broadcast scoped to it widens back to its whole audience; one that is scheduled, sending, paused or held blocks the delete. Broadcasts already sent keep their recipient records.
tags:
- Topics
parameters:
- name: id
in: path
required: true
description: The topic id.
schema:
type: string
format: uuid
responses:
'200':
description: Deleted.
content:
application/json:
schema:
$ref: '#/components/schemas/DeleteTopicResponse'
'401':
description: Missing, malformed, or revoked API key (`UNAUTHORIZED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: The key exists but may not reach this resource — a `sending_access` key outside the send paths, a test-mode key writing live state, or an account on hold (`FORBIDDEN`, `ACCOUNT_RESTRICTED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: No such resource on this organization (`NOT_FOUND`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'409':
description: A broadcast scoped to this topic has not finished sending (`CONFLICT`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit exceeded (`RATE_LIMITED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Unexpected server error (`INTERNAL_ERROR`). Carries `request_id`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/topics/{id}/contacts/{contactId}:
put:
operationId: setTopicSubscription
summary: Set a contact's topic subscription
description: Records an explicit preference, overriding the topic default.
tags:
- Topics
parameters:
- name: id
in: path
required: true
description: The topic id.
schema:
type: string
format: uuid
- name: contactId
in: path
required: true
description: The contact id.
schema:
type: string
format: uuid
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/TopicSubscriptionRequest'
responses:
'200':
description: Saved.
content:
application/json:
schema:
$ref: '#/components/schemas/TopicSubscriptionResponse'
'400':
description: The request body or query failed validation (`VALIDATION_ERROR`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'401':
description: Missing, malformed, or revoked API key (`UNAUTHORIZED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: The key exists but may not reach this resource — a `sending_access` key outside the send paths, a test-mode key writing live state, or an account on hold (`FORBIDDEN`, `ACCOUNT_RESTRICTED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: No such resource on this organization (`NOT_FOUND`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit exceeded (`RATE_LIMITED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Unexpected server error (`INTERNAL_ERROR`). Carries `request_id`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
delete:
operationId: clearTopicSubscription
summary: Clear a contact's topic subscription
description: 'Forgets the explicit choice so the contact falls back to the topic default. Different from `subscribed: false`, which records a refusal — on an `opt_in` topic, clearing means the contact starts receiving it again.'
tags:
- Topics
parameters:
- name: id
in: path
required: true
description: The topic id.
schema:
type: string
format: uuid
- name: contactId
in: path
required: true
description: The contact id.
schema:
type: string
format: uuid
responses:
'200':
description: Cleared.
content:
application/json:
schema:
$ref: '#/components/schemas/ClearTopicSubscriptionResponse'
'401':
description: Missing, malformed, or revoked API key (`UNAUTHORIZED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'403':
description: The key exists but may not reach this resource — a `sending_access` key outside the send paths, a test-mode key writing live state, or an account on hold (`FORBIDDEN`, `ACCOUNT_RESTRICTED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'404':
description: No such resource on this organization (`NOT_FOUND`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'429':
description: Rate limit exceeded (`RATE_LIMITED`).
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
'500':
description: Unexpected server error (`INTERNAL_ERROR`). Carries `request_id`.
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
TopicSubscriptionRequest:
type: object
required:
- subscribed
additionalProperties: false
properties:
subscribed:
type: boolean
UpdateTopicRequest:
type: object
additionalProperties: false
properties:
name:
type: string
maxLength: 50
description:
type:
- string
- 'null'
maxLength: 200
description: Send `null` to clear it.
visibility:
type: string
enum:
- public
- private
ClearTopicSubscriptionResponse:
type: object
required:
- topicId
- contactId
- deleted
properties:
topicId:
type: string
format: uuid
contactId:
type: string
format: uuid
deleted:
type: boolean
CreateTopicRequest:
type: object
required:
- name
- default_subscription
additionalProperties: false
properties:
name:
type: string
maxLength: 50
description: What the recipient reads on the unsubscribe page. Unique within the organization.
default_subscription:
type: string
enum:
- opt_in
- opt_out
description: '`opt_in` reaches everyone until they unsubscribe; `opt_out` reaches nobody until they explicitly subscribe. **Cannot be changed after creation** — flipping it would start mailing every contact who never asked, and nothing stored says which of them would have agreed.'
description:
type:
- string
- 'null'
maxLength: 200
visibility:
type: string
enum:
- public
- private
default: private
description: Whether the topic is listed on the hosted unsubscribe page to a contact who is not already opted in. A contact who explicitly opted in always sees it.
Error:
type: object
description: Every non-2xx response from the API has this shape. `code` is stable and safe to branch on; `error` is a human sentence that may change.
required:
- error
- code
properties:
error:
type: string
description: Human-readable explanation of the failure.
code:
type: string
enum:
- UNAUTHORIZED
- FORBIDDEN
- NOT_FOUND
- VALIDATION_ERROR
- RATE_LIMITED
- MONTHLY_LIMIT_EXCEEDED
- DAILY_LIMIT_EXCEEDED
- PLAN_LIMIT_EXCEEDED
- ALL_SUPPRESSED
- RECIPIENT_DOMAIN_UNDELIVERABLE
- DOMAIN_NOT_VERIFIED
- SENDING_SUSPENDED
- ACCOUNT_RESTRICTED
- WORKSPACE_INACTIVE
- SENDER_NOT_PERMITTED
- SERVICE_PAUSED
- LIST_SEND_HELD
- BROADCAST_HELD
- ATTACHMENT_STORAGE_ERROR
- INTERNAL_ERROR
- CONFLICT
- BAD_REQUEST
- PAYLOAD_TOO_LARGE
description: Stable machine-readable error code.
request_id:
type: string
description: Correlation id, returned on 500s and echoed in the `X-Request-Id` header on every response. Quote it in support requests.
TopicSubscriptionResponse:
type: object
required:
- topicId
- contactId
- subscribed
properties:
topicId:
type: string
format: uuid
contactId:
type: string
format: uuid
subscribed:
type: boolean
Topic:
type: object
required:
- id
- name
- description
- defaultSubscription
- visibility
- createdAt
properties:
id:
type: string
format: uuid
name:
type: string
description:
type:
- string
- 'null'
defaultSubscription:
type: string
enum:
- opt_in
- opt_out
description: What a contact who has never expressed a preference receives. Fixed at creation.
visibility:
type: string
enum:
- public
- private
subscriberCount:
type: integer
description: Contacts who explicitly opted in. Returned by create and list, not by get. For an `opt_out` topic this is the reachable audience; for an `opt_in` topic it is not, since everyone who never chose is subscribed too.
createdAt:
type: string
format: date-time
updatedAt:
type:
- string
- 'null'
format: date-time
ListTopicsResponse:
type: object
required:
- data
properties:
data:
type: array
items:
$ref: '#/components/schemas/Topic'
DeleteTopicResponse:
type: object
required:
- id
- deleted
properties:
id:
type: string
format: uuid
deleted:
type: boolean
securitySchemes:
bearerAuth:
type: http
scheme: bearer
description: 'An eusend API key, sent as `Authorization: Bearer eu_live_…`. Create one in the dashboard under API keys.'
externalDocs:
description: eusend documentation
url: https://eusend.dev/docs