Hanzo

Marketing

Package marketing is lifecycle email: drip sequences that reach the right people.

Package marketing is lifecycle email: drip sequences that reach the right people.

Base URLhttps://api.hanzo.ai
Operations35
AuthAuthorization: Bearer $HANZO_API_KEY

marketing

GET /v1/marketing/audiences/{id}/preview

Evaluates the cohort LIVE — the same resolution an enrollment would run — and reports how big it is and how many real mailboxes it reaches. It is the honest answer to "is this send worth making": a cohort of 500 that mails 3 says so, in deliverable and unmatched. Nothing is sent.

ParameterInTypeRequiredDescription
idpathstringyesID is the audience id from the path, as returned by create.

GET /v1/marketing/audiences/{id}

Returns one of the caller org's saved audiences. An audience belonging to another org reads as not found.

ParameterInTypeRequiredDescription
idpathstringyesID is the audience id from the path, as returned by create.

DELETE /v1/marketing/audiences/{id}

Removes one of the caller org's audiences and answers 204. It deletes the saved filter only — no customer, event or enrollment is touched.

ParameterInTypeRequiredDescription
idpathstringyesID is the audience id from the path, as returned by create.

GET /v1/marketing/audiences

Returns the org's saved audiences, most recently updated first.

ParameterInTypeRequiredDescription
limitqueryintegerLimit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.

POST /v1/marketing/audiences

Saves a cohort filter for the caller's org. Name is required. Omitting event saves the WHOLE-ORG audience — every mailable customer — which needs no analytics warehouse; naming one narrows that roster to the customers who fired it within windowDays.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
createdAtintegerCreatedAt and UpdatedAt are unix seconds, both server-assigned.
eventstringEvent is the analytics event a member must have fired.
idstringID is the server-assigned audience id ("aud_" + 128 random bits).
namestringName is the audience's label.
updatedAtinteger
windowDaysintegerWindowDays is how far back the event counts, ending now. 0 means 30 and nothing above 3650 is honoured.

POST /v1/marketing/calendar/{id}/publish

Publishes a post NOW, synchronously, whatever its schedule. No social connector is wired today, so every channel answers an honest 501 naming the seam a real one would plug into, and the post is recorded failed with that exact reason — never a faked "published".

ParameterInTypeRequiredDescription
idpathstringyesID is the post id from the path, as returned by create.

GET /v1/marketing/calendar/{id}

Returns one of the caller org's posts, including the exact error behind a failed publish. A post belonging to another org reads as not found.

ParameterInTypeRequiredDescription
idpathstringyesID is the post id from the path, as returned by create.

PUT /v1/marketing/calendar/{id}

Replaces a post's editable fields. It is a full write, not a patch, and it RESETS the lifecycle from the schedule: a scheduledAt makes the post "scheduled" again and none makes it a draft — so editing a failed post requeues it rather than leaving it stuck.

ParameterInTypeRequiredDescription
idpathstringyesID is the server-assigned post id ("cal_" + 128 random bits).

Request bodyapplication/json (required)

FieldTypeRequiredDescription
bodystringBody is the post text.
channelstringChannel is the target network: x, facebook, instagram, linkedin, tiktok, youtube or threads.
createdAtintegerCreatedAt and UpdatedAt are unix seconds, both server-assigned.
errorstringError is the exact reason the last publish attempt failed — the honest record behind a "failed" status, never a faked…
idstringID is the server-assigned post id ("cal_" + 128 random bits).
publishedAtintegerPublishedAt is when the publish succeeded; 0 until it does.
scheduledAtintegerScheduledAt is the unix publish time; 0 leaves the post a draft, and any value makes it "scheduled" for the durable…
statusstringStatus is draft, scheduled, published, failed or canceled.
titlestringTitle is the post's internal label, capped at 1024 bytes.
updatedAtinteger

DELETE /v1/marketing/calendar/{id}

Removes one of the caller org's posts and answers 204. A post already published is deleted from the calendar only — nothing is retracted from the network it went out on.

ParameterInTypeRequiredDescription
idpathstringyesID is the post id from the path, as returned by create.

GET /v1/marketing/calendar

Returns the org's calendar, soonest scheduled first, optionally narrowed to one status.

ParameterInTypeRequiredDescription
statusquerystringStatus keeps only posts in that state (draft, scheduled, published, failed, canceled).
limitqueryintegerLimit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.

POST /v1/marketing/calendar

Adds a post to the content calendar. Channel and body are required. A scheduledAt in the future makes the post "scheduled" and the durable sweep publishes it when it comes due — claimed once, so a post publishes at most once; without one it stays a draft.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
bodystringBody is the post text.
channelstringChannel is the target network: x, facebook, instagram, linkedin, tiktok, youtube or threads.
createdAtintegerCreatedAt and UpdatedAt are unix seconds, both server-assigned.
errorstringError is the exact reason the last publish attempt failed — the honest record behind a "failed" status, never a faked…
idstringID is the server-assigned post id ("cal_" + 128 random bits).
publishedAtintegerPublishedAt is when the publish succeeded; 0 until it does.
scheduledAtintegerScheduledAt is the unix publish time; 0 leaves the post a draft, and any value makes it "scheduled" for the durable…
statusstringStatus is draft, scheduled, published, failed or canceled.
titlestringTitle is the post's internal label, capped at 1024 bytes.
updatedAtinteger

POST /v1/marketing/campaigns/{id}/schedule

Sets a campaign's send time and moves it to "scheduled". A scheduledAt of 0 clears the schedule and returns it to "draft".

ParameterInTypeRequiredDescription
idpathstringyesID is the campaign id from the path.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
idstringID is the campaign id from the path.
scheduledAtintegerScheduledAt is the unix send time.

GET /v1/marketing/campaigns/{id}

Returns one of the caller org's campaigns. A campaign belonging to another org reads as not found.

ParameterInTypeRequiredDescription
idpathstringyesID is the campaign id from the path, as returned by create.

PUT /v1/marketing/campaigns/{id}

Replaces a campaign's editable fields. It is a full write, not a patch: every field takes the value in the body, and an omitted one is cleared. The id comes from the path — the body cannot retarget another campaign — and createdAt is never rewritten.

ParameterInTypeRequiredDescription
idpathstringyesID is the server-assigned campaign id ("camp_" + 128 random bits).

Request bodyapplication/json (required)

FieldTypeRequiredDescription
budgetintegerBudget and Spend are minor units (USD cents), clamped to >= 0.
channelstringChannel is the delivery surface: email, sms, social, meta, google or tiktok.
createdAtintegerCreatedAt and UpdatedAt are unix seconds, both server-assigned.
idstringID is the server-assigned campaign id ("camp_" + 128 random bits).
namestringName is the campaign's label.
objectivestringObjective is the free-text goal ("signups"), capped at 1024 bytes.
scheduledAtintegerScheduledAt is the unix send time; 0 means unscheduled.
spendinteger
statusstringStatus is the lifecycle: draft, scheduled, active, paused or completed.
updatedAtinteger

DELETE /v1/marketing/campaigns/{id}

Removes one of the caller org's campaigns and answers 204. A campaign belonging to another org reads as not found and is left untouched.

ParameterInTypeRequiredDescription
idpathstringyesID is the campaign id from the path, as returned by create.

GET /v1/marketing/campaigns

Returns the org's campaigns, most recently updated first, optionally narrowed to one lifecycle status.

ParameterInTypeRequiredDescription
statusquerystringStatus keeps only campaigns in that lifecycle state (draft, scheduled, active, paused, completed).
limitqueryintegerLimit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.

POST /v1/marketing/campaigns

Registers a campaign in the caller's org. Name is required; channel defaults to email and status to draft, and a future scheduledAt with no explicit status makes the campaign "scheduled". Budget and spend are cents and are clamped to >= 0. The id, createdAt and updatedAt of the input are ignored — the server assigns them.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
budgetintegerBudget and Spend are minor units (USD cents), clamped to >= 0.
channelstringChannel is the delivery surface: email, sms, social, meta, google or tiktok.
createdAtintegerCreatedAt and UpdatedAt are unix seconds, both server-assigned.
idstringID is the server-assigned campaign id ("camp_" + 128 random bits).
namestringName is the campaign's label.
objectivestringObjective is the free-text goal ("signups"), capped at 1024 bytes.
scheduledAtintegerScheduledAt is the unix send time; 0 means unscheduled.
spendinteger
statusstringStatus is the lifecycle: draft, scheduled, active, paused or completed.
updatedAtinteger

GET /v1/marketing/promos/{code}/eligibility

Prices a promo against a plan and seat count. It is PURE: nothing is redeemed, credited or counted, so it is safe to call from a pricing page on every keystroke. An inactive promo or an exhausted cap quotes ineligible with the reason rather than erroring.

ParameterInTypeRequiredDescription
codepathstringyesCode is the promo code from the path.
planquerystringPlan is the plan being priced: pro, max or team.
seatsqueryintegerSeats is the Team seat count; 0 means 1, and it is ignored for the single-seat plans.

POST /v1/marketing/promos/{code}/redeem

Records the caller org's claim on a promo. NOTHING IS CREDITED: the redemption is a row, and credit into an org is an admin decision made on the admin surface against an auditable ledger.

The plan is DERIVED from the org's live ACTIVE/TRIALING paid subscription and can never be named by the caller — an org with no qualifying subscription is refused, and so is one whose subscription cannot be read. The seat count is the single-seat floor (claimSeats), so the recorded figure has no input that can inflate it.

Guards run under one lock so the cap cannot be raced past: the fleet-wide redemption cap, one redemption per org, one per payment instrument (REQUIRED), and the per-redemption ceiling.

It is IDEMPOTENT: an org that already redeemed gets its original redemption back with alreadyRedeemed true.

ParameterInTypeRequiredDescription
codepathstringyesCode is the promo code from the path.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
codestringCode is the promo code from the path.
instrumentstringInstrument identifies the payment method. It is the anti-farming key: one redemption per instrument, fleet-wide, and it…

GET /v1/marketing/promos/{code}/redemption

Returns the caller org's OWN redemption of a promo — an org-scoped read, so it can never surface another tenant's. Not found when this org has not redeemed it.

ParameterInTypeRequiredDescription
codepathstringyesCode is the promo code from the path, e.g.

GET /v1/marketing/promos

Returns every promo the deployment offers with its live counters: how many orgs have redeemed it and how many redemptions remain under the cap. The promos are fleet-wide, not per-org — only the counters move.

POST /v1/marketing/sequences/{id}/enroll

Adds one contact or a whole audience to a sequence and schedules the first step for each. The sequence must be ACTIVE (a draft sends nothing), and the request must name exactly one of address or audienceId.

Enrolling is ALL this does: the message itself is sent later by the drip engine, through the suppression gate, so an opted-out customer can be enrolled here and still never be mailed. Re-posting is safe — an address this sequence already took is counted in alreadyEnrolled and never double-dripped — which is what makes retrying a partially-applied announcement a resume rather than a second send.

ParameterInTypeRequiredDescription
idpathstringyesID is the sequence id from the path.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
addressstringAddress is a single recipient, normalized (lower-cased, trimmed) before use.
audienceIdstringAudienceID fans the sequence out over a saved audience, resolved live to the org's mailable customers.
channelstringChannel is the delivery surface; empty means email.
idstringID is the sequence id from the path.

POST /v1/marketing/sequences/{id}/enrollments/{eid}/cancel

Stops one walk mid-sequence and answers 204: no further step is sent, and steps already delivered are not recalled. Only an ACTIVE enrollment can be canceled — one already completed or canceled reads as not found.

ParameterInTypeRequiredDescription
idpathstringyesID is the sequence id from the path.
eidpathstringyesEID is the enrollment id from the path, as returned by a single-address enroll.

GET /v1/marketing/sequences/{id}/enrollments

Returns who is walking one sequence, most recently enrolled first, with each walk's current step and next due time.

ParameterInTypeRequiredDescription
idpathstringyesID is the sequence id from the path.
limitqueryintegerLimit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.

POST /v1/marketing/sequences/{id}/status

Flips draft/active/archived — the activation gate for sending, since only an active sequence accepts enrollments. It does not touch enrollments already walking: archiving stops new ones, not in-flight ones.

ParameterInTypeRequiredDescription
idpathstringyesID is the sequence id from the path.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
idstringID is the sequence id from the path.
statusstringStatus is draft, active or archived. Required; there is no default here, unlike on create.

GET /v1/marketing/sequences/{id}/steps

Returns one sequence's steps in send order.

ParameterInTypeRequiredDescription
idpathstringyesID is the sequence id from the path, as returned by create.

POST /v1/marketing/sequences/{id}/steps

Appends a message to the END of a sequence: the new step's idx is one past the last, so steps arrive in the order they are added. Body is required and delaySeconds must be >= 0. Adding a step does not disturb enrollments already walking — one that has passed this index simply never sees it.

ParameterInTypeRequiredDescription
idpathstringyesSequenceID is the sequence id from the path (the route's :id).

Request bodyapplication/json (required)

FieldTypeRequiredDescription
bodystringBody is the message text.
delaySecondsintegerDelaySeconds is how long after the previous step this one sends (after enrollment, for the first step).
idstringSequenceID is the sequence id from the path (the route's :id).
subjectstringSubject is the email subject line, capped at 1024 bytes.

GET /v1/marketing/sequences/{id}

Returns one of the caller org's sequences together with its steps in send order. A sequence belonging to another org reads as not found.

ParameterInTypeRequiredDescription
idpathstringyesID is the sequence id from the path, as returned by create.

GET /v1/marketing/sequences

Returns the org's drip sequences, most recently updated first.

ParameterInTypeRequiredDescription
limitqueryintegerLimit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.

POST /v1/marketing/sequences

Registers a drip sequence in the caller's org. Name is required; status defaults to draft, and a sequence must be ACTIVE before it will accept enrollments. The id, createdAt and updatedAt of the input are ignored — the server assigns them.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
createdAtintegerCreatedAt and UpdatedAt are unix seconds, both server-assigned.
idstringID is the server-assigned sequence id ("seq_" + 128 random bits).
namestringName is the sequence's label.
statusstringStatus is the lifecycle: draft, active or archived.
updatedAtinteger

GET /v1/marketing/summary

Rolls up the caller org's campaigns: how many there are, how many are active, and the summed budget and spend in cents.

GET /v1/marketing/suppressions

Returns the org's opt-out list, newest first — everyone the send gate will refuse to deliver to.

ParameterInTypeRequiredDescription
limitqueryintegerLimit caps the rows returned; 0 means 200 and nothing above 1000 is honoured.

POST /v1/marketing/suppressions

Records an opt-out for the org (admin / self-service management). Address is required; channel defaults to email. It is idempotent: re-suppressing the same tuple keeps the original record rather than erroring. From here on the ONE send gate refuses that recipient on that channel.

Request bodyapplication/json (required)

FieldTypeRequiredDescription
addressstringAddress is the recipient, normalized (lower-cased, trimmed) so an opt-out cannot be slipped past on a case or…
channelstringChannel is the surface opted out of: email, sms, social, meta, google or tiktok. Empty means email.
createdAtintegerCreatedAt is unix seconds, server-assigned.
reasonstringReason is a free-text note, capped at 1024 bytes.

DELETE /v1/marketing/suppressions

Re-subscribes an address on one channel and answers 204. An address that is not on the list reads as not found.

ParameterInTypeRequiredDescription
channelquerystringChannel is the surface opted out of: email, sms, social, meta, google or tiktok. Empty means email.
addressquerystringAddress is the recipient, normalized (lower-cased, trimmed) so an opt-out cannot be slipped past on a case or…
reasonquerystringReason is a free-text note, capped at 1024 bytes.
createdAtqueryintegerCreatedAt is unix seconds, server-assigned.

GET /v1/marketing/unsubscribe

Is the PUBLIC one-click endpoint (no principal): a recipient clicks the signed link in an email footer. The token binds (org, channel, address), so a caller can only opt OUT exactly the tuple it was minted for — never another address and never another org. An invalid token is refused, and a deployment with no KMS-sealed key refuses rather than accepting anything.

ParameterInTypeRequiredDescription
orgquerystringOrg is the org the link was minted for.
channelquerystringChannel is the surface to opt out of.
addressquerystringAddress is the recipient to opt out.
tokenquerystringToken is the HMAC over (org, channel, address).

All Hanzo APIs · Interactive reference

How is this guide?

On this page