Read your campaigns, and cancel one that is scheduled but has not started sending.
There is no create or send endpoint here, deliberately. Every bulk send holds for explicit approval by the workspace owner before a single recipient is enqueued — see Bulk send. An endpoint that created and sent a campaign in one call would go around that, so it does not exist. An agent can see what is scheduled and stop it; it cannot start one.
Listing requires campaigns:read. Cancelling requires campaigns:cancel — a scope named for what it can do and nothing else.
Status, timing and identity. The campaign body — subject and content — is never returned.
GET /api/v1/campaigns — newest first, cursor-paginated. See Pagination.
GET /api/v1/campaigns/{id}/links — every link in the campaign that was clicked, ranked most-clicked first. Requires campaigns:read.
This is a leaderboard, not a paginated collection, so there is no cursor. limit takes the top N and total_links tells you how many distinct links were clicked — so a truncated list is visible as one.
Unsubscribe links are never in data. They are counted in opt_out_clicks and never named, because those URLs carry a live one-click opt-out token — returning one would let a read-only key opt a recipient out. Two consequences worth planning for: an empty data beside a non-zero opt_out_clicks means the only thing anyone pressed was the way out, which is not the same as nobody clicking; and opt_out_clicks counts presses on an opt-out link rather than completed unsubscribes, so quote unsubscribed from Metrics for that.
clicks counts presses and unique_clicks counts people, so any rate you compute against the number of readers must use unique_clicks — otherwise one enthusiastic recipient can push it over 100%.
reconstructed_clicks above zero means some of these totals were rebuilt from an older record that kept only the most recent click per recipient, so report them as a minimum. null there means we could not determine it, which is not the same as 0.
A transactional message belongs to no campaign, so its clicks are read from GET /api/v1/emails/{id}/clicks instead.
POST /api/v1/campaigns/{id}/cancel — only a campaign in scheduled can be cancelled.
The check is race-safe. If the send worker claimed the campaign moments before your request, nothing is cancelled and you get a 404 — rather than a success for something that did not happen.
One narrow window remains and is worth knowing about: if the worker had already claimed a batch when the cancel landed, that one batch may still go out. Every subsequent batch is stopped.
A campaign that cannot be cancelled — already sending, already sent, or not yours — returns 404 not_found. These are deliberately the same response: telling them apart would confirm the existence of another workspace's campaign id. See Errors.