Waymaker
WaymakerDocs
Developer Documentation
Documentation

Journeys audience from your app

A Host app can put its people on a Journeys canvas, with each person's lawful basis for marketing, and take back off the ones it put there. A CRM uses it so that a Journeys campaign with the audience Members with consent mails the people the CRM holds consent for, and stops when the CRM withdraws them.

A membership is not permission to mail. Every send still checks, at that moment, that the membership is subscribed with a basis that allows marketing, that the person has not opted out of all mail from your organisation, and that the address is not on the organisation's suppression list. Anyone reading memberships must apply the same checks.

The grant

One canvas per grant: journeys:audience:write:<canvas id>. Only an org admin can add it, and only for a live canvas in the organisation. * does not include it.

waymaker host apps update <app-id> --commander-permissions "team_ids,journeys:audience:write:<canvas id>"

From an agent, use the MCP tool host_app_update with commander_permissions: ["team_ids", "journeys:audience:write:<canvas id>"].

  • The list replaces what the app had, so include the names it already holds.
  • Removing it does not need an admin. The next call after removal is refused.
  • To find a canvas id, read any campaign on that canvas with the MCP tool commander_journey_get: it is the campaign's canvas_id. Write it in lowercase.

Calling it

The app's own context key only. A signed-in person's token is refused (app_key_required).

POST https://apps.waymakerone.com/functions/v1/host-context-api:

{ "action": "journeys_audience_upsert", "data": { "canvas_id": "<canvas id>", "members": [
  { "email": "pat@example.com", "lawful_basis": "express_opt_in", "note": "ticked the box on the signup form" },
  { "email": "lee@example.com", "lawful_basis": "legitimate_interest_b2b", "contact_type": "business",
    "basis_expires_at": "2027-11-01T00:00:00Z" }
] } }
{ "action": "journeys_audience_archive", "data": { "canvas_id": "<canvas id>", "members": [{ "email": "pat@example.com" }] } }

Both return { canvas_id, results: [{ email, status, reason? }], counts }, one result per member, in order.

Each member

Field
emailRequired. Lower-cased and trimmed.
lawful_basisRequired on upsert: express_opt_in, enquiry, existing_relationship, legitimate_interest_b2b or contract.
contact_typebusiness or consumer. legitimate_interest_b2b needs business.
basis_expires_atOptional. An ISO time in the future. After it, the person is not mailed.
noteOptional, up to 200 characters: where the consent came from.

Each upsert states the person's whole basis as your app holds it now. Leaving out contact_type or basis_expires_at on a later upsert clears it on your membership, so send them every time.

What upsert does

statusMeaning
createdThe person is on the canvas with this basis. A contact is created if the organisation has none for the address; an existing contact's details are never changed. If that person has already opted out of your organisation's mail, the membership is recorded but not mailable, and the result still says created.
updatedYour app's membership now has this basis. A membership your app archived is back in the audience, unless the person has opted out since.
unchangedYour app's membership already had exactly this.
not_ownedSomeone else put this person on the canvas: a person, an import or another app. Left as it is.
invalidNot written. reason: not_an_email, no_lawful_basis, b2b_basis_needs_business_contact, basis_expired, bad_expiry, bad_contact_type, note_too_long.

What archive does

statusMeaning
archivedThe person is no longer in your app's audience on this canvas. It is not an unsubscribe: they did not opt out, and other lists are untouched.
unchangedYour app had already archived them.
not_ownedSomeone else's membership. Left as it is.
not_foundThe person is not on this canvas.
invalidNot an email address.

Your app gets the same answers for its own members whatever their mail state: the results are identical whether or not the person unsubscribed, bounced or is suppressed, and an upsert never makes such a person mailable again. If your app needs mail state, give it journeys:suppressions:read and read the suppression list, where unsubscribes and bounces are recorded.

Limits and errors

  • At most 500 members a call (too_many_members) and 5,000 per app per rolling hour, upserts and archives together. A call over the hour is refused whole with 429 rate_limited and a Retry-After header. Nothing is written, so send the same batch again later.
  • 403 missing_grant names the grant the app needs for that canvas. 403 app_key_required: use the context key.
  • 404 canvas_not_found: the canvas is archived, or not in your organisation.
  • 503: nothing was written. Retry the same call.

Error messages never contain an address.