1. Create a campaign
In the dashboard
- Sign in at briq.tz/login and open the workspace you want to send from.
- Go to the Campaigns tab and click New Campaign.
- Fill in the name, an optional description, and pick the message channel (SMS, voice, or WhatsApp).
- Save. The new campaign appears in the list with status
draft.
draft is just a shell — no audiences, no content, no runs yet. Nothing will be sent until you finish the remaining steps and launch it.
Developer API
X-API-Key (required), Content-Type: application/json (required), X-App-ID (optional — must match the API key’s bound app if both are scoped), Idempotency-Key (optional).
Idempotency-Key in Phase 1. The header is accepted today but not yet enforced — duplicate POSTs may create duplicate campaigns. Send it anyway so SDK retries upgrade cleanly when enforcement ships.Request body
Success response
created_by is set by the server from the authenticated user and cannot be supplied or spoofed in the request body. New campaigns always start in draft regardless of what you send.
Code samples
Error responses
2. Attach audiences
A campaign without audiences has no recipients. Audiences are attached as contact groups — you don’t paste raw phone numbers into a campaign, you reference groups that already live in the workspace.In the dashboard
- Open the campaign you just created.
- Switch to the Audiences tab and click Add contact groups.
- Multi-select existing contact groups from the same workspace and save.
Developer API
There are three audience endpoints: bulk-attach, list, and detach-one.Bulk attach
The response is the full set of currently attached groups (including ones that were already attached before this call). Duplicates in
group_ids are silently kept rather than rejected — re-attaching is a no-op.
Same workspace only. All groups in
group_ids must live in the same workspace as the campaign. Cross-workspace ids return 404, not 403, to avoid leaking which ids exist elsewhere.Recipients at launch are the union of all attached groups. If a contact appears in two attached groups it still only receives one message — deduplication happens at launch time, keyed by phone number per channel.
List attached audiences
data: [] with 200 OK — not a 404.
Detach a single group
200 OK on success. If the group was never attached (or has already been detached), the response is 404.
3. Set the message content
Every campaign needs at least one content row — the actual message that will be delivered. Content comes in three flavours: a reusable Template, a one-off Raw body, or a channel-specific Rich payload.In the dashboard
- Open the campaign and switch to the Content tab.
- Pick the content type your channel supports (Template, Raw, or Rich).
- Fill the form — the dashboard validates as you type (template variables, sender id, required rich fields).
- Save. You can edit or replace the content later as long as the campaign hasn’t launched.
Developer API
content_type. Pick a tab below for the exact body and code samples.
- Template
- Raw
- Rich
Reuse a saved message template and substitute placeholders at send time. Variables are validated against the template’s placeholder set — missing or unknown keys return
400.Payload fields:POST content errors
Update content (PATCH)
payload. Keys you include overwrite the previous values; keys you omit are left alone. Nested objects (like variables) are replaced wholesale rather than merged recursively. content_type is immutable — to switch from raw to template, delete the row and POST a new one.
Delete content
draft, configured, validated, or ready. Deleting in scheduled, running, or any post-launch state returns 409.
Deleting the last content row on a campaign drops it back to draft — the campaign can no longer be validated or launched until new content is attached.
4. Schedule a run
A run is when the campaign actually sends. A campaign can have many runs — a single send tonight, a daily recurring run for a month, or both. Until a run exists the campaign has nothing to do at launch time.In the dashboard
- Open the campaign and switch to the Schedule tab.
- Pick Single send or Recurring.
- Set the date, time, and timezone. For recurring runs also set the frequency and an end date.
- Save. The run appears in the runs list at status
ready.
Developer API
run_type. Pick a tab below.
New runs start at
status: "ready", not scheduled. They only move to scheduled after you launch the campaign. A campaign with audiences, content, and a ready run will not send anything until you call launch — see Launch control.- Single run
- Recurring run
One-off send at a specific moment.Body fields:
Naive datetimes (no
Z and no +HH:MM) are interpreted as UTC. Always send an offset to avoid surprises.Response shape
The response is always a list, even when you created a single run. Forrecurring, the returned row is the template — its recurrence_index is null and it represents the recurrence definition, not a concrete occurrence. Children (with recurrence_index 0, 1, 2, …) are materialised at launch time, not at schedule time, so you will see exactly one row per call until the campaign launches.
Schedule errors
List runs
scheduled_at ASC. Supports filtering:
Delete a run
ready and scheduled runs are deletable. Any other status (running, completed, cancelled, failed) returns 409. To stop a running run, use the launch-control transitions in Launch control, not this endpoint.
Common gotchas
- One content per channel. POSTing a duplicate
content_typereturns 409 —PATCHthe existing row instead of trying to add another. - Editing a launched campaign undoes its launch state. Adding audiences, editing content, or adding a run on a
readyorscheduledcampaign drops it back toconfigured. You must re-validate (and re-launch) before it will send. - PATCH content does a shallow merge. Top-level keys merge; nested objects (like
variables) are replaced wholesale. To preserve fields, send the full object back. run_type: "recurring"returns a template row, not a concrete occurrence. Children withrecurrence_index0, 1, 2, … are materialised at launch time. Until then you see exactly one row per call.scheduled_atis parsed as UTC if naive. Always send an ISO-8601 datetime with offset (Zor+HH:MM) — otherwise a “9am” you meant locally will fire at “9am UTC”.