Skip to content
WaaruDocs

Campaign results

Read persisted campaign outcomes for one WhatsApp number.

Reviewed

Use campaigns:read to read existing campaigns for the key's number while Logic Flow continues handling replies. Grant this scope explicitly to an existing or new key; the key manager needs broadcast-read authority. It provides no campaign creation, scheduling, retarget action or send permission.

Read a campaign

RouteResponse
GET /v1/developer/campaigns{items, nextCursor}
GET /v1/developer/campaigns/:id{campaign}
GET /v1/developer/campaigns/:id/recipients{items, nextCursor}

Summaries include id, name, status, templateId, pauseCode, recipient and delivery counters, quick-reply counters and lifecycle timestamps. The exact counters are totalRecipients, admittedCount, quotaWaitingCount, providerAcceptedCount, sentCount, deliveredCount, readCount, repliedCount, failedCount, quickReplyCount and quickReplyUniqueCount. Timestamps are scheduledAt, startedAt, completedAt, sendCompletedAt, outcomeSettledAt, metricsReconciledAt, createdAt and updatedAt; all except creation/update may be null. These are persisted broadcast values. Provider acceptance is distinct from delivery, and send completion is distinct from settled outcomes.

Detail adds failureCohorts and settlementCohorts, keyed by the corresponding enums. Zero-count keys can be absent. These resources do not attribute orders, revenue, conversions or business costs.

Page through current results

Both list routes accept limit from 1 to 100 (default 50) and an opaque after cursor up to 512 characters. Follow nextCursor as after until null. Results use ascending immutable IDs. Keep filters and the parent campaign unchanged: the cursor binds workspace, number, resource and filters. Changing the page size is allowed. A malformed or wrong-context cursor returns 400 invalid_cursor.

Campaign list optionally filters status: DRAFT, SCHEDULED, QUEUED, SENDING, PAUSED, COMPLETED, FAILED or CANCELLED. Recipient lists optionally filter:

FieldValues
statusWAITING_QUOTA, ADMITTED, QUEUED, SEND_UNCERTAIN, PROVIDER_ACCEPTED, SENT, DELIVERED, READ, REPLIED, FAILED, SKIPPED, OPTED_OUT
settlementStatePENDING, SETTLED_SUCCESS, SETTLED_FAILURE, UNCERTAIN
failureClassMARKETING_LIMIT, PAIR_RATE_LIMIT, SENDER_QUALITY, MARKETING_SUPPRESSED, WHATSAPP_UNREACHABLE, PERMANENT_BOUNCE, TEMPLATE_PAUSED, SHARED_CONFIGURATION, TEMPORARY_UNDELIVERED, EXPIRED_UNDELIVERED
retargetEligibletrue or false

Pages show current state, not a fixed export. Concurrent status changes can move rows in or out of a filter. Reconcile current campaign and recipient results when completeness matters. All requests repeat workspace and exact number checks; a foreign, sibling-number or absent campaign returns 404 campaign_not_found.

Interpret recipient results

Each recipient exposes id, nullable authorized contactId, status, settlementState, nullable failureClass and normalizedFailureCode, retargetEligible, nextEligibleAt, queuedAt, providerAcceptedAt, sentAt, deliveredAt, readAt, repliedAt, failedAt and settledAt. All timestamps except queuedAt may be null. No phone number, provider message ID, raw provider failure, rendered variables or stored recipient snapshot is returned.

SEND_UNCERTAIN and UNCERTAIN require reconciliation; they do not authorize another send. Eligibility fields report the existing broadcast decision, not permission to launch a retarget campaign. Contact IDs remain workspace identities; preserve the number and campaign identity when storing recipient results across several keys.

Endpoint details

Every request uses Authorization: Bearer $WAARU_API_KEY and a connected number in Developer API mode. The scope below is required. IDs and payloads are illustrative; replace them with your own permitted resources. See authentication and common errors, size limits and retries.

GET /v1/developer/campaigns

Required scope: campaigns:read

Requires campaigns:read. Read this number's campaign summaries. Requires an explicitly granted campaigns:read scope; existing keys gain none. Keep filters unchanged across opaque cursor pages. No schedule, send or revenue-attribution authority.

Request parameters
ParameterLocationRequiredConstraints
limitqueryNo{"type":"integer","minimum":1,"maximum":100,"default":50}
afterqueryNo{"type":"string","maxLength":512} Use nextCursor from the previous response; keep filters unchanged.
statusqueryNo{"type":"string","enum":["DRAFT","SCHEDULED","QUEUED","SENDING","PAUSED","COMPLETED","FAILED","CANCELLED"]}

Request

curl --fail-with-body --request GET 'https://api.waaru.app/v1/developer/campaigns' \
  --header "Authorization: Bearer $WAARU_API_KEY"

Send no request body.

Response: HTTP 200

{
  "items": [
    {
      "id": "campaign_example",
      "name": "Workshop reminder",
      "status": "COMPLETED",
      "templateId": "template_example",
      "pauseCode": null,
      "totalRecipients": 1,
      "admittedCount": 1,
      "quotaWaitingCount": 0,
      "providerAcceptedCount": 1,
      "sentCount": 1,
      "deliveredCount": 1,
      "readCount": 0,
      "repliedCount": 0,
      "failedCount": 0,
      "quickReplyCount": 0,
      "quickReplyUniqueCount": 0,
      "scheduledAt": null,
      "startedAt": null,
      "completedAt": null,
      "sendCompletedAt": null,
      "outcomeSettledAt": null,
      "metricsReconciledAt": null,
      "createdAt": "2026-10-18T09:00:00.000Z",
      "updatedAt": "2026-10-18T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
Response body fields
FieldRequiredType and constraints
itemsYes{"maxItems":100,"type":"array"} Nested fields are in the complete schema below.
nextCursorYes{"anyOf":[{"type":"string","minLength":1,"maxLength":512},{"type":"null"}]}
Response body: complete JSON schema

Required fields appear in required. Omit optional fields unless needed. Null is accepted only where the schema includes it. Arrays describe their elements in items; variants appear in anyOf or oneOf.

{
  "type": "object",
  "properties": {
    "items": {
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 191
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "DRAFT",
              "SCHEDULED",
              "QUEUED",
              "SENDING",
              "PAUSED",
              "COMPLETED",
              "FAILED",
              "CANCELLED"
            ]
          },
          "templateId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 191
          },
          "pauseCode": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "MANUAL",
                  "QUOTA",
                  "MISSED_SCHEDULE",
                  "QUALITY",
                  "TEMPLATE",
                  "SHARED_CONFIGURATION",
                  "PROVIDER_LIMIT",
                  "QUEUE_BACKPRESSURE",
                  "WORKSPACE_BLOCKED"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "totalRecipients": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "admittedCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "quotaWaitingCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "providerAcceptedCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "sentCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "deliveredCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "readCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "repliedCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "failedCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "quickReplyCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "quickReplyUniqueCount": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          },
          "scheduledAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "startedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "completedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "sendCompletedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "outcomeSettledAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "metricsReconciledAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "templateId",
          "pauseCode",
          "totalRecipients",
          "admittedCount",
          "quotaWaitingCount",
          "providerAcceptedCount",
          "sentCount",
          "deliveredCount",
          "readCount",
          "repliedCount",
          "failedCount",
          "quickReplyCount",
          "quickReplyUniqueCount",
          "scheduledAt",
          "startedAt",
          "completedAt",
          "sendCompletedAt",
          "outcomeSettledAt",
          "metricsReconciledAt",
          "createdAt",
          "updatedAt"
        ],
        "additionalProperties": false
      }
    },
    "nextCursor": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 512
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "items",
    "nextCursor"
  ],
  "additionalProperties": false
}

Failures use a stable code and safe message. They never expose internal exception messages, traces or provider diagnostics. Check the error reference before retrying. An inaccessible resource can return 404 even if its identifier exists elsewhere.

GET /v1/developer/campaigns/{id}

Required scope: campaigns:read

Requires campaigns:read. Read one authorized campaign and retained settlement/failure cohort counts. Counters describe operational observations, not orders, conversions or revenue.

Request parameters
ParameterLocationRequiredConstraints
idpathYes{"type":"string","minLength":1,"maxLength":191}

Request

curl --fail-with-body --request GET 'https://api.waaru.app/v1/developer/campaigns/id_example' \
  --header "Authorization: Bearer $WAARU_API_KEY"

Send no request body.

Response: HTTP 200

{
  "campaign": {
    "id": "campaign_example",
    "name": "Workshop reminder",
    "status": "COMPLETED",
    "templateId": "template_example",
    "pauseCode": null,
    "totalRecipients": 1,
    "admittedCount": 1,
    "quotaWaitingCount": 0,
    "providerAcceptedCount": 1,
    "sentCount": 1,
    "deliveredCount": 1,
    "readCount": 0,
    "repliedCount": 0,
    "failedCount": 0,
    "quickReplyCount": 0,
    "quickReplyUniqueCount": 0,
    "scheduledAt": null,
    "startedAt": null,
    "completedAt": null,
    "sendCompletedAt": null,
    "outcomeSettledAt": null,
    "metricsReconciledAt": null,
    "createdAt": "2026-10-18T09:00:00.000Z",
    "updatedAt": "2026-10-18T09:00:00.000Z",
    "failureCohorts": {},
    "settlementCohorts": {
      "SETTLED_SUCCESS": 1
    }
  }
}
Response body fields
FieldRequiredType and constraints
campaignYes{"type":"object","required":["id","name","status","templateId","pauseCode","totalRecipients","admittedCount","quotaWaitingCount","providerAcceptedCount","sentCount","deliveredCount","readCount","repliedCount","failedCount","quickReplyCount","quickReplyUniqueCount","scheduledAt","startedAt","completedAt","sendCompletedAt","outcomeSettledAt","metricsReconciledAt","createdAt","updatedAt","failureCohorts","settlementCohorts"],"additionalProperties":false} Nested fields are in the complete schema below.
Response body: complete JSON schema

Required fields appear in required. Omit optional fields unless needed. Null is accepted only where the schema includes it. Arrays describe their elements in items; variants appear in anyOf or oneOf.

{
  "type": "object",
  "properties": {
    "campaign": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "minLength": 1,
          "maxLength": 191
        },
        "name": {
          "type": "string"
        },
        "status": {
          "type": "string",
          "enum": [
            "DRAFT",
            "SCHEDULED",
            "QUEUED",
            "SENDING",
            "PAUSED",
            "COMPLETED",
            "FAILED",
            "CANCELLED"
          ]
        },
        "templateId": {
          "type": "string",
          "minLength": 1,
          "maxLength": 191
        },
        "pauseCode": {
          "anyOf": [
            {
              "type": "string",
              "enum": [
                "MANUAL",
                "QUOTA",
                "MISSED_SCHEDULE",
                "QUALITY",
                "TEMPLATE",
                "SHARED_CONFIGURATION",
                "PROVIDER_LIMIT",
                "QUEUE_BACKPRESSURE",
                "WORKSPACE_BLOCKED"
              ]
            },
            {
              "type": "null"
            }
          ]
        },
        "totalRecipients": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "admittedCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "quotaWaitingCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "providerAcceptedCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "sentCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "deliveredCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "readCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "repliedCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "failedCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "quickReplyCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "quickReplyUniqueCount": {
          "type": "integer",
          "minimum": 0,
          "maximum": 9007199254740991
        },
        "scheduledAt": {
          "anyOf": [
            {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            {
              "type": "null"
            }
          ]
        },
        "startedAt": {
          "anyOf": [
            {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            {
              "type": "null"
            }
          ]
        },
        "completedAt": {
          "anyOf": [
            {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            {
              "type": "null"
            }
          ]
        },
        "sendCompletedAt": {
          "anyOf": [
            {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            {
              "type": "null"
            }
          ]
        },
        "outcomeSettledAt": {
          "anyOf": [
            {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            {
              "type": "null"
            }
          ]
        },
        "metricsReconciledAt": {
          "anyOf": [
            {
              "type": "string",
              "format": "date-time",
              "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
            },
            {
              "type": "null"
            }
          ]
        },
        "createdAt": {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
        },
        "updatedAt": {
          "type": "string",
          "format": "date-time",
          "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
        },
        "failureCohorts": {
          "type": "object",
          "propertyNames": {
            "type": "string",
            "enum": [
              "MARKETING_LIMIT",
              "PAIR_RATE_LIMIT",
              "SENDER_QUALITY",
              "MARKETING_SUPPRESSED",
              "WHATSAPP_UNREACHABLE",
              "PERMANENT_BOUNCE",
              "TEMPLATE_PAUSED",
              "SHARED_CONFIGURATION",
              "TEMPORARY_UNDELIVERED",
              "EXPIRED_UNDELIVERED"
            ]
          },
          "additionalProperties": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          }
        },
        "settlementCohorts": {
          "type": "object",
          "propertyNames": {
            "type": "string",
            "enum": [
              "PENDING",
              "SETTLED_SUCCESS",
              "SETTLED_FAILURE",
              "UNCERTAIN"
            ]
          },
          "additionalProperties": {
            "type": "integer",
            "minimum": 0,
            "maximum": 9007199254740991
          }
        }
      },
      "required": [
        "id",
        "name",
        "status",
        "templateId",
        "pauseCode",
        "totalRecipients",
        "admittedCount",
        "quotaWaitingCount",
        "providerAcceptedCount",
        "sentCount",
        "deliveredCount",
        "readCount",
        "repliedCount",
        "failedCount",
        "quickReplyCount",
        "quickReplyUniqueCount",
        "scheduledAt",
        "startedAt",
        "completedAt",
        "sendCompletedAt",
        "outcomeSettledAt",
        "metricsReconciledAt",
        "createdAt",
        "updatedAt",
        "failureCohorts",
        "settlementCohorts"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "campaign"
  ],
  "additionalProperties": false
}

Failures use a stable code and safe message. They never expose internal exception messages, traces or provider diagnostics. Check the error reference before retrying. An inaccessible resource can return 404 even if its identifier exists elsewhere.

GET /v1/developer/campaigns/{id}/recipients

Required scope: campaigns:read

Requires campaigns:read. Read paginated retained recipient outcomes for this number's campaign without phone numbers or provider identifiers. Keep filters unchanged; later status changes can change a filtered cohort, so reconcile current results. Retarget evidence does not authorize a new send.

Request parameters
ParameterLocationRequiredConstraints
idpathYes{"type":"string","minLength":1,"maxLength":191}
limitqueryNo{"type":"integer","minimum":1,"maximum":100,"default":50}
afterqueryNo{"type":"string","maxLength":512} Use nextCursor from the previous response; keep filters unchanged.
statusqueryNo{"type":"string","enum":["WAITING_QUOTA","ADMITTED","QUEUED","SEND_UNCERTAIN","PROVIDER_ACCEPTED","SENT","DELIVERED","READ","REPLIED","FAILED","SKIPPED","OPTED_OUT"]}
settlementStatequeryNo{"type":"string","enum":["PENDING","SETTLED_SUCCESS","SETTLED_FAILURE","UNCERTAIN"]}
failureClassqueryNo{"type":"string","enum":["MARKETING_LIMIT","PAIR_RATE_LIMIT","SENDER_QUALITY","MARKETING_SUPPRESSED","WHATSAPP_UNREACHABLE","PERMANENT_BOUNCE","TEMPLATE_PAUSED","SHARED_CONFIGURATION","TEMPORARY_UNDELIVERED","EXPIRED_UNDELIVERED"]}
retargetEligiblequeryNo{"type":"boolean"}

Request

curl --fail-with-body --request GET 'https://api.waaru.app/v1/developer/campaigns/id_example/recipients' \
  --header "Authorization: Bearer $WAARU_API_KEY"

Send no request body.

Response: HTTP 200

{
  "items": [
    {
      "id": "recipient_example",
      "contactId": "contact_example",
      "status": "DELIVERED",
      "settlementState": "SETTLED_SUCCESS",
      "failureClass": null,
      "normalizedFailureCode": null,
      "retargetEligible": false,
      "nextEligibleAt": null,
      "queuedAt": "2026-10-18T09:00:00.000Z",
      "providerAcceptedAt": "2026-10-18T09:00:00.000Z",
      "sentAt": "2026-10-18T09:00:00.000Z",
      "deliveredAt": "2026-10-18T09:00:00.000Z",
      "readAt": null,
      "repliedAt": null,
      "failedAt": null,
      "settledAt": "2026-10-18T09:00:00.000Z"
    }
  ],
  "nextCursor": null
}
Response body fields
FieldRequiredType and constraints
itemsYes{"maxItems":100,"type":"array"} Nested fields are in the complete schema below.
nextCursorYes{"anyOf":[{"type":"string","minLength":1,"maxLength":512},{"type":"null"}]}
Response body: complete JSON schema

Required fields appear in required. Omit optional fields unless needed. Null is accepted only where the schema includes it. Arrays describe their elements in items; variants appear in anyOf or oneOf.

{
  "type": "object",
  "properties": {
    "items": {
      "maxItems": 100,
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 191
          },
          "contactId": {
            "anyOf": [
              {
                "type": "string",
                "minLength": 1,
                "maxLength": 191
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "WAITING_QUOTA",
              "ADMITTED",
              "QUEUED",
              "SEND_UNCERTAIN",
              "PROVIDER_ACCEPTED",
              "SENT",
              "DELIVERED",
              "READ",
              "REPLIED",
              "FAILED",
              "SKIPPED",
              "OPTED_OUT"
            ]
          },
          "settlementState": {
            "type": "string",
            "enum": [
              "PENDING",
              "SETTLED_SUCCESS",
              "SETTLED_FAILURE",
              "UNCERTAIN"
            ]
          },
          "failureClass": {
            "anyOf": [
              {
                "type": "string",
                "enum": [
                  "MARKETING_LIMIT",
                  "PAIR_RATE_LIMIT",
                  "SENDER_QUALITY",
                  "MARKETING_SUPPRESSED",
                  "WHATSAPP_UNREACHABLE",
                  "PERMANENT_BOUNCE",
                  "TEMPLATE_PAUSED",
                  "SHARED_CONFIGURATION",
                  "TEMPORARY_UNDELIVERED",
                  "EXPIRED_UNDELIVERED"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "normalizedFailureCode": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 64
              },
              {
                "type": "null"
              }
            ]
          },
          "retargetEligible": {
            "type": "boolean"
          },
          "nextEligibleAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "queuedAt": {
            "type": "string",
            "format": "date-time",
            "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
          },
          "providerAcceptedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "sentAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "deliveredAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "readAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "repliedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "failedAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          },
          "settledAt": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time",
                "pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "id",
          "contactId",
          "status",
          "settlementState",
          "failureClass",
          "normalizedFailureCode",
          "retargetEligible",
          "nextEligibleAt",
          "queuedAt",
          "providerAcceptedAt",
          "sentAt",
          "deliveredAt",
          "readAt",
          "repliedAt",
          "failedAt",
          "settledAt"
        ],
        "additionalProperties": false
      }
    },
    "nextCursor": {
      "anyOf": [
        {
          "type": "string",
          "minLength": 1,
          "maxLength": 512
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "items",
    "nextCursor"
  ],
  "additionalProperties": false
}

Failures use a stable code and safe message. They never expose internal exception messages, traces or provider diagnostics. Check the error reference before retrying. An inaccessible resource can return 404 even if its identifier exists elsewhere.

Next: Message changes and current-state reconciliation or authentication and scopes.

On this page