{"openapi":"3.1.0","info":{"title":"Pager API","version":"1.0.0","description":"The Pager API sends and receives SMS, manages the numbers that carry it, and\nrecords the consent that makes sending it lawful.\n\n## Authentication\n\nEvery endpoint except `/v1/health` requires an API key, presented as a bearer\ntoken:\n\n```\nAuthorization: Bearer pk_live_...\n```\n\nA key belongs to exactly one account and every request is scoped to it. A\nresource belonging to another account is reported as `404`, not `403`:\nanswering `403` would confirm the resource exists and let one tenant\nenumerate another's message and number ids by watching the status code.\n\nThe key is shown once, when it is issued, and stored only as a hash. A key that\nis lost cannot be recovered, only replaced.\n\n## Errors\n\nEvery failure has the same shape, so a client can write one error path:\n\n```json\n{ \"error\": { \"type\": \"invalid_request\", \"message\": \"...\", \"details\": [ ... ] } }\n```\n\nMatch on `type`, which is stable. `message` is for humans and may change\nwithout notice. `details` names the offending fields when the failure is\nattributable to input.\n\n`recipient_opted_out` is its own type rather than a bare `403` because a\ncaller has to be able to tell \"this person unsubscribed, remove them from your\nlist\" apart from \"your key may not do that\". Retrying the first will never\nsucceed.\n\nEvery response carries `X-Correlation-Id`. Quoting it in a support report\nidentifies the exact request in the platform logs.\n\n## Pagination\n\nCollections are cursor-paginated, not offset-paginated: an offset silently\nskips or repeats records when the underlying set changes between pages, which\nit does constantly here.\n\nRead `data`, and if `hasMore` is true, pass `nextCursor` back as\n`cursor`. `nextCursor` is null on the last page, so a client can loop on\nits presence rather than comparing cursors.\n\n## Idempotency\n\n`POST /v1/messages` accepts an `Idempotency-Key` header. A repeat of the\nsame key returns the original message with `200` instead of sending a second\none and returning `202`. The distinction tells a caller retrying after a\ntimeout that its first attempt did land.\n\nKeys are at most 128 characters of letters, digits and `.`, `:`, `_`,\n`-`. An over-long key is rejected rather than truncated, because truncating\nwould silently merge two distinct requests into one message.\n\n## Rate limits\n\nTwo buckets are charged: one for requests generally, and a narrower one for\nsends. A send costs carrier capacity shared with every other tenant, which is\nscarcer than the database query behind a `GET`; one bucket for both would let\na client polling its message list starve its own sending.\n\nEvery response carries `RateLimit-Limit`, `RateLimit-Remaining` and\n`RateLimit-Reset`. A client that reads them never has to be refused at all. A\n`429` also carries `Retry-After`.\n\nA send refused by validation or replayed from an idempotency key is refunded,\nso a client retrying safely is not punished for it.\n\n## Webhooks\n\nEvents are delivered to the account webhook, or to a per-number override, as a\n`POST` with two headers:\n\n```\nPager-Timestamp: 1735689600\nPager-Signature: v1=<hex hmac-sha256>\n```\n\nThe signature covers `${timestamp}.${rawBody}` under the account's own\nsigning secret, which `GET /v1/account` returns. Verify over the raw body\nbefore parsing it, compare in constant time, and reject a timestamp outside a\nfive-minute window — without that check a captured request can be replayed\nforever.\n\nEach account has its own secret. Verification is symmetric, so a shared secret\nwould let any customer mint signatures for any other customer's endpoint.\n\nFailed deliveries are retried on a backoff of 1s, 5s, 30s and 120s — five\nattempts in total. `5xx`, `429`, `408` and network errors are retried;\nevery other `4xx` is final, since a `404` at a mistyped path will still be\na `404`. Redirects are never followed. Every attempt is recorded and readable\nat `GET /v1/webhook_deliveries`."},"servers":[{"url":"https://0.0.0.0:3000"}],"tags":[{"name":"Messages","description":"Sending and reading SMS."},{"name":"Numbers","description":"The numbers an account can send from."},{"name":"Consent","description":"Who may be messaged, and the evidence for it. Carriers require this to be answerable on demand."},{"name":"Webhooks","description":"Delivery of events, and its audit."},{"name":"Account","description":"The account and its API keys."},{"name":"System","description":"Liveness."}],"paths":{"/v1/health":{"get":{"tags":["System"],"operationId":"getHealth","summary":"Liveness probe","description":"Reports only that the process is serving requests. Requires no authentication, so it can be used by a load balancer.","security":[],"responses":{"200":{"description":"The service is up.","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","const":"ok"},"service":{"type":"string"},"uptimeSeconds":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"version":{"type":"string"}},"required":["status","service","uptimeSeconds","version"],"additionalProperties":false}}}}}}},"/v1/account":{"get":{"tags":["Account"],"operationId":"getAccount","summary":"The account this key belongs to","description":"The cheapest way to confirm a key works and to discover which account it acts as, which matters when an operator holds keys for several tenants. Also where a customer collects the secret their webhooks are signed with.","responses":{"200":{"description":"The account.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["active","suspended"]},"webhookUrl":{"anyOf":[{"type":"string"},{"type":"null"}]},"webhookSigningSecret":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The secret outbound webhooks are signed with. Returned to the owning account and to nobody else."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","name","status","webhookUrl","webhookSigningSecret","createdAt"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/api-keys":{"get":{"tags":["Account"],"operationId":"listApiKeys","summary":"List API keys","description":"Every key ever issued to this account, including revoked ones, so an audit can account for all of them. Secrets are never returned; only a hint of the last few characters.","responses":{"200":{"description":"The keys.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"The fixed prefix every Pager key carries."},"hint":{"type":"string","description":"The last few characters, to tell keys apart."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"lastUsedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"revokedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]}},"required":["id","name","prefix","hint","createdAt","lastUsedAt","revokedAt"],"additionalProperties":false}}},"required":["data"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"tags":["Account"],"operationId":"issueApiKey","summary":"Issue an API key","description":"The secret is returned exactly once, in this response. It is stored only as a hash, so a caller that loses it must issue another key.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":100,"description":"How the key will be recognised."}},"required":["name"]}}}},"responses":{"201":{"description":"The key, including its secret.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"The fixed prefix every Pager key carries."},"hint":{"type":"string","description":"The last few characters, to tell keys apart."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"lastUsedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"revokedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"secret":{"type":"string","description":"The key itself. Returned exactly once, at creation. It is not stored in recoverable form, so a caller that loses it must issue another."}},"required":["id","name","prefix","hint","createdAt","lastUsedAt","revokedAt","secret"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/api-keys/{id}":{"delete":{"tags":["Account"],"operationId":"revokeApiKey","summary":"Revoke an API key","description":"Takes effect immediately. Revoking is not deleting: the record stays so an audit can still see the key existed and when it stopped working.","parameters":[{"name":"id","in":"path","required":true,"description":"The key id.","schema":{"type":"string"}}],"responses":{"200":{"description":"The revoked key.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"prefix":{"type":"string","description":"The fixed prefix every Pager key carries."},"hint":{"type":"string","description":"The last few characters, to tell keys apart."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"lastUsedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"revokedAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]}},"required":["id","name","prefix","hint","createdAt","lastUsedAt","revokedAt"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/messages":{"get":{"tags":["Messages"],"operationId":"listMessages","summary":"List messages","description":"Both directions, newest first. Filters combine, so `direction=outbound&status=failed` is everything that did not get through. An inbound message is stored `delivered`: it has already arrived, so there is no later outcome to wait for.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["queued","sent","delivered","failed","undelivered"]}},{"name":"direction","in":"query","required":false,"schema":{"type":"string","enum":["inbound","outbound"]}},{"name":"to","in":"query","required":false,"description":"An E.164 phone number, including the leading plus.","schema":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$"}},{"name":"from","in":"query","required":false,"description":"An E.164 phone number, including the leading plus.","schema":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$"}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` from the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many records to return, from 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"A page of messages.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"from":{"type":"string"},"to":{"type":"string"},"body":{"type":"string"},"status":{"type":"string","enum":["queued","sent","delivered","failed","undelivered"]},"encoding":{"anyOf":[{"type":"string","enum":["GSM7","UCS2"]},{"type":"null"}],"description":"Null on records that predate stored encoding."},"segments":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many segments the carrier billed for."},"errorCode":{"anyOf":[{"type":"string"},{"type":"null"}]},"errorMessage":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","direction","from","to","body","status","encoding","segments","errorCode","errorMessage","createdAt","updatedAt"],"additionalProperties":false}},"hasMore":{"type":"boolean"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass as `cursor` for the next page. Null on the last page."}},"required":["data","hasMore","nextCursor"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"tags":["Messages"],"operationId":"sendMessage","summary":"Send an SMS","description":"Returns `202`, not `200`. The message is recorded and queued; the carrier has not seen it yet. Delivery is reported later through `status` and through webhooks.\n\nThe response carries the computed segment count and encoding, because carriers bill per segment and a single emoji can turn a one-segment message into three.\n\nSending to a number that has opted out fails with `recipient_opted_out`; that is final, and the number must be removed from the list rather than retried.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Repeat a request safely. A replay returns the original message with `200` rather than sending again. At most 128 characters of letters, digits and `.`, `:`, `_`, `-`.","schema":{"type":"string","maxLength":128,"pattern":"^[\\w.:-]+$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","description":"An E.164 phone number, including the leading plus."},"to":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","description":"An E.164 phone number, including the leading plus."},"body":{"type":"string","minLength":1,"description":"The text to send."}},"required":["from","to","body"]}}}},"responses":{"200":{"description":"A replay of an earlier request with the same `Idempotency-Key`. Nothing was sent this time.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"from":{"type":"string"},"to":{"type":"string"},"body":{"type":"string"},"status":{"type":"string","enum":["queued","sent","delivered","failed","undelivered"]},"encoding":{"anyOf":[{"type":"string","enum":["GSM7","UCS2"]},{"type":"null"}],"description":"Null on records that predate stored encoding."},"segments":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many segments the carrier billed for."},"errorCode":{"anyOf":[{"type":"string"},{"type":"null"}]},"errorMessage":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","direction","from","to","body","status","encoding","segments","errorCode","errorMessage","createdAt","updatedAt"],"additionalProperties":false}}}},"202":{"description":"Accepted and queued.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"from":{"type":"string"},"to":{"type":"string"},"body":{"type":"string"},"status":{"type":"string","enum":["queued","sent","delivered","failed","undelivered"]},"encoding":{"anyOf":[{"type":"string","enum":["GSM7","UCS2"]},{"type":"null"}],"description":"Null on records that predate stored encoding."},"segments":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many segments the carrier billed for."},"errorCode":{"anyOf":[{"type":"string"},{"type":"null"}]},"errorMessage":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","direction","from","to","body","status","encoding","segments","errorCode","errorMessage","createdAt","updatedAt"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/messages/{id}":{"get":{"tags":["Messages"],"operationId":"getMessage","summary":"Fetch a message","parameters":[{"name":"id","in":"path","required":true,"description":"The message id.","schema":{"type":"string"}}],"responses":{"200":{"description":"The message.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"from":{"type":"string"},"to":{"type":"string"},"body":{"type":"string"},"status":{"type":"string","enum":["queued","sent","delivered","failed","undelivered"]},"encoding":{"anyOf":[{"type":"string","enum":["GSM7","UCS2"]},{"type":"null"}],"description":"Null on records that predate stored encoding."},"segments":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"How many segments the carrier billed for."},"errorCode":{"anyOf":[{"type":"string"},{"type":"null"}]},"errorMessage":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","direction","from","to","body","status","encoding","segments","errorCode","errorMessage","createdAt","updatedAt"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/numbers":{"get":{"tags":["Numbers"],"operationId":"listNumbers","summary":"List numbers","description":"The inventory this account may send from. Which carrier a number rides on is deliberately not reported: it is a routing decision the platform makes and changes.","parameters":[{"name":"capability","in":"query","required":false,"schema":{"type":"string","enum":["voice","sms"]}},{"name":"country","in":"query","required":false,"description":"An ISO 3166-1 alpha-2 country code.","schema":{"type":"string","minLength":2,"maxLength":2}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` from the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many records to return, from 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"A page of numbers.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"phoneNumber":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string","enum":["voice","sms"]}},"country":{"anyOf":[{"type":"string"},{"type":"null"}]},"inboundEnabled":{"type":"boolean"},"inboundWebhookUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Overrides the account webhook for this number when set."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","phoneNumber","capabilities","country","inboundEnabled","inboundWebhookUrl","createdAt","updatedAt"],"additionalProperties":false}},"hasMore":{"type":"boolean"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass as `cursor` for the next page. Null on the last page."}},"required":["data","hasMore","nextCursor"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/numbers/{phoneNumber}":{"get":{"tags":["Numbers"],"operationId":"getNumber","summary":"Fetch a number","parameters":[{"name":"phoneNumber","in":"path","required":true,"description":"The number in E.164, url-encoded or not; `+12125551234` and `%2B12125551234` both work.","schema":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$"}}],"responses":{"200":{"description":"The number.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"phoneNumber":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string","enum":["voice","sms"]}},"country":{"anyOf":[{"type":"string"},{"type":"null"}]},"inboundEnabled":{"type":"boolean"},"inboundWebhookUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Overrides the account webhook for this number when set."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","phoneNumber","capabilities","country","inboundEnabled","inboundWebhookUrl","createdAt","updatedAt"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"patch":{"tags":["Numbers"],"operationId":"updateNumber","summary":"Update a number","description":"Supply at least one of `inboundEnabled` or `inboundWebhookUrl`; an empty patch is rejected. Setting `inboundWebhookUrl` to `null` clears the override and returns the number to the account default, which is the only way a PATCH can express \"remove this\".","parameters":[{"name":"phoneNumber","in":"path","required":true,"description":"The number in E.164.","schema":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"inboundEnabled":{"type":"boolean"},"inboundWebhookUrl":{"anyOf":[{"type":"string","maxLength":2000,"pattern":"^https:\\/\\/"},{"type":"null"}]}}}}}},"responses":{"200":{"description":"The updated number.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"phoneNumber":{"type":"string"},"capabilities":{"type":"array","items":{"type":"string","enum":["voice","sms"]}},"country":{"anyOf":[{"type":"string"},{"type":"null"}]},"inboundEnabled":{"type":"boolean"},"inboundWebhookUrl":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Overrides the account webhook for this number when set."},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","phoneNumber","capabilities","country","inboundEnabled","inboundWebhookUrl","createdAt","updatedAt"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/consent":{"get":{"tags":["Consent"],"operationId":"listConsent","summary":"List consent records","description":"The current state for every number this account knows.","parameters":[{"name":"state","in":"query","required":false,"schema":{"type":"string","enum":["opted_in","opted_out"]}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` from the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many records to return, from 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"A page of consent records.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"phoneNumber":{"type":"string"},"state":{"type":"string","enum":["opted_in","opted_out"]},"source":{"type":"string","enum":["keyword","web","api","operator"]},"keyword":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["phoneNumber","state","source","keyword","createdAt","updatedAt"],"additionalProperties":false}},"hasMore":{"type":"boolean"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass as `cursor` for the next page. Null on the last page."}},"required":["data","hasMore","nextCursor"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}},"post":{"tags":["Consent"],"operationId":"recordConsent","summary":"Record consent","description":"Returns `200`, not `201`: there is one record per number and this either created or replaced it, so a caller cannot read `201` as \"was not already known\".\n\nSupply `ipAddress` and `consentText` for an opt-in. An opt-out never needs justifying, but an opt-in without evidence is an assertion with nothing behind it — which is what a carrier will ask you to produce.\n\nConsent recorded here never overrides a STOP received by keyword; a handset always wins.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"phoneNumber":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","description":"An E.164 phone number, including the leading plus."},"state":{"type":"string","enum":["opted_in","opted_out"]},"ipAddress":{"type":"string","maxLength":64},"consentText":{"type":"string","maxLength":2000}},"required":["phoneNumber","state"]}}}},"responses":{"200":{"description":"The consent record.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"phoneNumber":{"type":"string"},"state":{"type":"string","enum":["opted_in","opted_out"]},"source":{"type":"string","enum":["keyword","web","api","operator"]},"keyword":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["phoneNumber","state","source","keyword","createdAt","updatedAt"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/consent/{phoneNumber}":{"get":{"tags":["Consent"],"operationId":"getConsentHistory","summary":"Consent history for a number","description":"Every change, newest first, with what caused it. This is the answer to a carrier asking why a number was messaged.","parameters":[{"name":"phoneNumber","in":"path","required":true,"description":"The number in E.164.","schema":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$"}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` from the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many records to return, from 1 to 200.","schema":{"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"The number's current consent state and a page of the events that produced it.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"phoneNumber":{"type":"string"},"state":{"type":"string","enum":["opted_in","opted_out"]},"source":{"type":"string","enum":["keyword","web","api","operator"]},"keyword":{"anyOf":[{"type":"string"},{"type":"null"}]},"ipAddress":{"anyOf":[{"type":"string"},{"type":"null"}]},"consentText":{"anyOf":[{"type":"string"},{"type":"null"}]},"messageId":{"anyOf":[{"type":"string"},{"type":"null"}]},"occurredAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["id","phoneNumber","state","source","keyword","ipAddress","consentText","messageId","occurredAt"],"additionalProperties":false}},"hasMore":{"type":"boolean"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass as `cursor` for the next page. Null on the last page."},"phoneNumber":{"type":"string"},"state":{"anyOf":[{"type":"string","enum":["opted_in","opted_out"]},{"type":"null"}]},"record":{"anyOf":[{"type":"object","properties":{"phoneNumber":{"type":"string"},"state":{"type":"string","enum":["opted_in","opted_out"]},"source":{"type":"string","enum":["keyword","web","api","operator"]},"keyword":{"anyOf":[{"type":"string"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"}},"required":["phoneNumber","state","source","keyword","createdAt","updatedAt"],"additionalProperties":false},{"type":"null"}]}},"required":["data","hasMore","nextCursor","phoneNumber","state","record"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}},"/v1/webhook_deliveries":{"get":{"tags":["Webhooks"],"operationId":"listWebhookDeliveries","summary":"List webhook deliveries","description":"What was sent to your endpoint, and what it answered. Each record carries the full attempt log, because the question this answers is \"why did my endpoint not get this\", and the answer is always in the attempts.","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending","succeeded","failed"]}},{"name":"eventType","in":"query","required":false,"schema":{"type":"string","maxLength":64}},{"name":"cursor","in":"query","required":false,"description":"The `nextCursor` from the previous page.","schema":{"type":"string"}},{"name":"limit","in":"query","required":false,"description":"How many records to return, from 1 to 100.","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"A page of deliveries.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"eventId":{"type":"string"},"eventType":{"type":"string"},"url":{"type":"string"},"status":{"type":"string","enum":["pending","succeeded","failed"]},"attempts":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"responseStatus":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"error":{"anyOf":[{"type":"string"},{"type":"null"}]},"deliveredAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},{"type":"null"}]},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"attemptLog":{"type":"array","items":{"type":"object","properties":{"attempt":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"},"responseStatus":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]},"error":{"anyOf":[{"type":"string"},{"type":"null"}]},"durationMs":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["attempt","at","responseStatus","error","durationMs"],"additionalProperties":false}}},"required":["id","eventId","eventType","url","status","attempts","responseStatus","error","deliveredAt","createdAt","updatedAt","attemptLog"],"additionalProperties":false}},"hasMore":{"type":"boolean"},"nextCursor":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Pass as `cursor` for the next page. Null on the last page."}},"required":["data","hasMore","nextCursor"],"additionalProperties":false}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"422":{"$ref":"#/components/responses/InvalidRequest"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/InternalError"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"An API key issued by `POST /v1/api-keys`."}},"responses":{"Unauthorized":{"description":"The key is missing, malformed, revoked, or its account is suspended.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["unauthorized","forbidden","not_found","invalid_request","conflict","recipient_opted_out","rate_limited","service_unavailable","internal_error"]},"message":{"type":"string","description":"For humans. May change without notice; match on `type`."},"details":{"description":"Present when the failure is attributable to input fields.","type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"],"additionalProperties":false}}},"required":["type","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}},"Forbidden":{"description":"Permitted to authenticate but not to do this — including `recipient_opted_out`, which is final.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["unauthorized","forbidden","not_found","invalid_request","conflict","recipient_opted_out","rate_limited","service_unavailable","internal_error"]},"message":{"type":"string","description":"For humans. May change without notice; match on `type`."},"details":{"description":"Present when the failure is attributable to input fields.","type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"],"additionalProperties":false}}},"required":["type","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}},"NotFound":{"description":"No such resource, or it belongs to another account. The two are deliberately indistinguishable.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["unauthorized","forbidden","not_found","invalid_request","conflict","recipient_opted_out","rate_limited","service_unavailable","internal_error"]},"message":{"type":"string","description":"For humans. May change without notice; match on `type`."},"details":{"description":"Present when the failure is attributable to input fields.","type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"],"additionalProperties":false}}},"required":["type","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}},"InvalidRequest":{"description":"The request failed validation. `details` names the fields.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["unauthorized","forbidden","not_found","invalid_request","conflict","recipient_opted_out","rate_limited","service_unavailable","internal_error"]},"message":{"type":"string","description":"For humans. May change without notice; match on `type`."},"details":{"description":"Present when the failure is attributable to input fields.","type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"],"additionalProperties":false}}},"required":["type","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}},"RateLimited":{"description":"Too many requests. Wait for `Retry-After` seconds.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}},"RateLimit-Limit":{"description":"Requests permitted in the current window.","schema":{"type":"integer"}},"RateLimit-Remaining":{"description":"Requests still available.","schema":{"type":"integer"}},"RateLimit-Reset":{"description":"Seconds until the bucket refills.","schema":{"type":"integer"}},"Retry-After":{"description":"Seconds to wait before retrying.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["unauthorized","forbidden","not_found","invalid_request","conflict","recipient_opted_out","rate_limited","service_unavailable","internal_error"]},"message":{"type":"string","description":"For humans. May change without notice; match on `type`."},"details":{"description":"Present when the failure is attributable to input fields.","type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"],"additionalProperties":false}}},"required":["type","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}},"InternalError":{"description":"Something failed on our side. The correlation id identifies it in our logs.","headers":{"X-Correlation-Id":{"description":"Identifies this request in the platform logs. Quote it in a support report and the exact request can be found.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["unauthorized","forbidden","not_found","invalid_request","conflict","recipient_opted_out","rate_limited","service_unavailable","internal_error"]},"message":{"type":"string","description":"For humans. May change without notice; match on `type`."},"details":{"description":"Present when the failure is attributable to input fields.","type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"message":{"type":"string"}},"required":["field","message"],"additionalProperties":false}}},"required":["type","message"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}}}}},"security":[{"bearerAuth":[]}]}