API reference menu
Getting started
OverviewAuthentication and rate limitExamplesSending
Send a notificationPriority and repeatLinks, images and buttonsSourcesScheduled sendsWebhook templatesReceipts and replies
Receipts and callbacksRepliesTeams
Teams, roles and endpointsChannels and escalationWebhooks and emailAlert email and timelineTeam formsPlans, audit log and transferCompany sign‑in and SCIMThe web consoleAccount
Identity and moving phonesDevices, keys and historyThe Upgrade PassCompatibility
ntfy's publish APIProwl's APIAppendix
Endpoint indexErrors and status codesLimitsPriorities and fieldsGlossaryPlans, audit log and transfer
Plans and billing#
A team is judged by its plan, through one function (plans.effective): active, trialing and
comped work until period_end, then for a 14-day grace; past_due gets the same grace;
canceled works until period_end; paused and unknown statuses are lapsed. A lapse stops
sends and new forms only: members, channels, history and forms already collected stay, and the
team drops to the free seat count. Team plans are bought on the web, never in the app.
GET /v1/teams/{id}/plan(admin): source, state, seats and seats used, forms, paid-through, grace end. No card or invoice detail is held here.POST /v1/teams/{id}/plan/checkout(owner): returns what the web checkout needs to buy or change the team's plan. The console's Buy or change the plan button uses it.409 plan_activewhile the team already has a live subscription.POST /v1/teams/{id}/plan/trial(owner): starts the free trial, once per account, no card and no Upgrade Pass: 5 seats and 1 form for 14 days, then lapsed with no grace.409 trial_unavailablewhen the account has had its trial, the team already has a plan, or the owner's Upgrade Pass already covers the team.
Audit log#
GET /v1/teams/{id}/audit?before=<id>&limit=<=100 (admin): who changed what, newest first, with next
as the cursor. Every change made through the teams API is logged. A row
is written only after a non-read request succeeds, and holds the actor, the route template, and the
ids its path named: never a body, so no key, destination target, message or form text. The merchant's
webhook and our own tools write rows with a system: actor. The table is append-only
(triggers refuse an update, and a delete while the team exists and the row is under 400 days old);
deleting the team or its owner's account erases the log with it.
Ownership transfer#
Two steps, so a team is never dumped on someone: the owner offers, one of the team's other admins
accepts. POST /v1/teams/{id}/transfer {"to": account_id} (owner; the target must be an admin),
DELETE .../transfer (owner withdraws), POST .../transfer/accept and .../transfer/decline (only the
named admin), GET .../transfer (admin; pending with you_are_the_one). An offer lasts 7 days.
Acceptance applies only while the offer is live and
unexpired, the offerer is still the owner and the taker is still an admin; the old owner becomes
an admin, the new one becomes the owner, and the offer is cleared in the same step, so the owner and the owner
role cannot disagree. Refused with 409:
plan_active while a merchant subscription is live (it belongs to whoever pays it: cancel, hand over,
subscribe again) and new_owner_cannot_carry_it while billing is not open and the new owner's own Upgrade
Pass would not let the team send. That check runs again at acceptance.