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/sendly-now:sendly-now-api-keys-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: Sendly Now API Keys API
version: 1.0.0
contact:
name: Sendly Support
url: https://sendly.now
license:
name: AGPL-3.0
url: https://www.gnu.org/licenses/agpl-3.0.txt
description: 'Operations tagged API Keys across 2 of this provider''s published API definitions: openapi.json, sendly-now-openapi.json. Each path carries the servers of the definition it was published in.'
servers:
- url: https://api.sendly.now
tags:
- description: Project-scoped sending/API credentials. All four operations are documented; minting and rotation answer with a one-time reveal link rather than the secret, so a delegated caller never receives a key it could use.
name: API Keys
paths:
/api/projects/{id}/api-keys:
get:
description: 'Returns every key on the project, revoked ones included — filter on `revokedAt` to show only live keys. Never returns a token or its hash: `lastFour` is the only fragment of the secret that survives creation.
Requires the `api-keys:read` scope — See which API keys exist, including what each one is allowed to do.'
operationId: listApiKeys
parameters:
- description: Project id.
in: path
name: id
required: true
schema:
description: Project id.
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ApiKeyListResponse'
description: API key list
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Validation error
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Unauthorized — missing or invalid auth
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Forbidden — insufficient permissions or project disabled
'404':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Resource not found
'429':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Rate limit or billing limit exceeded
'500':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Internal server error
security:
- SessionAuth: []
- OAuth2:
- api-keys:read
summary: List API keys for a project
tags:
- API Keys
post:
description: 'Mint a new API key on the project. The token is NOT returned — the response carries the key''s metadata plus a one-time `revealUrl` that only a signed-in dashboard session can open.
**Scope attenuation:** a key created with a delegated credential (an OAuth token or another API key) may not carry a scope that credential does not itself hold. A request that asks for more is refused with `400 SCOPE_ESCALATION` rather than quietly narrowed, so the mistake is reported where it was made instead of surfacing later as an unexplained 403. Naming only `legacyGrantPreset` counts as asking for every scope that preset implies.
Requires the `api-keys:write` scope — Create, rotate, and revoke API keys — these keep working even after you disconnect this app.'
operationId: createApiKey
parameters:
- description: Project id.
in: path
name: id
required: true
schema:
description: Project id.
type: string
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateApiKeyBody'
required: true
responses:
'201':
content:
application/json:
schema:
properties:
data:
allOf:
- $ref: '#/components/schemas/ApiKey'
- properties:
revealExpiresAt:
description: When the reveal link stops working. Create or rotate again to get a new one.
format: date-time
type: string
revealUrl:
description: 'A one-time, session-authenticated URL where the person who owns this project can see the secret. The secret itself is never returned to an API or agent caller: opening this link requires a signed-in dashboard session, so the credential that created the key cannot redeem it. Single use — the first successful open consumes it.'
format: uri
type: string
required:
- revealUrl
- revealExpiresAt
type: object
description: An API key's metadata. Never carries the token or its hash — `lastFour` is the only surviving fragment of the secret once the key has been created.
success:
enum:
- true
type: boolean
required:
- success
- data
type: object
description: API key created; the secret is behind the reveal link.
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Validation error
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Unauthorized — missing or invalid auth
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Forbidden — insufficient permissions or project disabled
'429':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Rate limit or billing limit exceeded
'500':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Internal server error
security:
- SessionAuth: []
- OAuth2:
- api-keys:write
summary: Create an API key
tags:
- API Keys
servers:
- url: https://api.sendly.now
/api/projects/{id}/api-keys/{keyId}:
delete:
description: 'Revoke an API key. Answers `{ success: true }` with no `data` key. 404 if the key does not exist under this project.
Requires the `api-keys:write` scope — Create, rotate, and revoke API keys — these keep working even after you disconnect this app.'
operationId: revokeApiKey
parameters:
- description: Project id.
in: path
name: id
required: true
schema:
description: Project id.
type: string
- description: API key id.
in: path
name: keyId
required: true
schema:
description: API key id.
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/SuccessEmpty'
description: API key revoked
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Validation error
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Unauthorized — missing or invalid auth
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Forbidden — insufficient permissions or project disabled
'404':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Resource not found
'429':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Rate limit or billing limit exceeded
'500':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Internal server error
security:
- SessionAuth: []
- OAuth2:
- api-keys:write
summary: Revoke an API key
tags:
- API Keys
get:
description: 'Returns one key on the project, revoked or not. Never returns a token or its hash: `lastFour` is the only fragment of the secret that survives creation. 404 if the key does not exist under this project.
Requires the `api-keys:read` scope — See which API keys exist, including what each one is allowed to do.'
operationId: getApiKey
parameters:
- description: Project id.
in: path
name: id
required: true
schema:
description: Project id.
type: string
- description: API key id.
in: path
name: keyId
required: true
schema:
description: API key id.
type: string
responses:
'200':
content:
application/json:
schema:
properties:
data:
$ref: '#/components/schemas/ApiKey'
success:
enum:
- true
type: boolean
required:
- success
- data
type: object
description: The API key
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Validation error
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Unauthorized — missing or invalid auth
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Forbidden — insufficient permissions or project disabled
'404':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Resource not found
'429':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Rate limit or billing limit exceeded
'500':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Internal server error
security:
- SessionAuth: []
- OAuth2:
- api-keys:read
summary: Get an API key
tags:
- API Keys
servers:
- url: https://api.sendly.now
/api/projects/{id}/api-keys/{keyId}/rotate:
post:
description: 'Replace the key''s secret in place, keeping its id, name and scopes. The PREVIOUS secret stops authenticating immediately — there is no overlap window — so anything still using it starts failing on its next request. As with creation, the new secret is not returned: the response carries `lastFour` and a one-time `revealUrl`. A revoked key cannot be rotated (`400 KEY_REVOKED`).
Requires the `api-keys:write` scope — Create, rotate, and revoke API keys — these keep working even after you disconnect this app.'
operationId: rotateApiKey
parameters:
- description: Project id.
in: path
name: id
required: true
schema:
description: Project id.
type: string
- description: API key id.
in: path
name: keyId
required: true
schema:
description: API key id.
type: string
responses:
'200':
content:
application/json:
schema:
properties:
data:
properties:
lastFour:
type: string
revealExpiresAt:
description: When the reveal link stops working. Create or rotate again to get a new one.
format: date-time
type: string
revealUrl:
description: 'A one-time, session-authenticated URL where the person who owns this project can see the secret. The secret itself is never returned to an API or agent caller: opening this link requires a signed-in dashboard session, so the credential that created the key cannot redeem it. Single use — the first successful open consumes it.'
format: uri
type: string
required:
- lastFour
- revealUrl
- revealExpiresAt
type: object
success:
enum:
- true
type: boolean
required:
- success
- data
type: object
description: API key rotated; the new secret is behind the reveal link.
'400':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Validation error
'401':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Unauthorized — missing or invalid auth
'403':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Forbidden — insufficient permissions or project disabled
'404':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Resource not found
'429':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Rate limit or billing limit exceeded
'500':
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
description: Internal server error
security:
- SessionAuth: []
- OAuth2:
- api-keys:write
summary: Rotate an API key's secret
tags:
- API Keys
servers:
- url: https://api.sendly.now
components:
schemas:
Error:
description: 'Standard error envelope returned by all 4xx/5xx responses. Migrated routes include `success: false`; 422 validation errors add `error.details.errors`.'
properties:
error:
properties:
code:
type: string
details:
properties:
errors:
items: {}
type: array
required:
- errors
type: object
message:
type: string
required:
- message
- code
type: object
success:
enum:
- false
type: boolean
required:
- error
type: object
ApiKeyListResponse:
description: Every API key on the project, including revoked ones — filter on `revokedAt` for live keys.
properties:
data:
items:
$ref: '#/components/schemas/ApiKey'
type: array
success:
enum:
- true
type: boolean
required:
- success
- data
type: object
ApiKey:
description: An API key's metadata. Never carries the token or its hash — `lastFour` is the only surviving fragment of the secret once the key has been created.
properties:
createdAt:
description: ISO 8601 datetime string
format: date-time
type: string
domainId:
format: uuid
type:
- string
- 'null'
id:
format: uuid
type: string
lastFour:
description: Last 4 characters of the token — the only fragment of the secret that survives creation.
type: string
lastUsedAt:
description: ISO 8601 datetime string
format: date-time
type:
- string
- 'null'
legacyGrantPreset:
description: The coarse preset the key was minted under. It picks the token prefix (`sk_` vs `pk_`) and the rate-limit window, and it is the key's authority ONLY while `scopes` is empty — which is true just of keys minted before scopes existed. Read `scopes` to learn what a key can do.
enum:
- FULL
- SENDING_ONLY
type: string
mode:
description: Where mail sent with this key may go. `LIVE` sends from your verified domains. `TEST` can only send from this project's sandbox address, whose recipient list is your own verified account addresses, so a test key cannot reach a customer whatever it is asked to do. Fixed at creation.
enum:
- LIVE
- TEST
type: string
name:
type: string
projectId:
format: uuid
type: string
revokedAt:
description: Set once the key is revoked. Revoked keys are NOT filtered out of list/get responses.
format: date-time
type:
- string
- 'null'
scopes:
description: The explicit scope grant this key carries. Empty on a key minted before this column existed — that row's grant is derived from `legacyGrantPreset` instead at request time.
items:
enum:
- emails:send
- emails:read
- contacts:read
- contacts:write
- campaigns:read
- campaigns:write
- segments:read
- segments:write
- workflows:read
- workflows:write
- templates:read
- templates:write
- domains:read
- domains:write
- webhooks:read
- webhooks:write
- suppression:read
- suppression:write
- analytics:read
- usage:read
- events:read
- events:write
- projects:read
- projects:write
- api-keys:read
- api-keys:write
- campaigns:send
- mailboxes:read
- mailboxes:write
- emails:test
- deliverability:read
- mailboxes:send
- validation:read
- validation:write
- topics:read
- topics:write
- lists:read
- lists:write
- mailbox-conversations:read
- mailbox-conversations:reply
- billing:read
- billing:write
- account:read
- account:write
- members:read
- members:write
- connections:read
- connections:write
- support:read
- support:write
type: string
type: array
required:
- id
- projectId
- name
- lastFour
- legacyGrantPreset
- mode
- scopes
- domainId
- lastUsedAt
- createdAt
- revokedAt
type: object
CreateApiKeyBody:
properties:
domainId:
format: uuid
type:
- string
- 'null'
legacyGrantPreset:
enum:
- FULL
- SENDING_ONLY
type: string
mode:
description: '`LIVE` (default) or `TEST`. A test key can only send from the project''s sandbox address, which accepts only the project''s own verified account addresses as recipients, so it cannot reach a customer. Its sends are real sends down the same pipeline and are marked `sentInTestMode`. Fixed at creation.'
enum:
- LIVE
- TEST
type: string
name:
maxLength: 120
minLength: 1
type: string
scopes:
description: The explicit grant the new key will carry. Omitted ⇒ materialised from `legacyGrantPreset`. A `SENDING_ONLY` key may carry only `emails:send`.
items:
enum:
- emails:send
- emails:read
- contacts:read
- contacts:write
- campaigns:read
- campaigns:write
- segments:read
- segments:write
- workflows:read
- workflows:write
- templates:read
- templates:write
- domains:read
- domains:write
- webhooks:read
- webhooks:write
- suppression:read
- suppression:write
- analytics:read
- usage:read
- events:read
- events:write
- projects:read
- projects:write
- api-keys:read
- api-keys:write
- campaigns:send
- mailboxes:read
- mailboxes:write
- emails:test
- deliverability:read
- mailboxes:send
- validation:read
- validation:write
- topics:read
- topics:write
- lists:read
- lists:write
- mailbox-conversations:read
- mailbox-conversations:reply
- billing:read
- billing:write
- account:read
- account:write
- members:read
- members:write
- connections:read
- connections:write
- support:read
- support:write
type: string
type: array
tiers:
description: 'Permission groups: the key carries every scope of each named tier. Send `tiers` or `scopes`, not both (`422`).'
items:
enum:
- read
- write
- destructive
- billing
- account
- team
- connections
- support
- keys
type: string
minItems: 1
type: array
required:
- name
type: object
SuccessEmpty:
description: Bare success envelope with no payload.
properties:
success:
enum:
- true
type: boolean
required:
- success
type: object
securitySchemes:
ApiKeyAuth:
bearerFormat: API Key
description: 'API key authentication. Use a `sk_*` (FULL) or `pk_*` (SENDING_ONLY) key as the bearer token. Public keys (`pk_*`) are restricted to the email-send endpoints and the self-serve list subscribe/unsubscribe pair; every other endpoint — event tracking included — answers 403 for them. In scope terms (used by `/api/v1/*` operations): FULL keys hold every scope; SENDING_ONLY keys hold only `emails:send`.'
scheme: bearer
type: http
MailgunBasicAuth:
description: 'Mailgun-style authentication for the `/api/compat/mailgun/*` operations only: HTTP Basic with the username `api` and a Sendly API key (`sk_*`, or a `pk_*` send-only key) as the password, exactly as mailgun.js sends it.'
scheme: basic
type: http
OAuth2:
description: 'OAuth 2.1 with PKCE, for AI agents and other delegated clients (this is what the MCP endpoint at `/api/mcp` uses, and how a third-party application sends on a user''s behalf). Tokens are minted through the consent screen and carry ONLY the scopes the user ticked there, so an operation lists the single scope it requires and a token without it answers `403` with code `SCOPE_MISSING` — before any input is parsed. Unlike an API key, a delegated token reaches an operation only where the route itself declares a scope; every other route refuses it outright.
**Ask for the right audience.** Pass `resource=https://app.sendly.now/api` on the authorization AND the token request to receive a token for this REST API; `resource=https://app.sendly.now/api/mcp` is the MCP endpoint''s identifier. A request carrying no `resource` receives an OPAQUE token, which every endpoint here rejects. Each identifier publishes RFC 9728 metadata at `/.well-known/oauth-protected-resource/api` and `/.well-known/oauth-protected-resource/api/mcp`.
**Name the project on every call.** An OAuth grant belongs to a USER, not to a project — the same person may belong to several — so a delegated token carries no tenant and the project is chosen per request with the `x-project-id` header, checked against real membership. Omit it and a project-scoped operation answers `400`; send one the user does not belong to and it answers `403 PROJECT_ACCESS_DENIED`. List the projects a token may name with `GET /api/users/me/projects` (scope `projects:read`), which needs no header. API keys are unaffected: a key already names its project.'
flows:
authorizationCode:
authorizationUrl: https://app.sendly.now/api/auth/oauth2/authorize
refreshUrl: https://app.sendly.now/api/auth/oauth2/token
scopes:
account:read: See your profile and notification preferences
account:write: Edit your profile and preferences, export your data, delete your account
analytics:read: View your sending analytics and engagement metrics
api-keys:read: See which API keys exist, including what each one is allowed to do
api-keys:write: Create, rotate, and revoke API keys — these keep working even after you disconnect this app
billing:read: See your plan, usage, invoices and spending limits
billing:write: Start a checkout, change spending limits, open the billing portal, cancel or refund
campaigns:read: View your campaigns and their performance
campaigns:send: Send or schedule your campaigns to their audience
campaigns:write: Create, edit, and organize your campaigns
connections:read: See connected services such as Slack and connected AI apps
connections:write: Connect, re-verify and disconnect services
contacts:read: View your contacts and their custom fields
contacts:write: Create, update, and delete your contacts
deliverability:read: Check why mail from one of your domains is not arriving
domains:read: View your sending domains and their verification status
domains:write: Add and remove sending domains, and trigger verification
emails:read: View the emails you have sent and their delivery status
emails:send: Send emails from your verified domains
emails:test: Send test emails to your own address from the Sendly sandbox
events:read: View the custom events your application has recorded
events:write: Record custom events for your contacts
lists:read: View your subscriber lists and who is on them
lists:write: Create, rename, and delete your subscriber lists
mailbox-conversations:read: Read the conversations in your hosted mailboxes — every message and attachment people have sent you
mailbox-conversations:reply: Reply to conversations in your hosted mailboxes, from the mailbox's own address
mailboxes:read: View the mailboxes on your domains and their settings
mailboxes:send: Write and send new email from your hosted mailboxes, as that address
mailboxes:write: Create and delete mailboxes on your verified domains
members:read: See who belongs to your projects
members:write: Add and remove project members and change their roles
projects:read: View your projects and their settings
projects:write: Create, change, reset and delete projects on your account
segments:read: View your segments and who belongs to them
segments:write: Create, edit, and delete your segments
support:read: See your support tickets
support:write: Open support tickets and suspension appeals
suppression:read: View the addresses on your suppression list
suppression:write: Add and remove addresses on your suppression list
templates:read: View your email templates
templates:write: Create, edit, and delete your email templates
topics:read: View the topics you mail about and who is subscribed to each
topics:write: Create and edit topics, and change what your contacts are subscribed to — this decides who your campaigns reach
usage:read: View your usage totals and billing limits
validation:read: View your email validation runs and their results
validation:write: Check whether email addresses can receive mail — this is billed per address
webhooks:read: View your webhook endpoints and their delivery history
webhooks:write: Create, edit, and delete your webhook endpoints
workflows:read: View your automation workflows and their runs
workflows:write: Create, edit, enable, and delete your automation workflows
tokenUrl: https://app.sendly.now/api/auth/oauth2/token
type: oauth2
PostmarkServerToken:
description: 'Postmark-style authentication for the `/api/compat/postmark/*` operations only: a Sendly API key (`sk_*`, or a `pk_*` send-only key) sent in the `X-Postmark-Server-Token` header, exactly where the postmark SDK puts its server token.'
in: header
name: X-Postmark-Server-Token
type: apiKey
SessionAuth:
description: BetterAuth session cookie. Used by the dashboard / browser clients. When present, the active project is taken from the `x-project-id` header.
in: cookie
name: better-auth.session_token
type: apiKey
x-refined-from:
- openapi.json
- sendly-now-openapi.json