Every API here is available over the APIs.io API and to AI agents over MCP.
openapi: 3.2.0
info:
title: Sendly Now Analytics 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 Analytics 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: Aggregate sending and engagement metrics. Every read is bounded to a window of at most 90 days and cached for 15 minutes.
name: Analytics
paths:
/api/activity:
get:
description: 'List this project''s activity feed: sends, opens, bounces, workflow steps and more. Filters: limit, cursor, contactId, types (comma-separated, e.g. email.sent,email.bounced,workflow.started), startDate, endDate.
Requires the `analytics:read` scope — View your sending analytics and engagement metrics.'
operationId: listActivity
responses:
'200':
content:
application/json:
schema:
additionalProperties: {}
properties: {}
type: object
description: OK
'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:
- ApiKeyAuth: []
- SessionAuth: []
- OAuth2:
- analytics:read
summary: 'List this project''s activity feed: sends, opens, bounces, workflow steps and…'
tags:
- Analytics
servers:
- url: https://api.sendly.now
/api/activity/recent-count:
get:
description: 'Count this project''s activity in the last few minutes. Filter: minutes.
Requires the `analytics:read` scope — View your sending analytics and engagement metrics.'
operationId: getActivityCount
responses:
'200':
content:
application/json:
schema:
additionalProperties: {}
properties: {}
type: object
description: OK
'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:
- ApiKeyAuth: []
- SessionAuth: []
- OAuth2:
- analytics:read
summary: Count this project's activity in the last few minutes
tags:
- Analytics
servers:
- url: https://api.sendly.now
/api/activity/stats:
get:
description: 'Summarise this project''s activity by type over a period. Filters: startDate, endDate.
Requires the `analytics:read` scope — View your sending analytics and engagement metrics.'
operationId: getActivityStats
responses:
'200':
content:
application/json:
schema:
additionalProperties: {}
properties: {}
type: object
description: OK
'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:
- ApiKeyAuth: []
- SessionAuth: []
- OAuth2:
- analytics:read
summary: Summarise this project's activity by type over a period
tags:
- Analytics
servers:
- url: https://api.sendly.now
/api/activity/upcoming:
get:
description: 'List scheduled sends and workflow steps due soon. Filters: limit, daysAhead.
Requires the `analytics:read` scope — View your sending analytics and engagement metrics.'
operationId: listUpcomingActivity
responses:
'200':
content:
application/json:
schema:
additionalProperties: {}
properties: {}
type: object
description: OK
'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:
- ApiKeyAuth: []
- SessionAuth: []
- OAuth2:
- analytics:read
summary: List scheduled sends and workflow steps due soon
tags:
- Analytics
servers:
- url: https://api.sendly.now
/api/analytics/top-events:
get:
description: 'List the most frequent events in a period. Filters: limit (up to 20), startDate, endDate.
Requires the `analytics:read` scope — View your sending analytics and engagement metrics.'
operationId: listTopEvents
responses:
'200':
content:
application/json:
schema:
additionalProperties: {}
properties: {}
type: object
description: OK
'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:
- ApiKeyAuth: []
- SessionAuth: []
- OAuth2:
- analytics:read
summary: List the most frequent events in a period
tags:
- Analytics
servers:
- url: https://api.sendly.now
/api/v1/analytics/campaigns:
get:
description: 'Campaign counts plus average open and click rates.
`total` and `active` count campaigns CREATED in the window; `completed` counts campaigns SENT in it — so a campaign created earlier and sent inside the window appears only in `completed`. Rates are percentages to one decimal place, averaged over the campaigns sent in the window.
`from` defaults to 30 days ago and is clamped to at most 90 days back; `to` defaults to now. A wider request is narrowed rather than refused, and the `window` field states the range actually covered — read it before comparing two responses.
Requires the `analytics:read` scope — View your sending analytics and engagement metrics.'
operationId: v1GetCampaignAnalytics
parameters:
- description: Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.
in: query
name: from
required: false
schema:
description: Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.
format: date-time
type:
- string
- 'null'
- description: End of the window (ISO 8601). Defaults to now.
in: query
name: to
required: false
schema:
description: End of the window (ISO 8601). Defaults to now.
format: date-time
type:
- string
- 'null'
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AnalyticsCampaignStatsV1'
description: Campaign statistics
'401':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`invalid_api_key` or `invalid_session` — missing or invalid credentials.'
'403':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`scope_missing`, `project_access_denied`, or `project_disabled`.'
'422':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`validation_error` — query, path, or body parameters did not match the schema.'
'429':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`rate_limited` — see `Retry-After` and the `RateLimit` headers.'
'500':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`internal_error`.'
security:
- ApiKeyAuth: []
- SessionAuth: []
- OAuth2:
- analytics:read
summary: Retrieve campaign totals and engagement
tags:
- Analytics
servers:
- url: https://api.sendly.now
/api/v1/analytics/timeseries:
get:
description: 'Daily counts of emails created, delivered, opened, clicked and bounced. Every day in the window is present even with zero activity, so the series never needs gap-filling.
`from` defaults to 30 days ago and is clamped to at most 90 days back; `to` defaults to now. A wider request is narrowed rather than refused, and the `window` field states the range actually covered — read it before comparing two responses.
Requires the `analytics:read` scope — View your sending analytics and engagement metrics.'
operationId: v1GetAnalyticsTimeseries
parameters:
- description: Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.
in: query
name: from
required: false
schema:
description: Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.
format: date-time
type:
- string
- 'null'
- description: End of the window (ISO 8601). Defaults to now.
in: query
name: to
required: false
schema:
description: End of the window (ISO 8601). Defaults to now.
format: date-time
type:
- string
- 'null'
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AnalyticsTimeseriesV1'
description: Daily time series
'401':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`invalid_api_key` or `invalid_session` — missing or invalid credentials.'
'403':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`scope_missing`, `project_access_denied`, or `project_disabled`.'
'422':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`validation_error` — query, path, or body parameters did not match the schema.'
'429':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`rate_limited` — see `Retry-After` and the `RateLimit` headers.'
'500':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`internal_error`.'
security:
- ApiKeyAuth: []
- SessionAuth: []
- OAuth2:
- analytics:read
summary: Retrieve the daily email time series
tags:
- Analytics
servers:
- url: https://api.sendly.now
/api/v1/analytics/top-campaigns:
get:
description: 'Campaigns sent in the window, ranked by open rate, capped at 50 rows. Not cursor-paginated: a leaderboard is a top-N by definition, and paging one would mean re-ranking on every page.
`from` defaults to 30 days ago and is clamped to at most 90 days back; `to` defaults to now. A wider request is narrowed rather than refused, and the `window` field states the range actually covered — read it before comparing two responses.
Requires the `analytics:read` scope — View your sending analytics and engagement metrics.'
operationId: v1ListTopCampaigns
parameters:
- description: Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.
in: query
name: from
required: false
schema:
description: Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.
format: date-time
type:
- string
- 'null'
- description: End of the window (ISO 8601). Defaults to now.
in: query
name: to
required: false
schema:
description: End of the window (ISO 8601). Defaults to now.
format: date-time
type:
- string
- 'null'
- in: query
name: limit
required: false
schema:
default: 10
maximum: 50
minimum: 1
type: integer
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/AnalyticsTopCampaignsV1'
description: Ranked campaigns
'401':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`invalid_api_key` or `invalid_session` — missing or invalid credentials.'
'403':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`scope_missing`, `project_access_denied`, or `project_disabled`.'
'422':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`validation_error` — query, path, or body parameters did not match the schema.'
'429':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`rate_limited` — see `Retry-After` and the `RateLimit` headers.'
'500':
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
description: '`internal_error`.'
security:
- ApiKeyAuth: []
- SessionAuth: []
- OAuth2:
- analytics:read
summary: List the best-performing campaigns
tags:
- Analytics
servers:
- url: https://api.sendly.now
components:
schemas:
AnalyticsCampaignStatsV1:
description: Campaign counters and engagement over the window.
properties:
active:
description: Campaigns in DRAFT or SCHEDULED.
type: integer
average_click_rate:
type: number
average_open_rate:
description: Percentage, one decimal place.
type: number
completed:
type: integer
total:
type: integer
window:
$ref: '#/components/schemas/AnalyticsWindowV1'
required:
- total
- active
- completed
- average_open_rate
- average_click_rate
- window
type: object
AnalyticsTimeseriesV1:
description: Daily email counters across the window. Every day in range is present, zero-filled.
properties:
data:
items:
properties:
bounces:
type: integer
clicks:
type: integer
date:
format: date-time
type: string
delivered:
type: integer
emails:
type: integer
opens:
type: integer
required:
- date
- emails
- delivered
- opens
- clicks
- bounces
type: object
type: array
window:
$ref: '#/components/schemas/AnalyticsWindowV1'
required:
- data
- window
type: object
Problem:
description: RFC 9457 problem document, served as `application/problem+json`. Returned by every 4xx/5xx response on the `/api/v1` surface.
properties:
code:
description: Machine-readable lowercase error code, e.g. `scope_missing`.
type: string
detail:
description: Explanation specific to this occurrence.
type: string
errors:
description: Field-level failures. Present on 422 `validation_error` responses.
items:
properties:
code:
type: string
message:
type: string
pointer:
description: RFC 6901 JSON Pointer to the offending field.
type: string
required:
- pointer
- code
- message
type: object
type: array
instance:
description: Request path the failure occurred on.
type: string
next:
description: The tool an agent calls next to repair this failure — `request_scopes` for a missing permission, `upgrade_plan` for an exhausted plan. Absent when no tool repairs it.
properties:
args:
additionalProperties: {}
description: Arguments to call it with.
type: object
tool:
description: The MCP tool that repairs this failure, e.g. `upgrade_plan`.
type: string
required:
- tool
type: object
request_id:
description: Correlation id — quote it in support requests.
type: string
status:
description: HTTP status code, repeated in the body.
type: integer
title:
description: Short, stable summary — the same for every occurrence of a `type`.
type: string
type:
description: Dereferenceable URI identifying the error class, anchored on the docs errors page.
format: uri
type: string
required:
- type
- title
- status
- code
type: object
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
AnalyticsTopCampaignsV1:
description: Sent campaigns ranked by open rate.
properties:
data:
items:
properties:
click_rate:
type: number
clicked:
type: integer
id:
format: uuid
type: string
open_rate:
type: number
opened:
type: integer
sent:
type: integer
subject:
type: string
required:
- id
- subject
- sent
- opened
- clicked
- open_rate
- click_rate
type: object
type: array
window:
$ref: '#/components/schemas/AnalyticsWindowV1'
required:
- data
- window
type: object
AnalyticsWindowV1:
description: The time range this response was computed over, after the 90-day clamp.
properties:
from:
format: date-time
type: string
to:
format: date-time
type: string
required:
- from
- to
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