Campaigns
Create and update draft campaigns, and retrieve campaign details and performance statistics. Drafts created through the API are reviewed, tested, and scheduled or sent from the TopSend dashboard.
campaign_drafts:write permission (Settings → API Keys → “Campaign drafts (no sending)”) to let it create and update drafts without being able to send anything. Sending campaigns or transactional email requires campaigns:write, which also includes draft access. Useful for AI assistants that set up campaigns for you to review./campaignsList all campaigns with summary stats.
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| status | string | Optional | Filter by campaign status: "draft", "scheduled", "sending", "sent", or "paused". |
| limit | integer | Optional | Number of campaigns to return (default: 100, max: 1000). |
| offset | integer | Optional | Number of campaigns to skip for pagination (default: 0). |
curl "https://api.topsend.com/api/v1/campaigns?status=sent&limit=10" \-H "Authorization: Bearer ts_live_abc123"
/campaignsCreate a draft campaign. Requires campaign_drafts:write (or campaigns:write). Nothing is sent.
Provide content as an existing template_id, or as html and/or json_structure (the email builder format — copy it from GET /templates/:id of your brand template and swap in your copy). When you send json_structure without html, the HTML is rendered for you and the draft stays editable in the visual builder. An html-only draft sends exactly as given but opens blank in the visual builder — send json_structure if you want to edit it there. Content passes the same safety checks as templates.
Body Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| name | string | Required | Internal campaign name (max 255). |
| subject | string | Required | Email subject line (max 500). |
| preview_text | string | Optional | Inbox preview text (max 500). |
| list_id | uuid | Optional | Audience list. One of list_id or segment_id is required. |
| segment_id | uuid | Optional | Audience segment. One of list_id or segment_id is required. |
| template_id | uuid | Optional | Use an existing template as-is. Cannot be combined with html/json_structure. |
| html | string | Optional | Full email HTML. |
| json_structure | object | Optional | Email builder document { blocks, settings }. |
curl -X POST https://api.topsend.com/api/v1/campaigns \-H "Authorization: Bearer ts_live_abc123" \-H "Content-Type: application/json" \-H "Idempotency-Key: newsletter-2026-10-01" \-d '{"name": "October Newsletter","subject": "Three things for your next release","preview_text": "Plus a free checklist","list_id": "LIST_ID","html": "<html><body><p>Hey {{first_name}},</p><p>...</p></body></html>"}'
/campaigns/:idUpdate a campaign while it is still a draft. Requires campaign_drafts:write (or campaigns:write).
All fields are optional; send only what changes. Accepts the same fields as create. Setting list_id clears segment_id and vice versa. New html/json_structure updates the draft's own content and never modifies a shared template it was created from. Returns 409 campaign_not_draft once the campaign is scheduled, sending, or sent.
curl -X PATCH https://api.topsend.com/api/v1/campaigns/CAMPAIGN_ID \-H "Authorization: Bearer ts_live_abc123" \-H "Content-Type: application/json" \-d '{ "subject": "Updated subject", "segment_id": "SEGMENT_ID" }'
/campaigns/:idRetrieve a single campaign with detailed performance stats and rates.
curl https://api.topsend.com/api/v1/campaigns/CAMPAIGN_ID \-H "Authorization: Bearer ts_live_abc123"
{"data": {"id": "camp_a1b2c3d4-e5f6-7890-abcd-ef1234567890","name": "January Newsletter","subject": "Your January Update","preview_text": "New features and product updates...","status": "sent","template": {"id": "t1a2b3c4","name": "Monthly Newsletter"},"list": {"id": "l1a2b3c4","name": "Newsletter Subscribers"},"segment": null,"stats": {"queued": 0,"sent": 1200,"delivered": 1185,"bounced": 15,"complained": 2,"opens": 980,"unique_opens": 450,"clicks": 340,"unique_clicks": 120,"unsubscribes": 5,"conversions": 18},"rates": {"delivery_rate": "98.75","open_rate": "37.97","click_rate": "10.13","bounce_rate": "1.25","unsubscribe_rate": "0.42"},"scheduled_at": null,"sent_at": "2025-01-15T09:00:00.000Z","created_at": "2025-01-10T14:00:00.000Z","updated_at": "2025-01-15T09:05:00.000Z"}}
Stats Fields
| Name | Type | Required | Description |
|---|---|---|---|
| queued | integer | Optional | Emails still in the send queue. |
| sent | integer | Optional | Total emails sent (including delivered). |
| delivered | integer | Optional | Emails confirmed delivered by the recipient mail server. |
| bounced | integer | Optional | Emails that bounced (hard or soft). |
| complained | integer | Optional | Emails marked as spam by recipients. |
| opens | integer | Optional | Total open events (includes repeat opens). |
| unique_opens | integer | Optional | Unique recipients who opened the email. |
| clicks | integer | Optional | Total click events (includes repeat clicks). |
| unique_clicks | integer | Optional | Unique recipients who clicked a link. |
| unsubscribes | integer | Optional | Recipients who unsubscribed from this campaign. |
| conversions | integer | Optional | Conversion events attributed to this campaign. |
Rates Fields
All rates are returned as percentage strings with two decimal places.
| Name | Type | Required | Description |
|---|---|---|---|
| delivery_rate | string | Optional | Percentage of sent emails that were delivered. |
| open_rate | string | Optional | Percentage of delivered emails that were opened (unique). |
| click_rate | string | Optional | Percentage of delivered emails with a link click (unique). |
| bounce_rate | string | Optional | Percentage of sent emails that bounced. |
| unsubscribe_rate | string | Optional | Percentage of delivered emails that led to an unsubscribe. |