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 from
PUBLIC_API_URL). - Authenticate with a Bearer header —
Authorization: Bearer dk_test_...; never a query-parameter key.
# 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.
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:
{
"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).
# 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"{
"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.