Docs menu

Run a campaign

When you only have a customer's phone number, a campaign collects their delivery location over WhatsApp (with SMS fallback) and auto-optimizes a route once locations arrive. In test mode you drive the entire lifecycle with scripted magic numbers — no real person is messaged.

Before you start

  • Mint a test key in Settings → API keys — free and instant on a trial account. Test campaigns never require a paid plan.
  • Set your base URL as a shell variable (the hosted portal wires it fromPUBLIC_API_URL).
  • Authenticate with a Bearer header — Authorization: Bearer dk_test_...; never a query-parameter key.
bash
# Your Darb API base URL (the hosted portal wires this from PUBLIC_API_URL)
export DARB_API_BASE="https://<your-darb-api-host>"
# A test key minted in Settings -> API keys
export DARB_KEY="dk_test_..."

Magic numbers

Darb's sandbox scripts three magic phone numbers so you can drive the whole collection flow without messaging a real person:

  • +12025550100 — always replies: shares an in-radius location on the next scan tick, triggering the auto-optimize chain.
  • +12025550101 — never replies: the campaign reaches its deadline with no location.
  • +12025550102 — replies late: the location is delivered after a 5-minute delay.

Any non-magic number defaults to never-reply and is never actually messaged in test mode. Use+12025550100 to see a campaign run all the way to a linked route.

1. Create the campaign

POST /api/v1/campaigns returns 202 with an id andstatus: "collecting". Send an Idempotency-Key header on the POST so a retried request never starts a duplicate campaign — replaying the same key echoes the original and enqueues no new sends.

bash
curl -sS -X POST "$DARB_API_BASE/api/v1/campaigns" \
  -H "Authorization: Bearer $DARB_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: campaign-2026-07-14-0001" \
  -d '{
    "contacts": [
      { "phone": "+12025550100", "name": "Ahmed" }
    ],
    "start": { "lat": 23.60, "lng": 58.40, "label": "Depot" },
    "deadline_at": "2026-07-15T17:00:00Z"
  }'

The 202 response — no coordinates yet, no linked route:

json
{
  "id": "3a9e7b10-6c22-4f88-b0d1-9e8f7a6b5c4d",
  "status": "collecting",
  "route_id": null,
  "purged_at": null,
  "contacts": [
    { "phone": "+12025550100", "name": "Ahmed", "state": "pending", "lat": null, "lng": null }
  ]
}

2. Poll for collected locations

Sends and replies happen in the background (see Conventions → Asynchronous jobs). Poll GET /api/v1/campaigns/{id} untilstatus becomes completed. The always-reply magic number shares an in-radius location, so each contact fills in its lat/lng and the campaign auto-optimizes a route — its id lands in route_id (poll it with theroute endpoint).

bash
# Poll until "status" is "completed" (or "expired"). Use the id from the create response.
curl -sS "$DARB_API_BASE/api/v1/campaigns/$CAMPAIGN_ID" \
  -H "Authorization: Bearer $DARB_KEY"
json
{
  "id": "3a9e7b10-6c22-4f88-b0d1-9e8f7a6b5c4d",
  "status": "completed",
  "route_id": "b6f1c0e2-4d3a-4c1b-9f22-0a1b2c3d4e5f",
  "purged_at": null,
  "contacts": [
    { "phone": "+12025550100", "name": "Ahmed", "state": "location_received", "lat": 23.615, "lng": 58.405 }
  ]
}

Test-mode caps

Test-mode caps: 100 routes, 200 WhatsApp, 100 SMS per month. Test usage never touches your live quota or your bill. Exhausting a cap returns a 402 quota_exceeded — see Conventions → Quotas & billing.

Going live

Next: read the conventions shared by every endpoint, or go back to optimizing a route.