Skip to content
Documentation for: Echo
HUuptools.io
Markdown

Campaigns

Reading needs Campaigns: view, every change Campaigns: manage.

The API creates regular campaigns with Custom HTML content. A/B, multivariate and resend campaigns, the drag & drop and the simple editor, and templates are made in the app. The API still lists and reads every campaign, and schedules, cancels or deletes them.

Method and path What it does
GET /campaigns Lists campaigns, newest change first.
POST /campaigns Creates a draft.
GET /campaigns/{id} Reads one campaign (with its content if it is Custom HTML).
PATCH /campaigns/{id} Changes a draft.
DELETE /campaigns/{id} Deletes a campaign.
POST /campaigns/{id}/test Sends a test email.
POST /campaigns/{id}/schedule Sends now or schedules a draft.
POST /campaigns/{id}/cancel Takes a scheduled campaign back, or stops one that is sending.
GET /campaigns/{id}/activity The recipients and what they did.
{
"id": "30bc6133-16ad-4074-b311-204127a80b62",
"name": "Winter-ready garden (EN)",
"type": "regular",
"status": "sent",
"editor": "dragdrop",
"subject": "Get your garden ready for winter",
"from_name": "Kertlak Garden Shop",
"from_email": "[email protected]",
"reply_to": "[email protected]",
"recipients": {"all": false, "groups": ["5e30c6ff-0462-47d1-9a6c-7db4c33d6b9a"], "segments": [], "exclude_groups": [], "exclude_segments": [], "has_rules": false},
"delivery": "instant",
"scheduled_for": null,
"created_at": "2026-10-15T07:00:00Z",
"updated_at": "2026-10-20T08:02:25.333333Z",
"sent_at": "2026-10-20T08:02:25.333333Z",
"stats": {"recipients": 1272, "sent": 1272, "delivered": 1266, "opened": 337, "clicked": 57, "unsubscribed": 4, "bounced": 0, "soft_bounced": 6, "complaints": 0, "open_rate": 26.49, "click_rate": 4.48}
}
Key Meaning
type regular, ab, multivariate or resend. Campaigns made in the app are read without their type’s own settings.
status draft, ready (scheduled, in the Outbox), sending, paused, sent or canceled.
editor dragdrop, simple, html (Custom HTML) or null (no content yet).
recipients all (every active subscriber) or the group and segment lists, with exclusions. has_rules is true when the recipients are chosen by a rule set built in the app; the lists are then empty.
delivery instant, scheduled, timezone_based, or null for a draft.
scheduled_for When sending starts, in UTC. For timezone_based, when the first time zone reaches the chosen local time.
sent_at When a sent campaign finished sending.
stats opened and clicked are unique; bounced is permanent bounces; open_rate and click_rate are percentages of sent.
content Only in single-campaign answers of a Custom HTML campaign: html and text.
Parameter Meaning
status draft, outbox (ready, sending, paused) or sent (sent, canceled). Without it: every campaign.
type regular, ab, multivariate, resend.
search Name or subject contains.
limit, cursor Paging. total is always there.
Terminal window
curl -H "Authorization: Bearer $ECHO_TOKEN" "$ECHO_API/campaigns?status=outbox"
{"data":[{"id":"f8ab784a-0a6a-45f0-8dd8-38ecd146826a","name":"Novemberi hírlevél: téli madáretetés","type":"regular","status":"ready","editor":"dragdrop","subject":"Téli madáretetés: mikor kezdjük és mivel?","from_name":"Kertlak Webshop","from_email":"[email protected]","reply_to":"[email protected]","recipients":{"all":false,"groups":["d5478c4a-97d3-4fa9-b38d-dd32c41dbac3"],"segments":[],"exclude_groups":[],"exclude_segments":[],"has_rules":false},"delivery":"scheduled","scheduled_for":"2026-11-05T08:00:00Z","created_at":"2026-10-19T05:20:29.127968Z","updated_at":"2026-10-21T09:20:29.127968Z","sent_at":null,"stats":{"recipients":0,"sent":0,"delivered":0,"opened":0,"clicked":0,"unsubscribed":0,"bounced":0,"soft_bounced":0,"complaints":0,"open_rate":0,"click_rate":0}}],"meta":{"next_cursor":null,"prev_cursor":null,"limit":25,"total":1}}

Lists never contain content.

POST /campaigns. name and content.html are required.

Key Meaning
name Internal name (up to 255 characters).
subject, from_name, from_email, reply_to The email’s details. Without them the workspace’s defaults are used. from_email must be on a verified sending domain; this is checked when you schedule, not before.
recipients all, groups, segments, exclude_groups, exclude_segments (IDs). Replaces the lists.
content.html The Custom HTML. It is sanitized as in the app, and every merge tag in it must exist.
content.text The plain text version. Without it, Echo makes one from the HTML.
Terminal window
curl -X POST "$ECHO_API/campaigns" \
-H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
-d '{"name":"Őszi kertápolás","subject":"Őszi kertápolás: öt tipp","from_name":"Kertlak Webshop","from_email":"[email protected]","recipients":{"groups":["d5478c4a-97d3-4fa9-b38d-dd32c41dbac3"]},"content":{"html":"<h1>Kedves {$name},</h1><p>itt az őszi kertápolási tippsorunk.</p><p><a href=\"{$unsubscribe}\">Leiratkozás</a></p>"}}'

201 Created:

{"data":{"id":"899d27ec-f6f3-4551-a7c5-7fc993cd4002","name":"Őszi kertápolás","type":"regular","status":"draft","editor":"html","subject":"Őszi kertápolás: öt tipp","from_name":"Kertlak Webshop","from_email":"[email protected]","reply_to":"[email protected]","recipients":{"all":false,"groups":["d5478c4a-97d3-4fa9-b38d-dd32c41dbac3"],"segments":[],"exclude_groups":[],"exclude_segments":[],"has_rules":false},"delivery":null,"scheduled_for":null,"created_at":"2026-10-22T06:08:40.347097Z","updated_at":"2026-10-22T06:08:40.363886Z","sent_at":null,"stats":{"recipients":0,"sent":0,"delivered":0,"opened":0,"clicked":0,"unsubscribed":0,"bounced":0,"soft_bounced":0,"complaints":0,"open_rate":0,"click_rate":0},"content":{"html":"\u003ch1\u003eKedves {$name},\u003c/h1\u003e\u003cp\u003eitt az őszi kertápolási tippsorunk.\u003c/p\u003e\u003cp\u003e\u003ca href=\"{$unsubscribe}\"\u003eLeiratkozás\u003c/a\u003e\u003c/p\u003e","text":"***************\nKedves {$name},\n***************\n\nitt az őszi kertápolási tippsorunk.\n\nLeiratkozás ( {$unsubscribe} )"}}}

Merge tags are the fields’ tags ({$name}, {$city}, your custom fields) and the system links {$unsubscribe}, {$preferences}, {$webview} and {$forward}. An unknown tag:

{"error":{"code":"unknown_tags","message":"Unknown personalization tags: {$keresztnev}","fields":{"content.html":"Unknown personalization tags: {$keresztnev}"}}}

If a step after creating the draft fails (for example the content), the draft is deleted again and the answer is that step’s error.

GET /campaigns/{id} returns the campaign; a Custom HTML campaign also has content.

PATCH /campaigns/{id} changes the given keys of a draft (same keys as create). content replaces the whole content, and is accepted for Custom HTML campaigns and for drafts without content yet.

Terminal window
curl -X PATCH "$ECHO_API/campaigns/899d27ec-f6f3-4551-a7c5-7fc993cd4002" \
-H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
-d '{"subject":"Őszi kertápolás: öt tipp a hidegek előtt"}'
Error When
409 not_editable The campaign isn’t a draft (also for a rename), or you sent content for a campaign made in another editor.
409 editor_chosen Another request chose a different editor for this draft at the same moment.

PATCH isn’t atomic: the details are saved before the content, so a content error can leave the other changes saved.

POST /campaigns/{id}/test sends the campaign to 1 to 5 addresses, with [Test] before the subject. The sender must be on a verified sending domain and the content must be set.

Terminal window
curl -X POST "$ECHO_API/campaigns/899d27ec-f6f3-4551-a7c5-7fc993cd4002/test" \
-H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
-d '{"emails":["[email protected]"]}'

202 Accepted, empty body. A workspace can send 50 test emails in any 24 hours, the app’s tests included; over that the answer is 422.

POST /campaigns/{id}/schedule takes a draft out of the drafts:

Key Meaning
delivery instant (send now), scheduled or timezone_based (each subscriber at the same local time).
date YYYY-MM-DD, for scheduled and timezone_based.
time HH:MM, in 15-minute steps, for scheduled and timezone_based.
timezone An IANA name from GET /timezones, for scheduled.

The app’s pre-send checks run first: a sender on a verified sending domain, the company details, an unsubscribe link in the content (unless the automatic footer is on) and recipients. A campaign that can’t be sent answers 422 validation with the fields. Times are at most a year ahead; timezone_based works for regular campaigns at least 24 hours ahead.

Terminal window
curl -X POST "$ECHO_API/campaigns/899d27ec-f6f3-4551-a7c5-7fc993cd4002/schedule" \
-H "Authorization: Bearer $ECHO_TOKEN" -H "Content-Type: application/json" \
-d '{"delivery":"scheduled","date":"2027-09-30","time":"09:00","timezone":"Europe/Budapest"}'

200 OK with the campaign, now "status":"ready", "delivery":"scheduled" and "scheduled_for":"2027-09-30T07:00:00Z". A time off the 15-minute grid:

{"error":{"code":"validation","message":"Please check the highlighted fields.","fields":{"schedule":"Choose a time in the future, in 15-minute steps."}}}

instant really starts sending to every recipient. Use a test email or a small test group first.

POST /campaigns/{id}/cancel (no body): a scheduled campaign goes back to the drafts ("status":"draft", "delivery":null); a sending or paused one stops and becomes canceled. Any other status is 409 not_editable.

Terminal window
curl -X POST "$ECHO_API/campaigns/899d27ec-f6f3-4551-a7c5-7fc993cd4002/cancel" -H "Authorization: Bearer $ECHO_TOKEN"
Terminal window
curl -X DELETE "$ECHO_API/campaigns/899d27ec-f6f3-4551-a7c5-7fc993cd4002" -H "Authorization: Bearer $ECHO_TOKEN"

204 No Content. A sending or paused campaign must be canceled first (409 not_editable), and a campaign that was sent within the last 30 days can’t be deleted yet (409 recently_sent).

GET /campaigns/{id}/activity lists the recipients of a campaign that has started sending (an earlier one answers an empty page).

Parameter Meaning
type all (default), opened, clicked, forwarded, unopened, unsubscribed, hard_bounce, soft_bounce, complaints.
search Email address contains (needs Subscribers: view).
limit, cursor Paging.
Terminal window
curl -H "Authorization: Bearer $ECHO_TOKEN" \
"$ECHO_API/campaigns/30bc6133-16ad-4074-b311-204127a80b62/activity?type=clicked&limit=2"
{"data":[{"subscriber":{"id":"04393fa0-81ff-44a3-82e2-67af141df99a","email":"[email protected]"},"opens":1,"clicks":1,"sent_at":"2026-10-20T08:00:23.444444Z","opened_at":"2026-10-20T08:34:16.826706Z","clicked_at":"2026-10-20T08:47:18.826706Z"},{"subscriber":{"id":"f56a4743-a5db-4d64-b9dc-bb69afdf0659","email":"[email protected]"},"opens":1,"clicks":1,"sent_at":"2026-10-20T08:02:19.666667Z","opened_at":"2026-10-20T09:58:56.816405Z","clicked_at":"2026-10-20T10:09:11.816405Z"}],"meta":{"next_cursor":"cDoy","prev_cursor":null,"limit":2,"total":57}}

opened_at and clicked_at are the first open and click. Without Subscribers: view the subscriber object has only id.