Skip to main content
Once a campaign launches, you can watch its progress in real time and inspect the final delivery breakdown when it completes. The Briq dashboard shows all of this on the campaign view — counters, per-batch status, recipient list, delivery rate. The developer API exposes the same data through three read-only endpoints.
Status translation. Every run-status field in the responses below translates the engine’s internal claimed sentinel to running. As an API consumer you will never see claimed — treat running as the in-flight state.

1. Live status — for polling loops

Dashboard. The campaign view updates automatically as batches complete. You see the current batch / total batches and live counters for sent, failed, skipped, and delivered. Developer API.
GET https://karibu.briq.tz/v1/campaign/{campaign_id}/status
  • Returns the latest run’s execution status plus batch progress counters.
  • Mirrors the user-facing /campaigns/{id}/status endpoint shape — the same fields whether you call the developer or the user surface.
  • This is the endpoint to poll while a campaign is active.

Success response (run exists)

{
  "success": true,
  "data": {
    "campaign_id": "8d2e1f6a-...",
    "campaign_run_id": "run_789",
    "status": "running",
    "current_batch": 1,
    "total_batches": 2,
    "recipient_count": 1500,
    "sent": 1000,
    "failed": 5,
    "skipped": 0,
    "delivered": 800,
    "updated_at": "2026-05-12T09:14:02.218Z"
  },
  "errors": null,
  "request_id": "req_01J..."
}

Response when no run yet

{
  "success": true,
  "data": {
    "campaign_id": "8d2e1f6a-...",
    "status": "NO_RUN"
  },
  "errors": null,
  "request_id": "req_01J..."
}
NO_RUN is a sentinel, not a run status. When the campaign has zero runs, the endpoint returns the literal string "NO_RUN" in status. This value is not part of the normal run-status enum (scheduled, running, completed, failed, cancelled). Check for status === "NO_RUN" before treating status as a run state.

Field reference

FieldTypeNotes
campaign_idstring (uuid)The campaign you queried.
campaign_run_idstring | nullLatest run’s id. Absent / null when status === "NO_RUN".
statusstringOne of scheduled, running, completed, failed, cancelled, or NO_RUN.
current_batchintCount of completed batches, not the batch currently in flight.
total_batchesintTotal batch count for the latest run.
recipient_countintRecipients on this run.
sentintPer-recipient submit successes recorded so far.
failedintPer-recipient submit failures recorded so far.
skippedintRecipients skipped (e.g. invalid number, dedupe).
deliveredintDelivery-report confirmations recorded so far (lags sent).
updated_atstring (ISO 8601)Last time the counters or status were touched.

Errors

HTTPWhen
401Missing or invalid X-API-Key.
403API key is valid but does not own this campaign’s workspace.
404campaign_id does not exist.
500Server error — safe to retry with backoff.

Code samples

curl -X GET "https://karibu.briq.tz/v1/campaign/8d2e1f6a-.../status" \
  -H "X-API-Key: YOUR_API_KEY"

Polling cadence

A reasonable polling rhythm.
  • 5–15 seconds is fine while status is scheduled or running.
  • Stop polling as soon as status flips to completed, failed, or cancelled — counters are terminal at that point.
  • Then call /analytics once to grab the final rollup. Do not keep polling /status after the terminal transition.

2. Aggregate analytics

Dashboard. The campaign’s “Analytics” tab shows total recipients, sent / delivered / failed / skipped counts, plus delivery and failure rates as percentages. Developer API.
GET https://karibu.briq.tz/v1/campaign/{campaign_id}/analytics
  • Returns campaign-wide aggregate analytics across all runs (delivery rate, failure rate, counts, timing).
  • When no analytics row has been written yet (e.g. a campaign created but never run, or polled in the first seconds after launch), the endpoint returns a zeroed-out payload with null rate fields. HTTP status is still 200 — check delivery_rate !== null to know whether percentages are renderable.

Success response (populated)

{
  "success": true,
  "data": {
    "campaign_id": "8d2e1f6a-...",
    "campaign_run_id": "run_789",
    "workspace_id": "ws_01J...",
    "total_recipients": 1500,
    "sent_count": 1495,
    "failed_count": 5,
    "skipped_count": 0,
    "cancelled_count": 0,
    "delivered_count": 1400,
    "failed_delivery_count": 95,
    "pending_delivery_count": 0,
    "delivery_rate": 0.9398,
    "failure_rate": 0.0033,
    "first_sent_at": "2026-05-12T09:12:01.001Z",
    "last_sent_at":  "2026-05-12T09:14:48.812Z",
    "completed_at":  "2026-05-12T09:14:59.044Z"
  },
  "errors": null,
  "request_id": "req_01J..."
}

Field reference

FieldTypeNotes
campaign_idstring (uuid)The campaign you queried.
campaign_run_idstring | nullLatest run referenced by the rollup, or null before any run completes.
workspace_idstringWorkspace that owns the campaign.
total_recipientsintAggregate recipient count across runs.
sent_countintSubmit successes.
failed_countintSubmit failures.
skipped_countintRecipients skipped before submit.
cancelled_countintRecipients on cancelled runs.
delivered_countintDLR-confirmed deliveries.
failed_delivery_countintDLR-confirmed delivery failures.
pending_delivery_countintSent but no terminal DLR yet.
delivery_ratefloat | nulldelivered_count / sent_count as a 0.0–1.0 float. null before any data.
failure_ratefloat | nullfailed_count / total_recipients as a 0.0–1.0 float. null before any data.
first_sent_atstring | nullISO 8601 UTC. First successful submit across all runs.
last_sent_atstring | nullISO 8601 UTC. Last successful submit across all runs.
completed_atstring | nullISO 8601 UTC. When the latest run reached a terminal state.
Rates are 0.0–1.0 floats — multiply by 100 for display. A delivery_rate of 0.9398 is 93.98%, not 0.94%. Never display the raw value as a percentage. Always check for null before rendering.
Analytics rows are written after the first batch completes. There is a small delay (seconds) between a campaign starting and analytics being non-zero. Do not hit /analytics immediately after /launch and expect populated counters — poll /status until it reports activity, or wait for the terminal transition.

Errors

HTTPWhen
401Missing or invalid X-API-Key.
403API key does not own this campaign’s workspace.
404campaign_id does not exist.
500Server error — safe to retry with backoff.

Code samples

curl -X GET "https://karibu.briq.tz/v1/campaign/8d2e1f6a-.../analytics" \
  -H "X-API-Key: YOUR_API_KEY"

3. Run detail — composite payload

Dashboard. Click any run in the campaign’s “Runs” tab to open a detail modal — the run row, channel + provider + sender metadata, per-batch progress, and a per-recipient list with live message status. Developer API.
GET https://karibu.briq.tz/v1/campaign/{campaign_id}/runs/{run_id}
Returns the composite “run detail” payload — a single round-trip that gives you everything for a run-detail UI:
FieldTypeWhat it contains
runobjectThe run row — translated status, counters, timestamps.
campaign_namestringDisplay name of the parent campaign.
channel_namestringDisplay name of the channel used (e.g. SMS).
channel_providerstringProvider that fulfilled the run (e.g. mock, carrier name).
snapshotobjectExecution snapshot summary: provider, sender_id, channel, template, personalized, frozen_at.
batches[]arrayPer-batch progress (see fields below).
recipients[]arrayJoined recipient list with recipient_status, message_id, message_status, sent_at. Capped at 500 rows.
recipient_summaryobject{ total, pending, sent, delivered, failed } — always accurate even when recipients[] is truncated.
recipients_truncatedbooleantrue when the run has more than 500 recipients and recipients[] was clipped.

Batch fields

FieldTypeNotes
idstring (uuid)Batch identifier.
batch_sequenceint1-indexed position within the run.
statusstringpending, running, completed, failed.
recipient_countintRecipients in this batch.
sent_countintSubmit successes within the batch.
failed_countintSubmit failures within the batch.
started_atstring | nullISO 8601 UTC.
completed_atstring | nullISO 8601 UTC.
error_detailobject | nullPopulated when status === "failed".

Recipient fields

FieldTypeNotes
recipient_idstringPer-run recipient row id.
phone_numberstringE.164 digits.
recipient_statusstringPer-run projection; may lag the live message state by seconds.
message_idstring | nullThe dispatched message; null until submit lands.
message_statusstring | nullLive status from the messages table — the authoritative state of the message right now.
sent_atstring | nullISO 8601 UTC.
recipients[] is capped at 500 rows per response. When more exist, the API sets recipients_truncated: true and clips the array. Use recipient_summary for the aggregate counts — do not derive totals by counting recipients[]. If you need every recipient for a large run, page through the run via the dashboard or contact Briq for a bulk export.
message_status may diverge from recipient_status. The message status is the live truth pulled from the messages table; recipient_status is a slightly stale projection rolled up onto the per-run recipient row. Prefer message_status when you display the current state of a single message.

Errors

HTTPWhen
401Missing or invalid X-API-Key.
403API key does not own this campaign’s workspace.
404campaign_id does not exist, or run_id does not belong to that campaign.
500Server error — safe to retry with backoff.

Code samples

curl -X GET "https://karibu.briq.tz/v1/campaign/8d2e1f6a-.../runs/run_789" \
  -H "X-API-Key: YOUR_API_KEY"

Run snapshot (read-only, PII-gated)

Developer API.
GET https://karibu.briq.tz/v1/campaign/{campaign_id}/runs/{run_id}/snapshot
  • Returns the engine’s execution_snapshot — the frozen, point-in-time state used to dispatch this run: content, sender, channel, provider, cost, recipient count, and optionally the recipient list.
  • Query param ?include_recipients=true opts into returning the recipient phone-number list. Stripped by default.
?include_recipients=true returns PII. The recipient list contains phone numbers. Never call this variant from end-user-facing surfaces (e.g. a customer-supportable web app, an unauthenticated dashboard view). Treat it as a server-to-server, admin-tier read. The caller is responsible for handling returned phone numbers under their own compliance regime.
cost is in service units, not currency. total_cost is denominated in currency_unitssms_parts, voice minutes, etc. — matching the units convention used by /validate. Do not display this value as money. Convert to your billing currency client-side if you need to show a price.
Snapshot may be partial before dispatch. For a run still in ready status (queued but not yet picked up by the worker), the engine may not have frozen the full snapshot yet — some fields may be null. Re-read after the run transitions to running or later.

Success response (default, without recipients)

{
  "success": true,
  "data": {
    "run_id": "run_789",
    "campaign_id": "8d2e1f6a-...",
    "recipient_count": 1500,
    "content": {
      "channel": "SMS",
      "provider": "mock",
      "sender_id": "BRIQ",
      "body": "Hello, thanks for joining."
    },
    "cost": {
      "total_cost": 1500,
      "currency_units": "sms_parts"
    },
    "status": "running",
    "started_at": "2026-05-12T09:12:00.118Z",
    "completed_at": null
  },
  "errors": null,
  "request_id": "req_01J..."
}

Success response (with ?include_recipients=true)

The same payload, plus a recipients array of phone numbers (E.164 digits):
{
  "success": true,
  "data": {
    "run_id": "run_789",
    "campaign_id": "8d2e1f6a-...",
    "recipient_count": 1500,
    "content": { "...": "..." },
    "cost":    { "...": "..." },
    "recipients": ["255712345678", "255713000111", "..."],
    "status": "running",
    "started_at": "2026-05-12T09:12:00.118Z",
    "completed_at": null
  },
  "errors": null,
  "request_id": "req_01J..."
}

Errors

HTTPWhen
401Missing or invalid X-API-Key.
403API key does not own this campaign’s workspace.
404campaign_id does not exist, run_id does not belong to that campaign, or no snapshot has been frozen yet.
500Server error — safe to retry with backoff.

Code samples

# Default — no PII
curl -X GET "https://karibu.briq.tz/v1/campaign/8d2e1f6a-.../runs/run_789/snapshot" \
  -H "X-API-Key: YOUR_API_KEY"

# Opt in to the recipient list (server-side only)
curl -X GET "https://karibu.briq.tz/v1/campaign/8d2e1f6a-.../runs/run_789/snapshot?include_recipients=true" \
  -H "X-API-Key: YOUR_API_KEY"

What happens after launch

A short narrative of the system behaviour that drives the numbers you see above. Useful if you are building a poller and want to understand the timing.
  1. Pickup (≤ 30 seconds). A separate worker microservice runs a cron query of campaign_runs WHERE status = 'scheduled' AND scheduled_at <= now(). It claims the row by flipping its status to the internal claimed sentinel. The API translates claimed to running in every response, so you will only ever see running.
  2. Batches of 1000. The engine partitions the run’s recipients into batches of up to 1000 and writes a campaign_batches row per partition. Each batch is dispatched through the campaign’s channel + provider; per-recipient rows land in campaign_recipients and messages.
  3. Counters roll up. As batches complete, sent_count, failed_count, skipped_count, and delivered_count on the run row are incremented. The parent campaign’s aggregate counters are recomputed off the run’s totals — this is what populates the next /status and /analytics response.
  4. Terminal state and analytics. When all batches finish, the run flips to completed (or failed). The engine then writes a campaign_analytics row — at this point /analytics returns its populated payload.
  5. Optional refund. If a run failed partway through, or the campaign was cancelled, the engine refunds the unsent portion of the credit reservation. Synchronous on /cancel; reconciler-driven for other failure paths.

Practical cadence

A 4-bullet recap of what to call when:
  • During scheduled / running: poll /status every 5–15 seconds.
  • On terminal transition (completed, failed, or cancelled): stop polling, call /analytics once for the final rollup.
  • For a run-level UI (drilldown modal, per-recipient list): call /runs/{run_id} for the composite payload. Use the snapshot endpoint only when you need the frozen dispatch state.
  • Prefer the dashboard for ad-hoc inspection. Build a poller only when you need automation — e.g. piping data into your own analytics, alerting on failure rates, or driving a custom operator console.