{
  "openapi": "3.1.0",
  "info": {
    "title": "SanadWA Public API",
    "version": "1.0.0",
    "summary": "Send WhatsApp messages and read your SanadWA account data from your own systems.",
    "description": "The SanadWA Public API lets your own software send WhatsApp messages through the WhatsApp Business numbers connected to your SanadWA account, look up those messages, list your approved message templates and sending numbers, manage your contacts and tags, run WhatsApp campaigns, and read your credit balance.\n\n## Authentication\nEvery request except `GET /openapi.json` needs an API key, sent as a bearer token: `Authorization: Bearer pk_live_...`. Create keys in the SanadWA console. A key belongs to one SanadWA account, and every request made with it reads and changes only that account's data. Keep keys secret: anyone holding a key can act for your account within its scope.\n\nEach key has one scope. Scopes are ordered, and a key may call every endpoint that requires its scope or a lower one:\n- `read`: read-only endpoints (`GET`).\n- `send`: everything `read` allows, plus endpoints that send messages or spend credits.\n- `full`: everything `send` allows, plus endpoints that create, change or delete account data.\n\nEach operation states the scope it needs in `x-required-scope`. A key can also be limited to a list of IP addresses.\n\n## Responses and errors\nEvery response body is JSON with a boolean `success`. Errors look like `{ \"success\": false, \"code\": \"phone_not_found\", \"message\": \"...\" }`. `code` is stable and meant for your code to branch on; `message` is for people and may change. Some errors add `details` (machine-readable context) and `message_ar` (an Arabic message). Each operation lists every `code` it can return, per HTTP status. A request whose method and path match no endpoint (a misspelt path, or `PUT` on a path that only has `GET`) answers `404` with code `not_found`, before the API key is checked. Because it belongs to no operation, it is described once, as the `NotFound` response and the `NotFoundError` schema.\n\nEvery response carries an `X-Request-Id` header. Quote it when you contact support about a request.\n\n## Rate limits\nRequests are limited per API key, by default 60 per minute; your key may have a different limit. A minute starts with the first request counted in it and lasts 60 seconds. Within it you may use the allowance at any pace, all at once if you like, but it does not refill gradually: once it is used up, further requests are refused until that minute ends. Responses to authenticated requests carry `X-RateLimit-Limit` (requests per minute for this key) and `X-RateLimit-Remaining` (requests left in the current minute). When the limit is reached the API answers `429` with code `rate_limited` and a `Retry-After` header: the number of seconds until the current minute ends.\n\n## Idempotent retries\n`POST` and `DELETE` endpoints accept an optional `Idempotency-Key` header (up to 120 characters; a UUID works well). If you repeat a request with the same key and the same body within 24 hours, and the first request succeeded, the API does not perform it again: it returns the first response, with the header `X-Idempotent-Replay: true`. Use this to retry safely after a timeout without sending a message twice. Keys are per API key, with one exception: on `POST /campaigns` a key is remembered for your whole account, whichever API key sent it. A failed request (any non-2xx answer) is not remembered, so a retry with the same key runs again. Reusing a key with a different body answers `409 idempotency_conflict`; repeating it while the first request is still running answers `409 idempotency_in_progress`. Without the header, every request is performed. A key identifies one request: use it again only to retry that same request, and use a new key for every new request. On the contacts, tags and campaigns endpoints a key that was used for a different method, path or body answers `409 idempotency_conflict`; on the two message endpoints the comparison is made on the body. Exception: `POST /campaigns` requires the header, and a key that created a campaign keeps returning that campaign for as long as it exists.\n\n## Pagination\nList endpoints take `page` (starting at 1, default 1) and `limit` (default 50, at most 200) and return the values they used next to `data`. A page shorter than `limit` is the last page; a page past the end has an empty `data`.\n\nSend whole numbers in range. Other values are not rejected, but they are not always replaced by the nearest allowed value:\n- `GET /tags`, `GET /contacts`, `GET /campaigns`: a fractional value is first cut to a whole number (`2.7` becomes `2`, `0.5` becomes `0`). Then a `page` below 1, missing or not a number becomes 1. A `limit` of 0, missing or not a number becomes 50 (the default, not 1); a negative `limit` becomes 1; a `limit` above 200 becomes 200.\n- `GET /templates`: a `page` below 1, missing or not a number becomes 1. A `limit` of 0, missing or not a number becomes 50 (the default, not 1); a negative `limit`, or one between 0 and 1, becomes 1; a `limit` above 200 becomes 200. Fractional values are not cut: a fractional `limit` above 1 fails with `500 templates_lookup_failed`, and a fractional `page` above 1 is returned as sent and either starts the page part-way through the list or fails with `500 templates_lookup_failed`."
  },
  "servers": [
    {
      "url": "https://api.sanadwa.com/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Messages",
      "description": "Send WhatsApp messages and look them up."
    },
    {
      "name": "Templates",
      "description": "Your approved WhatsApp message templates."
    },
    {
      "name": "Phone numbers",
      "description": "The WhatsApp numbers you can send from."
    },
    {
      "name": "Contacts",
      "description": "The people you message: create, update, import, list and delete contacts."
    },
    {
      "name": "Tags",
      "description": "Your account's tag list, used to label contacts."
    },
    {
      "name": "Campaigns",
      "description": "Send one approved template to many contacts, chosen by tag, now or at a set time."
    },
    {
      "name": "Account",
      "description": "Your API key and account balance."
    },
    {
      "name": "Reference",
      "description": "This API description."
    }
  ],
  "paths": {
    "/messages/template": {
      "post": {
        "operationId": "sendTemplateMessage",
        "tags": [
          "Messages"
        ],
        "summary": "Send a template message",
        "description": "Sends an approved WhatsApp message template to one recipient. Templates are the only way to start a conversation, or to message someone who has not written to you in the last 24 hours. The template must be approved for the WhatsApp Business account that owns `phone_number_id`.\n\nFill the template's body variables with `template_params`, in order. For authentication (one-time password) templates the first parameter is also copied into the copy-code button, as WhatsApp requires. For headers, buttons or any other component, pass `template_components` instead: it is sent to WhatsApp unchanged and replaces `template_params`.\n\nMarketing templates are not sent to contacts who opted out of marketing messages (`409 MARKETING_SUPPRESSED`). Templates whose buttons use SanadWA tracked links can only be sent in campaigns. The send spends credits according to the template's category.\n\nA `200` means WhatsApp accepted the message for delivery, not that it was delivered. Pass the returned `message_id` to `GET /messages/{id}` to follow its status: it can normally be looked up as soon as this call returns, starts as `sent`, and moves to `delivered`, `read` or `failed` as WhatsApp reports them.",
        "x-required-scope": "send",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendTemplateRequest"
              },
              "examples": {
                "body_variables": {
                  "summary": "Fill the body variables",
                  "value": {
                    "phone_number_id": "106540352242922",
                    "to_phone": "+12025550123",
                    "template_name": "order_update",
                    "template_language": "en_US",
                    "template_params": [
                      "Sara",
                      "A-1042"
                    ]
                  }
                },
                "components": {
                  "summary": "Header, body and URL button with template_components",
                  "value": {
                    "phone_number_id": "106540352242922",
                    "to_phone": "+12025550123",
                    "template_name": "order_shipped",
                    "template_language": "en_US",
                    "template_components": [
                      {
                        "type": "header",
                        "parameters": [
                          {
                            "type": "text",
                            "text": "A-1042"
                          }
                        ]
                      },
                      {
                        "type": "body",
                        "parameters": [
                          {
                            "type": "text",
                            "text": "Sara"
                          },
                          {
                            "type": "text",
                            "text": "A-1042"
                          }
                        ]
                      },
                      {
                        "type": "button",
                        "sub_type": "url",
                        "index": "0",
                        "parameters": [
                          {
                            "type": "text",
                            "text": "A-1042"
                          }
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WhatsApp accepted the message.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Idempotent-Replay": {
                "$ref": "#/components/headers/XIdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendTemplateResponse"
                }
              }
            }
          },
          "400": {
            "description": "`validation_error`: the request body does not match the schema; `message` names the first problem. `idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters. `INVALID_JSON`: the body is not valid JSON. `TRACKED_TEMPLATE_CAMPAIGN_ONLY`: the template has SanadWA tracked-link buttons and can only be sent in a campaign.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "validation_error",
                            "idempotency_key_too_long",
                            "INVALID_JSON",
                            "TRACKED_TEMPLATE_CAMPAIGN_ONLY"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`phone_number_id` is not one of your sending numbers (`phone_not_found`), or its WhatsApp Business account is no longer linked to SanadWA (`account_not_found`).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "phone_not_found",
                            "account_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`MARKETING_SUPPRESSED`: the recipient opted out of marketing messages, so this marketing template was not sent; `details` says why. `idempotency_conflict`: this `Idempotency-Key` was already used with a different request body; use a new key for a new request. `idempotency_in_progress`: a request with this `Idempotency-Key` is still being processed; retry after the `Retry-After` delay (1 second).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfterIdempotency"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "MARKETING_SUPPRESSED",
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "A check that must pass before sending could not be completed, so nothing was sent; retry shortly. `TEMPLATE_CATEGORY_UNAVAILABLE`: the template's category could not be read. `TRACKED_TEMPLATE_GUARD_UNAVAILABLE`: the template's buttons could not be checked. `SUPPRESSION_CHECK_FAILED`: the recipient's marketing preference could not be checked.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "TEMPLATE_CATEGORY_UNAVAILABLE",
                            "TRACKED_TEMPLATE_GUARD_UNAVAILABLE",
                            "SUPPRESSION_CHECK_FAILED"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/SendFailed"
          }
        }
      }
    },
    "/messages/text": {
      "post": {
        "operationId": "sendTextMessage",
        "tags": [
          "Messages"
        ],
        "summary": "Reply with a free-form text message",
        "description": "Sends a free-form text reply. WhatsApp only allows free-form messages inside a customer-service window: the recipient must have written to this same `phone_number_id` in the last 24 hours. Outside that window the API answers `409 customer_service_window_closed` and nothing is sent; send an approved template with `POST /messages/template` instead.\n\nA `200` means WhatsApp accepted the message for delivery, not that it was delivered. Pass the returned `message_id` to `GET /messages/{id}` to follow its status: it can normally be looked up as soon as this call returns, starts as `sent`, and moves to `delivered`, `read` or `failed` as WhatsApp reports them.",
        "x-required-scope": "send",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendTextRequest"
              },
              "example": {
                "phone_number_id": "106540352242922",
                "to_phone": "+12025550123",
                "text": "Your order A-1042 has shipped and arrives tomorrow.",
                "preview_url": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "WhatsApp accepted the message.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Idempotent-Replay": {
                "$ref": "#/components/headers/XIdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendTextResponse"
                }
              }
            }
          },
          "400": {
            "description": "`validation_error`: the request body does not match the schema; `message` names the first problem. `idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters. `INVALID_JSON`: the body is not valid JSON. `invalid_to_phone`: `to_phone` is not a valid phone number.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "validation_error",
                            "idempotency_key_too_long",
                            "INVALID_JSON",
                            "invalid_to_phone"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/InsufficientCredits"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`phone_number_id` is not one of your sending numbers (`phone_not_found`), or its WhatsApp Business account is no longer linked to SanadWA (`account_not_found`).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "phone_not_found",
                            "account_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`customer_service_window_closed`: the recipient has not written to this number in the last 24 hours; `details` gives the time of their last message and when the window closed. `idempotency_conflict`: this `Idempotency-Key` was already used with a different request body; use a new key for a new request. `idempotency_in_progress`: a request with this `Idempotency-Key` is still being processed; retry after the `Retry-After` delay (1 second).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfterIdempotency"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "customer_service_window_closed",
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        },
                        "details": {
                          "$ref": "#/components/schemas/ReplyWindowDetails"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/SendFailed"
          }
        }
      }
    },
    "/messages/{id}": {
      "get": {
        "operationId": "getMessage",
        "tags": [
          "Messages"
        ],
        "summary": "Get a message",
        "description": "Returns one message sent from your account and its current delivery status. `id` is the WhatsApp message id (it starts with `wamid.`): the `message_id` returned by `POST /messages/template` or `POST /messages/text`, or the `provider_msg_id` of a message webhook event. Messages sent from the SanadWA console inbox and from campaigns can be looked up the same way. Messages your contacts sent to you are not returned. Any other value answers `404 message_not_found`, exactly like an id that does not belong to your account.\n\n`status` of a message sent with this API:\n- `sent`: WhatsApp accepted the message. This is the status as soon as the send call returns.\n- `delivered`: the message reached the recipient's phone; `delivered_at` is set.\n- `read`: the recipient opened it; `read_at` is set. WhatsApp does not report reads for recipients who turned read receipts off, so such a message stays `delivered`.\n- `failed`: WhatsApp could not deliver it; `error` holds WhatsApp's reason, for example `[131026] Message undeliverable.`\n\nThe status only moves forward (`sent`, then `delivered`, then `read`): a late or repeated report never moves it back, and a message already delivered never becomes `failed`. It changes once WhatsApp has reported the event to SanadWA and SanadWA has processed that report. There is no guaranteed delay between the event and the change: WhatsApp can report late, and a recipient whose phone is off or offline is reported `delivered` only when the phone reconnects, which can take hours or days. Polling often does not bring a change sooner and counts against your rate limit; to be told instead of polling, set up a webhook in the SanadWA console for the `message.delivered`, `message.read` and `message.failed` events.\n\nFor messages sent with this API, from the console inbox or from campaigns, `last_status` equals `status` and `failed_at` is `null` (a failure shows as `status` `failed` with `error`). For messages recorded through older SanadWA integrations, `status` is the status recorded when the message was created, and `last_status`, `delivered_at`, `read_at` and `failed_at` carry WhatsApp's later reports.",
        "x-required-scope": "read",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The WhatsApp message id, starting with `wamid.`.",
            "schema": {
              "type": "string",
              "pattern": "^wamid\\.[A-Za-z0-9_+/=-]+$"
            },
            "example": "wamid.HBgExample1"
          }
        ],
        "responses": {
          "200": {
            "description": "The message.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetMessageResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`message_not_found`: no message with this id belongs to your account, or the id is not a WhatsApp message id.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "message_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `message_lookup_failed`: the message could not be read; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "message_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/templates": {
      "get": {
        "operationId": "listTemplates",
        "tags": [
          "Templates"
        ],
        "summary": "List approved templates",
        "description": "Lists the WhatsApp message templates approved for your account, most recently updated first. Pass `phone_number_id` to see only the templates you can send from that number (they belong to the number's WhatsApp Business account).",
        "x-required-scope": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Only templates whose name or language code contains this text (case-insensitive).",
            "schema": {
              "type": "string"
            },
            "example": "order"
          },
          {
            "name": "phone_number_id",
            "in": "query",
            "required": false,
            "description": "Only templates of the WhatsApp Business account that owns this sending number. Takes precedence over `waba_id`.",
            "schema": {
              "type": "string"
            },
            "example": "106540352242922"
          },
          {
            "name": "waba_id",
            "in": "query",
            "required": false,
            "description": "Only templates of this WhatsApp Business account.",
            "schema": {
              "type": "string"
            },
            "example": "102290129340398"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of templates.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListTemplatesResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`phone_not_found`: `phone_number_id` is not one of your sending numbers.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "phone_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `templates_lookup_failed`: the templates could not be read; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "templates_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/phone-numbers": {
      "get": {
        "operationId": "listPhoneNumbers",
        "tags": [
          "Phone numbers"
        ],
        "summary": "List sending numbers",
        "description": "Lists the active WhatsApp numbers connected to your account. Use a number's `phone_number_id` as the sender when you send messages.",
        "x-required-scope": "read",
        "responses": {
          "200": {
            "description": "Your active sending numbers.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListPhoneNumbersResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `phone_numbers_lookup_failed`: the numbers could not be read; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "phone_numbers_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "checkApiKey",
        "tags": [
          "Account"
        ],
        "summary": "Check an API key",
        "description": "Confirms that the API key works and shows its account and scope. It is cheap, so use it to test a new key or a deployment.",
        "x-required-scope": "read",
        "responses": {
          "200": {
            "description": "The key works.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/balance": {
      "get": {
        "operationId": "getBalance",
        "tags": [
          "Account"
        ],
        "summary": "Get the credit balance",
        "description": "Returns your account's current credit balance and this month's credit usage. Sending messages spends credits according to the message category.\n\nOn a prepaid account, `available_credits` is what you can spend now: your remaining paid credits plus this month's remaining free credits, minus credits held for messages that are being sent. On a postpaid account usage is invoiced, so there is no prepaid balance and `available_credits` and `paid_credits_remaining` are `null`. While a subscription covers your sending, `unlimited` is `true` and messages do not use credits until `unlimited_until`.",
        "x-required-scope": "read",
        "responses": {
          "200": {
            "description": "The balance.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BalanceResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `balance_lookup_failed`: the balance could not be read, so none is reported; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "balance_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/tags": {
      "get": {
        "operationId": "listTags",
        "tags": [
          "Tags"
        ],
        "summary": "List tags",
        "description": "Returns your account's tag list, oldest first. These are the tags you can apply to contacts with `POST /contacts` and `POST /contacts/import`.",
        "x-required-scope": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of tags.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListTagsResponse"
                },
                "example": {
                  "success": true,
                  "page": 1,
                  "limit": 50,
                  "data": [
                    {
                      "id": "42",
                      "name": "VIP",
                      "description": null,
                      "created_at": "2026-11-11T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `tags_lookup_failed`: the tags could not be read; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "tags_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createTag",
        "tags": [
          "Tags"
        ],
        "summary": "Create a tag",
        "description": "Adds a tag to your account's tag list. Tag names are unique in your account without regard to case.",
        "x-required-scope": "full",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTagRequest"
              },
              "example": {
                "name": "VIP",
                "description": "Donors who gave more than 1,000 SAR this year"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The tag was created.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TagResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "42",
                    "name": "VIP",
                    "description": "Donors who gave more than 1,000 SAR this year",
                    "created_at": "2026-11-11T10:00:00.000Z"
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_error`: the request body does not match the schema; `message` names the first problem. `idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters. `INVALID_JSON`: the body is not valid JSON.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "validation_error",
                            "idempotency_key_too_long",
                            "INVALID_JSON"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`tag_already_exists`: your account already has a tag with this name (compared without regard to case). `idempotency_conflict`: this `Idempotency-Key` was already used with a different request; use a new key for a new request. `idempotency_in_progress`: a request with this `Idempotency-Key` is still being processed; retry after the `Retry-After` delay (1 second).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfterIdempotency"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "tag_already_exists",
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `tag_create_failed`: the tag could not be created; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "tag_create_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/tags/{id}": {
      "delete": {
        "operationId": "deleteTag",
        "tags": [
          "Tags"
        ],
        "summary": "Delete a tag",
        "description": "Removes a tag from your account's tag list. Contacts that carry the tag keep its name in their `tags`, and `GET /contacts?tag=` still finds them; the name can no longer be applied with `POST /contacts` or `POST /contacts/import` until a tag with that name is created again.",
        "x-required-scope": "full",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The tag's `id`, as returned when it was created or listed.",
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]{0,17}$"
            },
            "example": "42"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The tag was deleted.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "42",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "idempotency_key_too_long"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`tag_not_found`: your account has no tag with this id.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "tag_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_conflict`: this `Idempotency-Key` was already used with a different request; use a new key for a new request. `idempotency_in_progress`: a request with this `Idempotency-Key` is still being processed; retry after the `Retry-After` delay (1 second).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfterIdempotency"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `tag_delete_failed`: the tag could not be deleted; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "tag_delete_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "operationId": "listContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "List contacts",
        "description": "Returns your account's contacts, ordered by phone number. There is no phone-number filter, because URLs are logged and phone numbers are personal data. To get the contact for a phone number, send only `phone` to `POST /contacts`: the result is the contact with that number (only its `updated_at` changes), or a new contact if your account does not have the number yet.",
        "x-required-scope": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Only contacts that carry this tag name, compared without regard to case. A name no contact carries returns an empty page.",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "example": "VIP"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListContactsResponse"
                },
                "example": {
                  "success": true,
                  "page": 1,
                  "limit": 50,
                  "data": [
                    {
                      "id": "1001",
                      "phone": "+12025550123",
                      "first_name": "Sara",
                      "last_name": "Ahmed",
                      "email": "sara@example.com",
                      "tags": [
                        "VIP"
                      ],
                      "created_at": "2026-11-11T10:00:00.000Z",
                      "updated_at": "2026-11-11T10:00:00.000Z"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "`validation_error`: a query parameter is invalid (for example `tag` is longer than 100 characters or given twice).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "validation_error"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `contacts_lookup_failed`: the contacts could not be read; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "contacts_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "Create or update contacts",
        "description": "Creates or updates 1 to 100 contacts. The phone number identifies a contact: a number your account does not have yet creates a contact, and a number it already has updates that contact.\n\nAn update follows the same rules as the SanadWA console: a new `first_name` replaces the stored one; `last_name` and `email` are filled in only when the contact has none, so a stored value is kept; `tags` are added to the ones the contact already carries. Nothing is removed, and a field you leave out or send empty is left as it is. A contact's opt-out from marketing messages is never changed by this endpoint.\n\nEach contact is handled on its own: the answer is `200` with one result per contact, in request order, saying whether it was `created`, `updated`, `rejected` (with the reason) or `failed`. Check `summary` and each result's `status`. Retrying the whole request with the same `Idempotency-Key` returns the first answer; sending the rejected or failed contacts again in a new request is also safe.\n\nThe request body may be up to 1 MB.",
        "x-required-scope": "full",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContactsRequest"
              },
              "example": {
                "contacts": [
                  {
                    "phone": "+12025550123",
                    "first_name": "Sara",
                    "last_name": "Ahmed",
                    "email": "sara@example.com",
                    "tags": [
                      "VIP"
                    ]
                  },
                  {
                    "phone": "+12025550124",
                    "first_name": "Omar"
                  },
                  {
                    "phone": "not a phone"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The contacts were processed. See each result's `status`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateContactsResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "summary": {
                      "created": 1,
                      "updated": 1,
                      "rejected": 1,
                      "failed": 0
                    },
                    "results": [
                      {
                        "index": 0,
                        "status": "created",
                        "contact": {
                          "id": "1001",
                          "phone": "+12025550123",
                          "first_name": "Sara",
                          "last_name": "Ahmed",
                          "email": "sara@example.com",
                          "tags": [
                            "VIP"
                          ],
                          "created_at": "2026-11-11T10:00:00.000Z",
                          "updated_at": "2026-11-11T10:00:00.000Z"
                        },
                        "error": null
                      },
                      {
                        "index": 1,
                        "status": "updated",
                        "contact": {
                          "id": "1002",
                          "phone": "+12025550124",
                          "first_name": "Omar",
                          "last_name": null,
                          "email": null,
                          "tags": [],
                          "created_at": "2026-11-11T10:00:00.000Z",
                          "updated_at": "2026-11-11T10:00:00.000Z"
                        },
                        "error": null
                      },
                      {
                        "index": 2,
                        "status": "rejected",
                        "contact": null,
                        "error": {
                          "code": "validation_error",
                          "message": "\"phone\" must be a phone number in international format, for example +12025550123."
                        }
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`validation_error`: the body is not an object with a `contacts` array of 1 to 100 items. Problems with a single contact do not fail the request; they are reported in its result. `idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters. `INVALID_JSON`: the body is not valid JSON.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "validation_error",
                            "idempotency_key_too_long",
                            "INVALID_JSON"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "description": "`idempotency_conflict`: this `Idempotency-Key` was already used with a different request; use a new key for a new request. `idempotency_in_progress`: a request with this `Idempotency-Key` is still being processed; retry after the `Retry-After` delay (1 second).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfterIdempotency"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "`PAYLOAD_TOO_LARGE`: the request body is larger than 1 MB.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "PAYLOAD_TOO_LARGE"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `contacts_upsert_failed`: the contacts could not be processed; retry later. Contacts already saved before the problem stay saved; retrying is safe.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "contacts_upsert_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/contacts/import": {
      "post": {
        "operationId": "importContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "Import contacts from a CSV file",
        "description": "Creates or updates the contacts in a CSV file, and optionally adds the same tags to every one of them. Each row is handled like one contact of `POST /contacts`: the phone number identifies the contact, an existing contact is updated with the same rules, and opt-outs are never changed.\n\nThe answer is `200` with counts and one entry per row that was not saved. A problem with the file as a whole (not CSV, too large, too many rows, an unknown tag in `tags`) fails the request and saves nothing.\n\nWith an `Idempotency-Key`, a retry with the same file and the same `tags` returns the first answer; a different file or different `tags` with the same key answers `409 idempotency_conflict`.",
        "x-required-scope": "full",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ImportContactsRequest"
              },
              "encoding": {
                "file": {
                  "contentType": "text/csv"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The file was processed. See `summary` and `errors`.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportContactsResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "summary": {
                      "rows": 3,
                      "created": 1,
                      "updated": 1,
                      "rejected": 1,
                      "failed": 0
                    },
                    "errors": [
                      {
                        "row": 4,
                        "status": "rejected",
                        "code": "validation_error",
                        "message": "\"phone\" is required."
                      }
                    ]
                  }
                }
              }
            }
          },
          "400": {
            "description": "`import_file_missing`: there is no file in the `file` field. `import_csv_invalid`: the file is not valid CSV. `import_csv_empty`: the file has no contact rows. `import_too_many_rows`: the file has more than 5,000 contact rows; `details.max_rows` gives the limit. `validation_error`: the body is not a valid multipart form with one file in the `file` field, or `tags` is not a valid list. `idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "import_file_missing",
                            "import_csv_invalid",
                            "import_csv_empty",
                            "import_too_many_rows",
                            "validation_error",
                            "idempotency_key_too_long"
                          ]
                        },
                        "details": {
                          "type": "object",
                          "properties": {
                            "max_rows": {
                              "type": "integer",
                              "example": 5000
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`tag_not_found`: a name in `tags` is not in your account's tag list; `details.tags` lists the unknown names. Nothing was imported.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "tag_not_found"
                          ]
                        },
                        "details": {
                          "type": "object",
                          "properties": {
                            "tags": {
                              "type": "array",
                              "items": {
                                "type": "string"
                              },
                              "example": [
                                "Ramadan 2026"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`idempotency_conflict`: this `Idempotency-Key` was already used with a different request; use a new key for a new request. `idempotency_in_progress`: a request with this `Idempotency-Key` is still being processed; retry after the `Retry-After` delay (1 second).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfterIdempotency"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "413": {
            "description": "`import_file_too_large`: the file is larger than 5 MB.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "import_file_too_large"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "415": {
            "description": "`import_file_not_csv`: the file is an Excel workbook; save it as CSV and upload that.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "import_file_not_csv"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `contacts_import_failed`: the file could not be processed; retry later. Rows already saved before the problem stay saved; retrying is safe.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "contacts_import_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/contacts/{id}": {
      "delete": {
        "operationId": "deleteContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Delete a contact",
        "description": "Permanently deletes a contact. Messages and campaign results already sent to the contact are kept, without the link to the contact.\n\nA contact that opted out of marketing messages cannot be deleted: deleting it would erase the opt-out, and the number could then receive marketing messages again. Such a request answers `409 contact_opted_out`.",
        "x-required-scope": "full",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The contact's `id`, as returned when it was created or listed.",
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]{0,17}$"
            },
            "example": "42"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The contact was deleted.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeletedResponse"
                },
                "example": {
                  "success": true,
                  "data": {
                    "id": "1001",
                    "deleted": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "idempotency_key_too_long"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`contact_not_found`: your account has no contact with this id.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "contact_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`contact_opted_out`: the contact opted out of marketing messages, so it is kept; `details.suppression_state` says how the opt-out is recorded (`no_marketing`, `cool_down` or `opt_out_tag`). `idempotency_conflict`: this `Idempotency-Key` was already used with a different request; use a new key for a new request. `idempotency_in_progress`: a request with this `Idempotency-Key` is still being processed; retry after the `Retry-After` delay (1 second).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfterIdempotency"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "contact_opted_out",
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        },
                        "details": {
                          "type": "object",
                          "properties": {
                            "suppression_state": {
                              "type": "string",
                              "enum": [
                                "no_marketing",
                                "cool_down",
                                "opt_out_tag"
                              ]
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified because of a temporary server problem; retry later. `contact_delete_failed`: the contact could not be deleted; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "contact_delete_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiDescription",
        "tags": [
          "Reference"
        ],
        "summary": "Get this API description",
        "description": "Returns this OpenAPI description of the API. No API key is needed.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1 description.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/campaigns": {
      "post": {
        "operationId": "createCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Create and start (or schedule) a campaign",
        "description": "Creates a WhatsApp campaign and starts it now, or schedules it when `send_at` is given. The answer comes as soon as the campaign is accepted; messages are then sent in the background, at the pace WhatsApp allows for your number. Follow progress with `GET /campaigns/{id}`.\n\nA campaign created here is the same as one created with **Send now** or **Schedule** in the SanadWA console, and it appears there. Before anything is created the API checks that your account is verified, the sending number and the template belong to your account, the template is approved, the template variables and header match the template, the tags exist, at least one contact can receive the campaign, and your credits cover every recipient. If any check fails, nothing is created and nothing is sent.\n\nWho receives it: every contact with at least one of the tags, except contacts that opted out of marketing messages, contacts paused after a WhatsApp delivery problem, and invalid numbers. A scheduled campaign checks its audience and your credits again when it starts.\n\nCredits: each message uses credits by the template's category, reserved when the message is sent and charged when WhatsApp accepts it. If the balance runs out while sending, the campaign pauses with `pause_reason` `insufficient_credits`; buy credits and call `POST /campaigns/{id}/resume`. If the balance cannot be checked while sending, it pauses with `pause_reason` `balance_check_failed`; call `POST /campaigns/{id}/resume` to continue.\n\n**`Idempotency-Key` is required on this endpoint**, unlike other endpoints. Send a new unique key for each new campaign and the same key when you retry: one key never creates or sends a second campaign.\n\nCreating campaigns has its own limit of 5 requests per minute for each API key, in addition to the general rate limit. Every request to this endpoint counts, including a retry. Above the limit the API answers `429 rate_limited` with a `Retry-After` header.",
        "x-required-scope": "send",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKeyRequired"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCampaignRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The campaign was created and is sending (`active`) or `scheduled`. A retry with an `Idempotency-Key` that already created a campaign answers the same campaign with `X-Idempotent-Replay: true`: within 24 hours the first answer exactly as it was sent, afterwards the campaign in its current state.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Idempotent-Replay": {
                "$ref": "#/components/headers/XIdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignResponse"
                }
              }
            }
          },
          "400": {
            "description": "Nothing was created. `idempotency_key_required`: send an `Idempotency-Key` header. `idempotency_key_too_long`: the key is longer than 120 characters. `validation_error`: the body does not match the schema; `message` names the first problem. `INVALID_JSON`: the body is not valid JSON. `template_variables_invalid`: `template_variables` does not match the template's body variables; `message` says how. `header_media_required`: the template has a media header; send `header_media_url`. `header_media_too_large`: the header file is over WhatsApp's size limit. `tracked_destination_required`, `tracked_destination_invalid`, `tracked_destination_not_applicable`: see `tracked_destination_url`. `campaign_invalid`: another campaign setting is not valid; `message` says which.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "idempotency_key_required",
                            "idempotency_key_too_long",
                            "validation_error",
                            "INVALID_JSON",
                            "template_variables_invalid",
                            "header_media_required",
                            "header_media_too_large",
                            "tracked_destination_required",
                            "tracked_destination_invalid",
                            "tracked_destination_not_applicable",
                            "campaign_invalid"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "Nothing was created. `insufficient_credits`: your credits do not cover every recipient; `details` gives the credits needed and available. Buy credits, then retry. `subscription_lapsed`: the subscription that covered your sending ended and your credits do not cover the campaign.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "insufficient_credits",
                            "subscription_lapsed"
                          ]
                        },
                        "details": {
                          "$ref": "#/components/schemas/FundingErrorDetails"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "description": "`api_key_ip_blocked`, `api_scope_insufficient`, `private_auth_restricted`: see the API key errors in the introduction (this endpoint needs scope `send`). `kyc_required`: verify your account in the SanadWA console (Settings, Verification & privacy) before sending campaigns. `tracked_links_disabled`: the template has click-tracked buttons and click tracking is not enabled for your account.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_ip_blocked",
                            "api_scope_insufficient",
                            "private_auth_restricted",
                            "kyc_required",
                            "tracked_links_disabled"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Nothing was created. `phone_not_found`: no active sending number with this id in your account. `template_not_found`: no template with this name and language for that number's WhatsApp account. `tag_not_found`: one or more tags do not exist in your account; `details.tags` lists them. Records of other accounts answer the same way as records that do not exist.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "phone_not_found",
                            "template_not_found",
                            "tag_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`template_not_approved`: the template exists but is not approved by WhatsApp; `details.template_status` gives its status. `idempotency_conflict`: this `Idempotency-Key` was already used with a different body; use a new key for a new campaign. `idempotency_in_progress`: a request with this key is still running; retry with the same key after `Retry-After` seconds.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfterIdempotency"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "template_not_approved",
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "422": {
            "description": "Nothing was created. `audience_empty`: no contact has any of these tags. `audience_all_excluded`: every contact with these tags is excluded (opted out, paused after a delivery problem, or invalid number); `details.contacts_with_tags` gives how many have the tags. `audience_too_large`: the audience is over the per-campaign limit; split it into smaller campaigns.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "audience_empty",
                            "audience_all_excluded",
                            "audience_too_large"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified; retry later. `campaign_create_failed`: the campaign could not be created. Retry with the same `Idempotency-Key`: if the first attempt did create it, the retry returns it instead of creating another.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "campaign_create_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Nothing was created. `balance_check_failed`: your credit balance could not be checked. `campaign_create_timeout`: creating the campaign took too long and was stopped. Retry with the same `Idempotency-Key` after `Retry-After` seconds.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "balance_check_failed",
                            "campaign_create_timeout"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "get": {
        "operationId": "listCampaigns",
        "tags": [
          "Campaigns"
        ],
        "summary": "List campaigns",
        "description": "Returns your account's WhatsApp campaigns, newest first, including campaigns created in the SanadWA console. Each item has the campaign's state and message counts. `clicks` is only returned by `GET /campaigns/{id}`.",
        "x-required-scope": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/Limit"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of campaigns.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListCampaignsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified; retry later. `campaigns_lookup_failed`: the campaigns could not be read; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "campaigns_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/campaigns/{id}": {
      "get": {
        "operationId": "getCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Get a campaign",
        "description": "Returns one campaign's state and message counts. Poll it to follow a campaign while it sends; once a minute is plenty.",
        "x-required-scope": "read",
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`campaign_not_found`: no WhatsApp campaign with this id exists in your account. A campaign of another account answers the same way.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "campaign_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified; retry later. `campaign_lookup_failed`: the campaign could not be read; retry later.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "campaign_lookup_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/campaigns/{id}/pause": {
      "post": {
        "operationId": "pauseCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Pause a campaign",
        "description": "Stops sending, like the **Pause** button in the SanadWA console. Only an `active` campaign can be paused. Messages already handed to WhatsApp are not recalled; the rest stay `queued` until you resume. Credits are only used for messages that are sent.",
        "x-required-scope": "send",
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign after the change.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Idempotent-Replay": {
                "$ref": "#/components/headers/XIdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignResponse"
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters. `INVALID_JSON`: the body is not valid JSON (send no body, or `{}`).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "idempotency_key_too_long",
                            "INVALID_JSON"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`campaign_not_found`: no WhatsApp campaign with this id exists in your account. A campaign of another account answers the same way.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "campaign_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`campaign_not_active`: only an `active` campaign can be paused; `details.status` gives its status. `campaign_busy`: another change to this campaign is in progress; retry in a few seconds. `idempotency_conflict`: this `Idempotency-Key` was already used with a different request. `idempotency_in_progress`: a request with this key is still running.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "campaign_not_active",
                            "campaign_busy",
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified; retry later. `campaign_pause_failed`: the change could not be made; retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "campaign_pause_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Nothing was changed. `campaign_change_timeout`: the change took too long and was stopped. Retry after `Retry-After` seconds.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "campaign_change_timeout"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/campaigns/{id}/resume": {
      "post": {
        "operationId": "resumeCampaign",
        "tags": [
          "Campaigns"
        ],
        "summary": "Resume a paused campaign",
        "description": "Continues sending, like the **Resume** button in the SanadWA console. Only a `paused` campaign can be resumed. Your credits must cover every message the campaign still has to send, or it stays paused and the answer is `402`.",
        "x-required-scope": "send",
        "parameters": [
          {
            "$ref": "#/components/parameters/CampaignId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "200": {
            "description": "The campaign after the change.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Idempotent-Replay": {
                "$ref": "#/components/headers/XIdempotentReplay"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignResponse"
                }
              }
            }
          },
          "400": {
            "description": "`idempotency_key_too_long`: the `Idempotency-Key` header is longer than 120 characters. `INVALID_JSON`: the body is not valid JSON (send no body, or `{}`).",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "idempotency_key_too_long",
                            "INVALID_JSON"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "The campaign stays paused. `insufficient_credits`: your credits do not cover the messages it still has to send; `details` gives the credits needed and available. Buy credits, then resume. `subscription_lapsed`: the subscription that covered your sending ended and your credits do not cover the rest of the campaign.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "insufficient_credits",
                            "subscription_lapsed"
                          ]
                        },
                        "details": {
                          "$ref": "#/components/schemas/FundingErrorDetails"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "description": "`campaign_not_found`: no WhatsApp campaign with this id exists in your account. A campaign of another account answers the same way.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "campaign_not_found"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "409": {
            "description": "`campaign_not_paused`: only a `paused` campaign can be resumed; `details.status` gives its status. `campaign_cannot_resume`: the campaign cannot continue (for example its event was cancelled); `message` says why. `campaign_busy`: another change to this campaign is in progress; retry in a few seconds. `idempotency_conflict`: this `Idempotency-Key` was already used with a different request. `idempotency_in_progress`: a request with this key is still running.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "campaign_not_paused",
                            "campaign_cannot_resume",
                            "campaign_busy",
                            "idempotency_conflict",
                            "idempotency_in_progress"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "description": "Server error. `api_key_lookup_failed`: the API key could not be verified; retry later. `campaign_resume_failed`: the change could not be made; retry.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "api_key_lookup_failed",
                            "campaign_resume_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "503": {
            "description": "Nothing was changed. `campaign_change_timeout`: the change took too long and was stopped. `balance_check_failed`: your credit balance could not be checked. Retry after `Retry-After` seconds.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/XRateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/XRateLimitRemaining"
              },
              "X-Request-Id": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Error"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string",
                          "enum": [
                            "campaign_change_timeout",
                            "balance_check_failed"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "NotFound": {
        "description": "`not_found`: no endpoint has this HTTP method and path, for example a misspelt path or `PUT` on a path that only has `GET`. Not tied to any one operation: every request under the API's base URL that matches no operation gets this answer, under both prefixes. It is answered before the API key is checked, so it has no rate-limit headers.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/NotFoundError"
            },
            "example": {
              "success": false,
              "code": "not_found",
              "message": "This endpoint does not exist. Check the HTTP method and the path."
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The API key is missing, malformed, unknown or revoked. `api_key_missing`: no `Authorization: Bearer` header. `api_key_invalid`: the key does not start with `pk_live_`, is not recognized, or has been revoked.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "enum": [
                        "api_key_missing",
                        "api_key_invalid"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "Forbidden": {
        "description": "The API key may not call this endpoint. `api_key_ip_blocked`: the key has an IP allow-list and the request came from another address. `api_scope_insufficient`: the key's scope is below the scope this endpoint requires. `private_auth_restricted`: the key is restricted to a single purpose and cannot call this endpoint.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "enum": [
                        "api_key_ip_blocked",
                        "api_scope_insufficient",
                        "private_auth_restricted"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "RateLimited": {
        "description": "The API key has used its request allowance for the current minute. Wait the number of seconds in `Retry-After` before retrying.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemainingZero"
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "enum": [
                        "rate_limited"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "`PAYLOAD_TOO_LARGE`: the request body is larger than 100 KB.",
        "headers": {
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "enum": [
                        "PAYLOAD_TOO_LARGE"
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "InsufficientCredits": {
        "description": "The account cannot pay for this message. `PREPAID_CREDIT_CAP_REACHED`: not enough credits; buy credits and retry. `SUBSCRIPTION_LAPSED`: the subscription that covered sending ended recently and there are not enough credits. `PREPAID_CAP_CHECK_FAILED`: the balance could not be checked, so nothing was sent; retry later. `details` gives the credits needed and remaining when known. No message was sent.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Error"
                },
                {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "string",
                      "enum": [
                        "PREPAID_CREDIT_CAP_REACHED",
                        "SUBSCRIPTION_LAPSED",
                        "PREPAID_CAP_CHECK_FAILED"
                      ]
                    },
                    "details": {
                      "$ref": "#/components/schemas/FundingErrorDetails"
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "SendFailed": {
        "description": "`send_failed`: WhatsApp (Meta) rejected the message or it could not be handed over. The HTTP status is the one WhatsApp returned when there was one, otherwise 500. `upstream_code` and `meta_error` carry WhatsApp's own error; when WhatsApp reports a payment problem on the WhatsApp Business account (error 131042) the response also has `recovery`. Whether a retry can succeed depends on the WhatsApp error.",
        "headers": {
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/XRateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/XRateLimitRemaining"
          },
          "X-Request-Id": {
            "$ref": "#/components/headers/XRequestId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/SendFailedError"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key (pk_live_...)",
        "description": "An API key from the SanadWA console, sent as `Authorization: Bearer pk_live_...`. The key's scope (`read`, `send` or `full`) decides which operations it may call; see `x-required-scope` on each operation."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Optional. A unique value you choose (up to 120 characters; a UUID works well) so that you can retry this request safely. A repeat with the same key and body within 24 hours returns the first successful response instead of performing the request again. Use a new key for every new request. See \"Idempotent retries\" in the introduction.",
        "schema": {
          "type": "string",
          "maxLength": 120
        },
        "example": "3f1c2a9e-7d4b-4c55-9a1e-2b6f0d8c4e11"
      },
      "Page": {
        "name": "page",
        "in": "query",
        "required": false,
        "description": "Page number, starting at 1. Send a whole number. Values below 1 or that are not numbers are treated as 1. For fractional values see \"Pagination\" in the introduction.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Items per page, 1 to 200. Send a whole number. Larger values are treated as 200; missing, zero or non-numeric values as 50; negative whole numbers as 1. For fractional values see \"Pagination\" in the introduction.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "IdempotencyKeyRequired": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "Required on this endpoint. A unique value you choose for each new campaign (up to 120 characters; a UUID works well). If the request times out or fails, retry with the same key and the same body: the API never creates or sends a second campaign for one key. A key that already created a campaign returns that campaign (with `X-Idempotent-Replay: true`) for as long as the campaign exists, not only for 24 hours: within 24 hours the first answer exactly, afterwards the campaign's current state. Using the key again with a different body answers `409 idempotency_conflict`.",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        },
        "example": "8d0a6a3e-5c1f-4c9b-a3f1-0f6c2b7e9d41"
      },
      "CampaignId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The campaign id, from `POST /campaigns` or `GET /campaigns`.",
        "schema": {
          "type": "string",
          "pattern": "^[1-9][0-9]{0,15}$"
        },
        "example": "4821"
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "description": "Requests per minute allowed for this API key.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitRemaining": {
        "description": "Requests left in the current window for this API key.",
        "schema": {
          "type": "integer"
        }
      },
      "XRateLimitRemainingZero": {
        "description": "Always `0` on a rate-limited response.",
        "schema": {
          "type": "integer",
          "const": 0
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfterIdempotency": {
        "description": "Present on `idempotency_in_progress`: seconds to wait before retrying (1).",
        "schema": {
          "type": "integer"
        }
      },
      "XRequestId": {
        "description": "Identifier of this request. Quote it when contacting support.",
        "schema": {
          "type": "string"
        }
      },
      "XIdempotentReplay": {
        "description": "`true` when this response is a replay of an earlier request with the same `Idempotency-Key`. Absent otherwise.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "success",
          "code",
          "message"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code. Each operation lists the codes it can return. One code belongs to no operation: `not_found` (HTTP 404) answers any request whose method and path match no endpoint; see the `NotFound` response and the `NotFoundError` schema."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation, in English. May change; do not parse it."
          },
          "message_ar": {
            "type": "string",
            "description": "The same explanation in Arabic, on some errors."
          },
          "details": {
            "type": "object",
            "description": "Machine-readable context, on some errors.",
            "additionalProperties": true
          }
        }
      },
      "NotFoundError": {
        "description": "The answer to a request whose HTTP method and path match no endpoint (HTTP 404). Not listed under any operation, because it is not tied to one.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "not_found"
                ]
              }
            }
          }
        ]
      },
      "FundingErrorDetails": {
        "type": "object",
        "properties": {
          "needed": {
            "type": [
              "number",
              "null"
            ],
            "description": "Credits this send needs."
          },
          "remaining": {
            "type": [
              "number",
              "null"
            ],
            "description": "Credits available."
          },
          "shortfall": {
            "type": [
              "number",
              "null"
            ],
            "description": "Credits missing."
          }
        }
      },
      "ReplyWindowDetails": {
        "type": "object",
        "properties": {
          "last_inbound_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the recipient last wrote to this number, if ever."
          },
          "window_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the 24-hour window closed (null if they never wrote)."
          },
          "template_required": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "SendFailedError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Error"
          },
          {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "send_failed"
                ]
              },
              "upstream_code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "WhatsApp's error code, when WhatsApp returned one."
              },
              "meta_error": {
                "type": "object",
                "description": "WhatsApp's error, when it returned one.",
                "properties": {
                  "title": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "message": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "code": {
                    "type": [
                      "integer",
                      "string",
                      "null"
                    ]
                  },
                  "subcode": {
                    "type": [
                      "integer",
                      "string",
                      "null"
                    ]
                  },
                  "type": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "fbtrace_id": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "details": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "recovery": {
                "type": "object",
                "description": "Present when WhatsApp reports a payment problem (error 131042): what to fix in the WhatsApp Business account.",
                "properties": {
                  "action": {
                    "type": "string",
                    "enum": [
                      "manage_meta_payment_method"
                    ]
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              }
            }
          }
        ]
      },
      "SendTemplateRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "phone_number_id",
          "to_phone",
          "template_name"
        ],
        "properties": {
          "phone_number_id": {
            "type": "string",
            "description": "The sending number's id, from `GET /phone-numbers`.",
            "example": "106540352242922"
          },
          "to_phone": {
            "type": "string",
            "description": "Recipient phone number in international format.",
            "example": "+12025550123"
          },
          "template_name": {
            "type": "string",
            "description": "Name of an approved template.",
            "example": "order_update"
          },
          "template_language": {
            "type": "string",
            "default": "ar",
            "description": "Language code of the template, as listed by `GET /templates`.",
            "example": "en_US"
          },
          "template_params": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Values for the template's body variables, in order.",
            "example": [
              "Sara",
              "A-1042"
            ]
          },
          "template_components": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "WhatsApp template components (body, header, button parameters) in the WhatsApp Cloud API format. When present and not empty, they are sent as given and `template_params` is ignored."
          },
          "idempotency_key": {
            "type": "string",
            "maxLength": 120,
            "description": "Accepted for compatibility and ignored. Use the `Idempotency-Key` header."
          }
        }
      },
      "SendTextRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "phone_number_id",
          "to_phone",
          "text"
        ],
        "properties": {
          "phone_number_id": {
            "type": "string",
            "maxLength": 128,
            "description": "The sending number's id, from `GET /phone-numbers`. The recipient must have written to this number in the last 24 hours.",
            "example": "106540352242922"
          },
          "to_phone": {
            "type": "string",
            "maxLength": 32,
            "description": "Recipient phone number in international format.",
            "example": "+12025550123"
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 4096,
            "description": "Message text. Must contain at least one non-space character.",
            "example": "Your order A-1042 has shipped and arrives tomorrow."
          },
          "preview_url": {
            "type": "boolean",
            "default": false,
            "description": "Show a preview of the first link in the text.",
            "example": false
          },
          "idempotency_key": {
            "type": "string",
            "maxLength": 120,
            "description": "Accepted for compatibility and ignored. Use the `Idempotency-Key` header."
          }
        }
      },
      "UpstreamAcceptance": {
        "type": "object",
        "description": "WhatsApp's acceptance response, passed through.",
        "properties": {
          "messaging_product": {
            "type": "string",
            "example": "whatsapp"
          },
          "contacts": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "SendTemplateResponse": {
        "type": "object",
        "required": [
          "success",
          "message_id",
          "accepted_at"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "message_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "WhatsApp message id; use it with `GET /messages/{id}`.",
            "example": "wamid.HBgExample1"
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time",
            "description": "When SanadWA received WhatsApp's acceptance."
          },
          "upstream": {
            "$ref": "#/components/schemas/UpstreamAcceptance"
          }
        }
      },
      "SendTextResponse": {
        "type": "object",
        "required": [
          "success",
          "message_id",
          "message_type",
          "accepted_at"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "message_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "WhatsApp message id; use it with `GET /messages/{id}`.",
            "example": "wamid.HBgExample2"
          },
          "message_type": {
            "type": "string",
            "const": "text"
          },
          "accepted_at": {
            "type": "string",
            "format": "date-time"
          },
          "upstream": {
            "$ref": "#/components/schemas/UpstreamAcceptance"
          }
        }
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "integer",
              "string"
            ],
            "description": "SanadWA's internal message number. For messages sent with this API it is the `message_id` that message webhook events (`message.delivered`, `message.read`, `message.failed`) carry."
          },
          "provider_msg_id": {
            "type": "string",
            "description": "WhatsApp message id.",
            "example": "wamid.HBgExample1"
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current status: `sent`, `delivered`, `read` or `failed`. See the operation description for how it changes.",
            "example": "delivered"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When WhatsApp accepted the message."
          },
          "template_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Template name, or `null` for a free-form text message.",
            "example": "order_update"
          },
          "to_phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Recipient phone number.",
            "example": "+12025550123"
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "example": "whatsapp"
          },
          "provider": {
            "type": [
              "string",
              "null"
            ],
            "example": "waba"
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "WhatsApp's reason when the message failed, otherwise `null`."
          },
          "delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When WhatsApp reported the message delivered, or `null`."
          },
          "read_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When WhatsApp reported the message read, or `null`."
          },
          "failed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When WhatsApp reported a failure. Always `null` for messages sent with this API, from the console inbox or from campaigns: use `status` and `error`."
          },
          "last_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Status reported by WhatsApp. For messages sent with this API it equals `status`."
          }
        }
      },
      "GetMessageResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "$ref": "#/components/schemas/Message"
          }
        }
      },
      "Template": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "order_update"
          },
          "language": {
            "type": "string",
            "example": "en_US"
          },
          "category": {
            "type": [
              "string",
              "null"
            ],
            "description": "`MARKETING`, `UTILITY` or `AUTHENTICATION`."
          },
          "status": {
            "type": "string",
            "description": "Always `APPROVED` here."
          },
          "waba_id": {
            "type": "string",
            "description": "The WhatsApp Business account that owns the template."
          },
          "components": {
            "type": [
              "array",
              "object",
              "null"
            ],
            "description": "The template's components as approved by WhatsApp."
          }
        }
      },
      "ListTemplatesResponse": {
        "type": "object",
        "required": [
          "success",
          "page",
          "limit",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Template"
            }
          }
        }
      },
      "PhoneNumber": {
        "type": "object",
        "properties": {
          "phone_number_id": {
            "type": "string",
            "description": "Use this as `phone_number_id` when sending.",
            "example": "106540352242922"
          },
          "display_phone_number": {
            "type": [
              "string",
              "null"
            ],
            "example": "+1 202-555-0123"
          },
          "verified_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "The display name shown to recipients, once approved."
          },
          "name_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "WhatsApp's review status of the display name."
          },
          "new_name_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Review status of a requested new display name."
          },
          "waba_id": {
            "type": "string",
            "description": "The WhatsApp Business account the number belongs to."
          },
          "quality_rating": {
            "type": [
              "string",
              "null"
            ],
            "description": "WhatsApp's quality rating, for example `GREEN`."
          },
          "status": {
            "type": [
              "string",
              "null"
            ],
            "description": "WhatsApp's connection status, for example `CONNECTED`."
          }
        }
      },
      "ListPhoneNumbersResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PhoneNumber"
            }
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "required": [
          "success",
          "tenant_id",
          "scope",
          "server_time"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "tenant_id": {
            "type": "integer",
            "description": "Your SanadWA account number."
          },
          "scope": {
            "type": "string",
            "enum": [
              "read",
              "send",
              "full"
            ],
            "description": "The key's scope."
          },
          "server_time": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Balance": {
        "type": "object",
        "required": [
          "billing_type",
          "available_credits",
          "paid_credits_remaining",
          "free_credits_remaining",
          "unlimited",
          "unlimited_until",
          "usage_this_month",
          "as_of"
        ],
        "properties": {
          "billing_type": {
            "type": "string",
            "enum": [
              "prepaid",
              "postpaid"
            ],
            "description": "`prepaid`: you buy credits in advance. `postpaid`: usage is invoiced."
          },
          "available_credits": {
            "type": [
              "number",
              "null"
            ],
            "description": "Credits you can spend now (paid plus free, minus credits held for messages being sent). `null` on postpaid accounts."
          },
          "paid_credits_remaining": {
            "type": [
              "number",
              "null"
            ],
            "description": "Purchased credits not yet used. `null` on postpaid accounts."
          },
          "free_credits_remaining": {
            "type": "number",
            "description": "Free credits left this month."
          },
          "unlimited": {
            "type": "boolean",
            "description": "`true` while a subscription covers your sending; messages then use no credits."
          },
          "unlimited_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the covering subscription period ends. `null` when `unlimited` is `false`."
          },
          "usage_this_month": {
            "type": "object",
            "required": [
              "period_start",
              "credits_used",
              "marketing",
              "utility",
              "authentication",
              "service"
            ],
            "description": "Credits used since the start of the current month (UTC), by message category.",
            "properties": {
              "period_start": {
                "type": "string",
                "format": "date",
                "description": "First day of the month (UTC)."
              },
              "credits_used": {
                "type": "number"
              },
              "marketing": {
                "type": "number"
              },
              "utility": {
                "type": "number"
              },
              "authentication": {
                "type": "number"
              },
              "service": {
                "type": "number"
              }
            }
          },
          "as_of": {
            "type": "string",
            "format": "date-time",
            "description": "When the balance was computed."
          }
        }
      },
      "BalanceResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "$ref": "#/components/schemas/Balance"
          }
        },
        "example": {
          "success": true,
          "data": {
            "billing_type": "prepaid",
            "available_credits": 670,
            "paid_credits_remaining": 600,
            "free_credits_remaining": 70,
            "unlimited": false,
            "unlimited_until": null,
            "usage_this_month": {
              "period_start": "2026-11-01",
              "credits_used": 34,
              "marketing": 30,
              "utility": 4,
              "authentication": 0,
              "service": 0
            },
            "as_of": "2026-11-11T10:00:00.000Z"
          }
        }
      },
      "Tag": {
        "type": "object",
        "description": "A tag in your account's tag list. Tags label contacts; a contact carries tag names (see `Contact.tags`).",
        "required": [
          "id",
          "name",
          "description",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The tag's id, a decimal number in a string.",
            "example": "42"
          },
          "name": {
            "type": "string",
            "description": "The tag's name, unique in your account without regard to case.",
            "example": "VIP"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "A free-text note about the tag, or `null`.",
            "example": "Donors who gave more than 1,000 SAR this year"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the tag was created, in UTC (ISO 8601).",
            "example": "2026-11-11T10:00:00.000Z"
          }
        }
      },
      "ListTagsResponse": {
        "type": "object",
        "required": [
          "success",
          "page",
          "limit",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Tag"
            }
          }
        }
      },
      "CreateTagRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "pattern": "^[^,;]*$",
            "description": "The tag's name: 1 to 100 characters after surrounding spaces are removed, without `,` or `;` (the CSV import separates tag names with them). Must not already exist in your account, compared without regard to case.",
            "example": "VIP"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 500,
            "description": "Optional note about the tag, up to 500 characters.",
            "example": "Donors who gave more than 1,000 SAR this year"
          }
        }
      },
      "TagResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "$ref": "#/components/schemas/Tag"
          }
        }
      },
      "DeletedResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "required": [
              "id",
              "deleted"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "The id of the deleted record.",
                "example": "42"
              },
              "deleted": {
                "type": "boolean",
                "const": true
              }
            }
          }
        }
      },
      "Contact": {
        "type": "object",
        "description": "A contact in your account. The phone number identifies the contact: your account has at most one contact per phone number.",
        "required": [
          "id",
          "phone",
          "first_name",
          "last_name",
          "email",
          "tags",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The contact's id, a decimal number in a string.",
            "example": "1001"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "The phone number in E.164 format (`+` and country code).",
            "example": "+12025550123"
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Sara"
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Ahmed"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "example": "sara@example.com"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The names of the tags the contact carries. A contact that asked to stop receiving marketing messages carries the opt-out tag your account uses for that.",
            "example": [
              "VIP",
              "Riyadh"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "When the contact was created, in UTC (ISO 8601).",
            "example": "2026-11-11T10:00:00.000Z"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the contact was last changed, in UTC (ISO 8601), or `null`.",
            "example": "2026-11-11T10:05:00.000Z"
          }
        }
      },
      "ListContactsResponse": {
        "type": "object",
        "required": [
          "success",
          "page",
          "limit",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contact"
            }
          }
        }
      },
      "ContactInput": {
        "type": "object",
        "required": [
          "phone"
        ],
        "additionalProperties": false,
        "properties": {
          "phone": {
            "type": "string",
            "maxLength": 32,
            "description": "The phone number, preferably in E.164 format (`+` and country code). Spaces, dashes, dots and brackets are allowed and removed. A number without a country code is read as a Saudi number. Stored in E.164 format. Identifies the contact: if your account already has a contact with this number, that contact is updated.",
            "example": "+12025550123"
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "example": "Sara"
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100,
            "example": "Ahmed"
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "maxLength": 254,
            "example": "sara@example.com"
          },
          "tags": {
            "type": "array",
            "maxItems": 10,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100
            },
            "description": "Names of tags to add to the contact. Each must be in your account's tag list (see `GET /tags`); names are matched without regard to case. Tags are only ever added: tags the contact already carries are kept.",
            "example": [
              "VIP"
            ]
          }
        }
      },
      "CreateContactsRequest": {
        "type": "object",
        "required": [
          "contacts"
        ],
        "properties": {
          "contacts": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "$ref": "#/components/schemas/ContactInput"
            },
            "description": "1 to 100 contacts. Each phone number may appear only once per request."
          }
        }
      },
      "ContactError": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "validation_error",
              "duplicate_phone",
              "tag_not_found",
              "contact_write_failed"
            ],
            "description": "`validation_error`: a field is missing or invalid; `message` names it. `duplicate_phone`: the same phone number appears earlier in the request. `tag_not_found`: a tag name is not in your account's tag list. `contact_write_failed`: the contact could not be saved because of a temporary server problem; send it again."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation, in English. May change; do not parse it."
          }
        }
      },
      "ContactResult": {
        "type": "object",
        "required": [
          "index",
          "status",
          "contact",
          "error"
        ],
        "properties": {
          "index": {
            "type": "integer",
            "description": "The position of the contact in the request's `contacts` array, starting at 0."
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "updated",
              "rejected",
              "failed"
            ],
            "description": "`created`: a new contact was created. `updated`: a contact with this phone number already existed and was updated. `rejected`: the contact was not saved because of a problem with it (see `error`); fix it before sending it again. `failed`: the contact was not saved because of a temporary server problem; send it again."
          },
          "contact": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Contact"
              },
              {
                "type": "null"
              }
            ],
            "description": "The contact as saved, or `null` when it was not saved."
          },
          "error": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ContactError"
              },
              {
                "type": "null"
              }
            ],
            "description": "Why the contact was not saved, or `null`."
          }
        }
      },
      "ContactsSummary": {
        "type": "object",
        "required": [
          "created",
          "updated",
          "rejected",
          "failed"
        ],
        "properties": {
          "created": {
            "type": "integer"
          },
          "updated": {
            "type": "integer"
          },
          "rejected": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          }
        }
      },
      "CreateContactsResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "required": [
              "summary",
              "results"
            ],
            "properties": {
              "summary": {
                "$ref": "#/components/schemas/ContactsSummary"
              },
              "results": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ContactResult"
                },
                "description": "One result per contact in the request, in the same order."
              }
            }
          }
        }
      },
      "ImportContactsRequest": {
        "type": "object",
        "required": [
          "file"
        ],
        "properties": {
          "file": {
            "type": "string",
            "contentMediaType": "text/csv",
            "description": "A CSV file (UTF-8, comma-separated, at most 5 MB and 5,000 rows) whose first row names the columns. Recognised columns: `phone` (also `phone_number` or `phoneNumber`; required in every row), `first_name` (or `firstName`, `firstname`), `last_name` (or `lastName`, `lastname`), `email`, and `tags` (or `tag`: tag names separated by `;` or `,`, at most 10). Other columns are ignored. Excel workbooks are not accepted; save the sheet as CSV first."
          },
          "tags": {
            "type": "string",
            "description": "Optional. Comma-separated names of tags to add to every imported contact, at most 50, each at most 100 characters. Each must be in your account's tag list (see `GET /tags`).",
            "example": "VIP,Ramadan 2026"
          }
        }
      },
      "ImportRowError": {
        "type": "object",
        "required": [
          "row",
          "status",
          "code",
          "message"
        ],
        "properties": {
          "row": {
            "type": "integer",
            "description": "The row's line number in the file, counting the header row as line 1.",
            "example": 7
          },
          "status": {
            "type": "string",
            "enum": [
              "rejected",
              "failed"
            ],
            "description": "As in `ContactResult.status`."
          },
          "code": {
            "type": "string",
            "enum": [
              "validation_error",
              "tag_not_found",
              "contact_write_failed"
            ],
            "description": "As in `ContactError.code`."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation, in English. May change; do not parse it."
          }
        }
      },
      "ImportContactsResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "type": "object",
            "required": [
              "summary",
              "errors"
            ],
            "properties": {
              "summary": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ContactsSummary"
                  },
                  {
                    "type": "object",
                    "required": [
                      "rows"
                    ],
                    "properties": {
                      "rows": {
                        "type": "integer",
                        "description": "The number of contact rows in the file."
                      }
                    }
                  }
                ]
              },
              "errors": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ImportRowError"
                },
                "description": "One entry per row that was not saved. Empty when every row was saved."
              }
            }
          }
        }
      },
      "CampaignTemplateVariable": {
        "type": "object",
        "required": [
          "index",
          "source"
        ],
        "description": "The value of one body variable (`{{1}}`, `{{2}}`, ...) of the template. Give one entry per body variable, numbered from 1 without gaps.",
        "properties": {
          "index": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "description": "The variable's number in the template body: 1 for `{{1}}`."
          },
          "source": {
            "type": "string",
            "enum": [
              "contact.first_name",
              "contact.last_name",
              "contact.phone",
              "contact.email",
              "custom"
            ],
            "description": "Where each recipient's value comes from: a field of the contact, or `custom` for the same text for everyone. A recipient whose value is empty (for example a contact with no first name) is skipped and counted as `canceled`."
          },
          "value": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1024,
            "description": "The text, when `source` is `custom`. Not allowed for other sources."
          }
        },
        "additionalProperties": false
      },
      "CreateCampaignRequest": {
        "type": "object",
        "required": [
          "name",
          "phone_number_id",
          "template",
          "audience"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "description": "A name for the campaign, shown in the SanadWA console."
          },
          "phone_number_id": {
            "type": "string",
            "pattern": "^[0-9]{1,32}$",
            "description": "The sending number's id, from `GET /phone-numbers`."
          },
          "template": {
            "type": "object",
            "required": [
              "name",
              "language"
            ],
            "additionalProperties": false,
            "description": "An approved template of the sending number's WhatsApp Business account, from `GET /templates?phone_number_id=...`.",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 1,
                "maxLength": 512
              },
              "language": {
                "type": "string",
                "minLength": 1,
                "maxLength": 32,
                "description": "The template's language code, for example `ar` or `en_US`."
              }
            }
          },
          "audience": {
            "type": "object",
            "required": [
              "tags"
            ],
            "additionalProperties": false,
            "description": "Who receives the campaign: every contact that has at least one of these tags, minus contacts that opted out of marketing messages, contacts paused after a delivery problem, and numbers that are not valid WhatsApp numbers. Each phone number receives the campaign once.",
            "properties": {
              "tags": {
                "type": "array",
                "minItems": 1,
                "maxItems": 20,
                "uniqueItems": true,
                "items": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 100
                },
                "description": "Tag names as they appear in your account (compared without regard to letter case)."
              }
            }
          },
          "template_variables": {
            "type": "array",
            "maxItems": 100,
            "default": [],
            "items": {
              "$ref": "#/components/schemas/CampaignTemplateVariable"
            },
            "description": "Values for the template's body variables. Leave it out for a template without variables."
          },
          "header_media_url": {
            "type": "string",
            "format": "uri",
            "maxLength": 2048,
            "description": "Required when the template has an image, video or document header: an `https` link to the file. WhatsApp downloads it when sending."
          },
          "tracked_destination_url": {
            "type": "string",
            "maxLength": 2048,
            "description": "Only for templates with click-tracked buttons that have no saved destination: the `https` link the button opens. Clicks are counted in `clicks`."
          },
          "send_at": {
            "type": "string",
            "format": "date-time",
            "description": "When to start sending, with a time zone, at most 30 days ahead. Leave it out to start sending now."
          }
        },
        "example": {
          "name": "November offers",
          "phone_number_id": "112233445566778",
          "template": {
            "name": "november_offer",
            "language": "ar"
          },
          "audience": {
            "tags": [
              "vip",
              "riyadh"
            ]
          },
          "template_variables": [
            {
              "index": 1,
              "source": "contact.first_name"
            },
            {
              "index": 2,
              "source": "custom",
              "value": "20%"
            }
          ],
          "send_at": "2026-11-20T09:00:00+03:00"
        }
      },
      "CampaignCounts": {
        "type": "object",
        "description": "Messages of this campaign by state. Every recipient is one message. Numbers change while the campaign sends and as WhatsApp reports deliveries and reads.",
        "properties": {
          "total": {
            "type": "integer",
            "description": "All messages of the campaign."
          },
          "queued": {
            "type": "integer",
            "description": "Waiting to be sent."
          },
          "sending": {
            "type": "integer",
            "description": "Being handed to WhatsApp right now."
          },
          "sent": {
            "type": "integer",
            "description": "Accepted by WhatsApp, not yet reported delivered."
          },
          "delivered": {
            "type": "integer",
            "description": "Delivered to the recipient's phone, not yet read."
          },
          "read": {
            "type": "integer",
            "description": "Read by the recipient (or played, for audio and video)."
          },
          "failed": {
            "type": "integer",
            "description": "Not delivered. Credits are not kept for messages WhatsApp reports as failed."
          },
          "canceled": {
            "type": "integer",
            "description": "Not sent on purpose, for example because a template variable was empty for that recipient, or the campaign was stopped."
          }
        }
      },
      "Campaign": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The campaign id."
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "scheduled",
              "delayed",
              "start_failed",
              "active",
              "paused",
              "completed",
              "failed"
            ],
            "description": "The campaign's state:\n- `draft`: saved in the console but not started. Campaigns created through this API are never drafts.\n- `scheduled`: waiting for `start_at`. Its recipients are already chosen, and they are checked again when it starts.\n- `delayed`: `start_at` has passed and starting is being retried after a temporary problem.\n- `start_failed`: the scheduled start could not happen in time; nothing was sent. Open the campaign in the console.\n- `active`: sending. Messages go out gradually; `counts` changes as they do.\n- `paused`: sending is stopped; `pause_reason` says why. Messages not sent yet stay `queued` until you resume.\n- `completed`: every message has been attempted. Delivery and read updates can still arrive afterwards.\n- `failed`: the campaign stopped because of an error and will not send more."
          },
          "pause_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "paused_by_user",
              "insufficient_credits",
              "balance_check_failed",
              "start_failed",
              "event_cancelled",
              null
            ],
            "description": "Only when `status` is `paused`, why sending is stopped; `null` otherwise.\n- `paused_by_user`: someone paused it in the console or with `POST /campaigns/{id}/pause`.\n- `insufficient_credits`: sending stopped because the account's credits ran out, or the subscription that covered sending ended. Buy credits, then resume.\n- `balance_check_failed`: sending stopped because your credit balance could not be checked at that moment. Nothing was charged for the messages still `queued`. Resume to continue; resuming checks the balance again.\n- `start_failed`: a scheduled start did not complete; check the campaign in the console.\n- `event_cancelled`: the campaign was scheduled around an event in the console, and the event was cancelled. Its unsent messages were canceled and it cannot be resumed: `POST /campaigns/{id}/resume` answers `409 campaign_not_paused`.\n\nSanadWA does not record who paused a campaign: the reason is worked out from the campaign and from its messages that are still `queued`. So if a campaign stopped for credits or a failed balance check, was resumed, and was paused again before those messages were retried, it can still show `insufficient_credits` or `balance_check_failed` although the latest pause was a person's. Likewise, a campaign that a person pauses shortly after a failed balance check can show `balance_check_failed`."
          },
          "phone_number_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The sending number's id."
          },
          "template": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "name": {
                "type": "string"
              },
              "language": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "audience": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "tags",
                  "all_contacts",
                  "uploaded_list",
                  "event",
                  "retarget"
                ],
                "description": "`tags` for campaigns created with this API. Campaigns created in the console can use other audiences; their recipients are not listed here."
              },
              "tags": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Only for `tags`: the tag names."
              }
            }
          },
          "start_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When sending started, or is scheduled to start."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_via": {
            "type": "string",
            "enum": [
              "api",
              "console"
            ],
            "description": "`api` for campaigns created with this API, `console` otherwise."
          },
          "counts": {
            "$ref": "#/components/schemas/CampaignCounts"
          },
          "clicks": {
            "type": "object",
            "description": "Only on `GET /campaigns/{id}`, and meaningful only for templates with click-tracked buttons.",
            "properties": {
              "tracked": {
                "type": "integer",
                "description": "Messages with a tracked link."
              },
              "clicked": {
                "type": "integer",
                "description": "Messages whose link was opened at least once."
              }
            }
          }
        }
      },
      "CampaignResponse": {
        "type": "object",
        "required": [
          "success",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "$ref": "#/components/schemas/Campaign"
          }
        },
        "example": {
          "success": true,
          "data": {
            "id": "4821",
            "name": "November offers",
            "status": "active",
            "pause_reason": null,
            "phone_number_id": "112233445566778",
            "template": {
              "name": "november_offer",
              "language": "ar"
            },
            "audience": {
              "type": "tags",
              "tags": [
                "vip",
                "riyadh"
              ]
            },
            "start_at": "2026-11-11T10:00:02.000Z",
            "created_at": "2026-11-11T10:00:02.000Z",
            "updated_at": "2026-11-11T10:00:02.000Z",
            "created_via": "api",
            "counts": {
              "total": 1250,
              "queued": 830,
              "sending": 20,
              "sent": 150,
              "delivered": 180,
              "read": 60,
              "failed": 8,
              "canceled": 2
            },
            "clicks": {
              "tracked": 0,
              "clicked": 0
            }
          }
        }
      },
      "ListCampaignsResponse": {
        "type": "object",
        "required": [
          "success",
          "page",
          "limit",
          "data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "page": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Campaign"
            }
          }
        },
        "example": {
          "success": true,
          "page": 1,
          "limit": 50,
          "data": [
            {
              "id": "4821",
              "name": "November offers",
              "status": "active",
              "pause_reason": null,
              "phone_number_id": "112233445566778",
              "template": {
                "name": "november_offer",
                "language": "ar"
              },
              "audience": {
                "type": "tags",
                "tags": [
                  "vip",
                  "riyadh"
                ]
              },
              "start_at": "2026-11-11T10:00:02.000Z",
              "created_at": "2026-11-11T10:00:02.000Z",
              "updated_at": "2026-11-11T10:00:02.000Z",
              "created_via": "api",
              "counts": {
                "total": 1250,
                "queued": 830,
                "sending": 20,
                "sent": 150,
                "delivered": 180,
                "read": 60,
                "failed": 8,
                "canceled": 2
              }
            }
          ]
        }
      }
    }
  }
}
