verifywith

API documentation

Everything this website does, your code or an AI agent can do with an API key: get a number, wait for its text, extend it or cancel it. Create a key on the Developers page.

Base URLhttps://api.verifywith.io/v1

Get started

Three calls take you from a key to a code. Put your key in VERIFYWITH_API_KEY first.

1. Get a number

Buys a number for a day with your saved card, within the key’s limits. Keep the Idempotency-Key: sending the same request with it again never buys twice. The response has the number in number.phoneNumber and its id in number.id. If it answers 202 with order.nextAction, the card holder must confirm with their bank at that link; then send the same request again.

Request
curl -X POST https://api.verifywith.io/v1/numbers \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"duration": "day"}'

2. Wait for the code

Waits for the next text (timeoutSeconds, 60 by default, at most 90) and answers with it and the code found in it (code). On "status": "timeout", call again; pass the last message id you saw as afterMessageId so a text is never missed.

Request
curl "https://api.verifywith.io/v1/numbers/NUMBER_ID/messages/next?timeoutSeconds=60" \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"

3. Cancel it, if no text came

In its first 24 hours and before any text, a number can be cancelled; when our provider accepts, everything paid for it comes back. When it refuses (not_cancellable), the number stays yours until it ends.

Request
curl -X POST https://api.verifywith.io/v1/numbers/NUMBER_ID/cancel \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"

API keys and limits

Create a key on the Developers page and send it as a bearer token. The whole key is shown once, when you create it. Use one key per program, so you can revoke one without stopping the others.

What a key may do

Read only
Lists numbers and reads their texts and codes. It can’t change or buy anything.
Manage
Also renames numbers, turns auto-renew on or off, cancels, and manages webhooks.
Full access
Also gets new numbers and extends them, charged to your saved card within the key’s limits.

A full-access key has two spending limits you set: the most it may pay for one number, and the most it may spend in a day. A request over either limit is refused with spending_limit_exceeded and nothing is charged. Change the limits or revoke the key at any time; both apply from its next request.

Every request
curl https://api.verifywith.io/v1/numbers \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"

Connect an AI agent

AI agents connect over MCP (Model Context Protocol). Most sign in through the browser: the app opens this website, you choose what it may do and spend, and it appears on the Developers page beside your keys, where you change its limits or disconnect it.

Programs that can’t sign in use an API key sent as Authorization: Bearer sk_…. Either way the level and spending limits apply to everything the agent does, and revoking stops it at once.

MCP server
https://api.verifywith.io/mcp

Claude, on the web and in the desktop app

Customize, then Connectors, then Add custom connector, with the server address. Claude opens this site to sign in.

ChatGPT

Plugins, then Add custom MCP server, with the server address and OAuth. ChatGPT opens this site to sign in.

Claude Code

Add the server, then run /mcp in Claude Code (or claude mcp login verifywith) to sign in.

Terminal
claude mcp add --transport http verifywith https://api.verifywith.io/mcp

Cursor

In ~/.cursor/mcp.json, or .cursor/mcp.json in a project, then sign in from Cursor’s MCP settings.

mcp.json
{
  "mcpServers": {
    "verifywith": {
      "url": "https://api.verifywith.io/mcp"
    }
  }
}

Your own code, with a key

Any MCP client that can send an Authorization: Bearer sk_… header. In Claude Code, for example.

Terminal
claude mcp add --transport http verifywith https://api.verifywith.io/mcp \
  --header "Authorization: Bearer sk_..."

The tools an agent gets

list_prices
The durations and prices, and this connection’s level and limits.
buy_number
Buys a number. A repeated identical call resumes the same purchase instead of buying again.
wait_for_code
Waits up to 55 seconds for the next text, and answers with it and its code.
list_numbers
The person’s numbers.
get_number_details
One number, including whether and until when it can be cancelled.
read_messages
The texts on a number, newest first.
extend_number
Adds time, charged at once.
set_auto_renew
Turns renewing on or off.
cancel_number
Cancels, with the money back when our provider accepts.

When the bank wants to confirm a payment, or there is no card to pay with, the agent gets a link for its person and continues once they have done it.

Receive webhooks

Instead of waiting on a request, your server can be told. Add an endpoint (an https address) on the Developers page or with the API, and choose the events it wants. We send each one as a JSON POST. Answer with any 2xx within 10 seconds.

Events

message.received
A text arrives. Carries the message, with its code when one was found.
number.active
A number is ready to use.
number.failed
We couldn’t get a number.
number.renewed
A number renewed.
number.renewal_failed
A renewal payment failed.
number.expired
A number ended.
number.cancelled
A number was cancelled.
payment.action_required
The bank wants the card holder to confirm a payment.
What we sendPOST
{
  "id": "evt_msg_665f1c2a9b3e4d0012a7c3f2",
  "type": "message.received",
  "createdAt": "2026-11-12T10:00:00.000Z",
  "data": {
    "number": {
      "id": "665f1c2a9b3e4d0012a7c3f1",
      "phoneNumber": "+16285550113",
      "…": "the whole Number object"
    },
    "message": {
      "id": "665f1c2a9b3e4d0012a7c3f2",
      "sender": "WhatsApp",
      "body": "Your WhatsApp code is 482-913.",
      "code": "482913"
    }
  }
}

Check that it came from us

Requests follow the Standard Webhooks spec, so its libraries work as they are. Each request carries webhook-id, webhook-timestamp and webhook-signature. The signature is v1, then the base64 HMAC-SHA256 of id.timestamp.body, using the raw body and the endpoint’s signing secret (the part after whsec_, base64-decoded). The secret is shown once, when you add the endpoint.

Refuse a request whose timestamp is more than a few minutes old, and use webhook-id to ignore an event you have already handled.

Check the signatureNode.js
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the request body exactly as received
export function cameFromUs(headers, rawBody, secret) {
  const key = Buffer.from(secret.replace("whsec_", ""), "base64");
  const signed = `${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${rawBody}`;
  const ours = createHmac("sha256", key).update(signed).digest("base64");
  return headers["webhook-signature"].split(" ").some((part) => {
    const theirs = Buffer.from(part.replace("v1,", ""));
    return theirs.length === ours.length &&
      timingSafeEqual(theirs, Buffer.from(ours));
  });
}

When your server doesn’t answer

We try up to eight times over about a day, waiting longer each time. Every attempt and what your server answered is listed under the endpoint on the Developers page, where you can send an event again. An endpoint that keeps failing is turned off.

How the API works

Private, receive-only US phone numbers for verification texts. Everything the website does, your code or agent can do with an API key.

Authenticate with Authorization: Bearer sk_... (create keys on the Developers page). Key levels are cumulative: read; manage (rename, auto-renew, cancel, webhooks); buy (get and extend numbers, within the key's spending limits). Every buying call needs an Idempotency-Key header. Lists are paged with limit and cursor (pass nextCursor). Every error is { "error": code, "message": sentence, "requestId": id, "url"?: link }; url is where the person must act in a browser. Money is integer cents in USD; dates are ISO 8601 in UTC; ids are ours (24 hex characters).

The machine-readable description (OpenAPI 3.1), for generating a client, is at https://api.verifywith.io/v1/openapi.json.

Every request
curl https://api.verifywith.io/v1/numbers \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Every error4xx or 5xx
{
  "error": "not_found",
  "message": "There is no number with that id.",
  "requestId": "req_7f3a2c1e"
}

Numbers

List durations and prices

GET/v1/prices

The four durations on sale and what each costs now. No sign-in needed.

No key needed.

Returns

200
Prices in force now, shortest first: a page of Price

Errors

invalid_requestrate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/prices
Response200
{
  "object": "list",
  "data": [
    {
      "object": "price",
      "id": "665f1c2a9b3e4d0012a7c3f1",
      "duration": "day",
      "label": "string",
      "amount": 1299,
      "currency": "usd",
      "version": 0,
      "activeFrom": "2026-11-12T10:00:00.000Z",
      "renewable": false
    }
  ]
}

Get a number

POST/v1/numbers

Buys a private US number for the chosen duration. With an API key, the saved card is charged (held until the first text) within the key's spending limits. 201: paid and ordered; the number is active, or provisioning for a few seconds. 202 with order.nextAction: the bank wants the card holder to confirm; hand them the link, then repeat this request with the same Idempotency-Key. 202 with checkout (website only): mount the embedded payment form, then call confirm. Repeating a request with the same Idempotency-Key never buys twice.

Needs a full-access key. Charged within the key’s spending limits.

Parameters

Idempotency-Keyheader
string, 8 to 255 charactersAny unique string (8 to 255 printable characters) you choose per purchase. Repeating a request with the same key returns the same purchase instead of buying again, for 24 hours. Reusing a key for a different request answers idempotency_key_reused.

Body

duration
one of day, week, month, year
autoRenewoptional
boolean, default false
nicknameoptional
string, 1 to 100 characters
cardConsentTextoptional
string, 10 to 2000 characters

Returns

201
Paid and ordered
202
The person must act first (see checkout and order.nextAction)
Fields in the response
order
Order
number
Number
checkout
object or null

Errors

invalid_requestunauthenticatedcard_requiredpayment_declinedaccount_suspendedinsufficient_scopeidempotency_key_reusedidempotency_in_progressspending_limit_exceededauto_renew_unavailablerate_limitedinternal_errorsupplier_unavailablesales_paused

Request
curl -X POST https://api.verifywith.io/v1/numbers \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"duration":"day"}'
Response201
{
  "order": {
    "object": "order",
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "status": "pending_payment",
    "duration": "day",
    "amount": 1299,
    "currency": "usd",
    "autoRenew": false,
    "numberId": "665f1c2a9b3e4d0012a7c3f1",
    "paymentId": "665f1c2a9b3e4d0012a7c3f1",
    "source": "web",
    "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
    "failure": {
      "code": "482913",
      "message": "What went wrong, in a sentence a person can read."
    },
    "nextAction": {
      "type": "confirm_payment",
      "url": "https://your-app.example/hooks"
    },
    "createdAt": "2026-11-12T10:00:00.000Z",
    "completedAt": "2026-11-12T10:00:00.000Z"
  },
  "number": {
    "object": "number",
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "phoneNumber": "+16285550113",
    "displayNumber": "(628) 555-0113",
    "status": "active",
    "state": "waiting_for_text",
    "duration": "day",
    "startsAt": "2026-11-12T10:00:00.000Z",
    "endsAt": "2026-11-13T10:00:00.000Z",
    "autoRenew": false,
    "renewDuration": "day",
    "renewalAmount": 1299,
    "currency": "usd",
    "nickname": "Work chat",
    "messageCount": 1,
    "firstMessageAt": "2026-11-12T10:00:00.000Z",
    "lastMessageAt": "2026-11-12T10:00:00.000Z",
    "latestCode": "482913",
    "cancellableFrom": "2026-11-12T10:00:00.000Z",
    "cancellableUntil": "2026-11-13T10:00:00.000Z",
    "lastCheckedAt": "2026-11-12T10:00:00.000Z",
    "services": [
      "string"
    ],
    "endedAt": "2026-11-12T10:00:00.000Z",
    "endReason": "expired",
    "source": "web",
    "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
    "createdAt": "2026-11-12T10:00:00.000Z"
  },
  "checkout": {
    "clientSecret": "string",
    "expiresAt": "2026-11-13T10:00:00.000Z"
  }
}

List numbers

GET/v1/numbers

Newest first. Numbers whose payment never went through are left out.

Any key can call this.

Parameters

limitqueryoptional
integer, 1 to 100
cursorqueryoptional
string, 1 to 512 characters
livequeryoptional
one of true, false

Returns

200
A page of numbers: a page of Number

Errors

invalid_requestunauthenticatedinsufficient_scoperate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/numbers \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "list",
  "data": [
    {
      "object": "number",
      "id": "665f1c2a9b3e4d0012a7c3f1",
      "phoneNumber": "+16285550113",
      "displayNumber": "(628) 555-0113",
      "status": "active",
      "state": "waiting_for_text",
      "duration": "day",
      "startsAt": "2026-11-12T10:00:00.000Z",
      "endsAt": "2026-11-13T10:00:00.000Z",
      "autoRenew": false,
      "renewDuration": "day",
      "renewalAmount": 1299,
      "currency": "usd",
      "nickname": "Work chat",
      "messageCount": 1,
      "firstMessageAt": "2026-11-12T10:00:00.000Z",
      "lastMessageAt": "2026-11-12T10:00:00.000Z",
      "latestCode": "482913",
      "cancellableFrom": "2026-11-12T10:00:00.000Z",
      "cancellableUntil": "2026-11-13T10:00:00.000Z",
      "lastCheckedAt": "2026-11-12T10:00:00.000Z",
      "services": [
        "string"
      ],
      "endedAt": "2026-11-12T10:00:00.000Z",
      "endReason": "expired",
      "source": "web",
      "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
      "createdAt": "2026-11-12T10:00:00.000Z"
    }
  ],
  "nextCursor": null
}

Continue a purchase after the payment step

POST/v1/orders/{orderId}/confirm

After the embedded payment form finished in the browser, or after the person confirmed with their bank, this moves the order on and answers where it stands, like getting the number did. Safe to call more than once.

Needs a full-access key. Charged within the key’s spending limits.

Parameters

orderIdin the path
string

Returns

201
Paid and ordered
202
Still waiting for the person
Fields in the response
order
Order
number
Number
checkout
object or null

Errors

invalid_requestunauthenticatedpayment_declinedcard_requiredinsufficient_scopenot_foundrate_limitedinternal_errorsupplier_unavailable

Request
curl -X POST https://api.verifywith.io/v1/orders/ORDER_ID/confirm \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response201
{
  "order": {
    "object": "order",
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "status": "pending_payment",
    "duration": "day",
    "amount": 1299,
    "currency": "usd",
    "autoRenew": false,
    "numberId": "665f1c2a9b3e4d0012a7c3f1",
    "paymentId": "665f1c2a9b3e4d0012a7c3f1",
    "source": "web",
    "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
    "failure": {
      "code": "482913",
      "message": "What went wrong, in a sentence a person can read."
    },
    "nextAction": {
      "type": "confirm_payment",
      "url": "https://your-app.example/hooks"
    },
    "createdAt": "2026-11-12T10:00:00.000Z",
    "completedAt": "2026-11-12T10:00:00.000Z"
  },
  "number": {
    "object": "number",
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "phoneNumber": "+16285550113",
    "displayNumber": "(628) 555-0113",
    "status": "active",
    "state": "waiting_for_text",
    "duration": "day",
    "startsAt": "2026-11-12T10:00:00.000Z",
    "endsAt": "2026-11-13T10:00:00.000Z",
    "autoRenew": false,
    "renewDuration": "day",
    "renewalAmount": 1299,
    "currency": "usd",
    "nickname": "Work chat",
    "messageCount": 1,
    "firstMessageAt": "2026-11-12T10:00:00.000Z",
    "lastMessageAt": "2026-11-12T10:00:00.000Z",
    "latestCode": "482913",
    "cancellableFrom": "2026-11-12T10:00:00.000Z",
    "cancellableUntil": "2026-11-13T10:00:00.000Z",
    "lastCheckedAt": "2026-11-12T10:00:00.000Z",
    "services": [
      "string"
    ],
    "endedAt": "2026-11-12T10:00:00.000Z",
    "endReason": "expired",
    "source": "web",
    "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
    "createdAt": "2026-11-12T10:00:00.000Z"
  },
  "checkout": {
    "clientSecret": "string",
    "expiresAt": "2026-11-13T10:00:00.000Z"
  }
}

Get a number

GET/v1/numbers/{numberId}

Any key can call this.

Parameters

numberIdin the path
string

Returns

200
The number: Number

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundrate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/numbers/NUMBER_ID \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "number",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "phoneNumber": "+16285550113",
  "displayNumber": "(628) 555-0113",
  "status": "active",
  "state": "waiting_for_text",
  "duration": "day",
  "startsAt": "2026-11-12T10:00:00.000Z",
  "endsAt": "2026-11-13T10:00:00.000Z",
  "autoRenew": false,
  "renewDuration": "day",
  "renewalAmount": 1299,
  "currency": "usd",
  "nickname": "Work chat",
  "messageCount": 1,
  "firstMessageAt": "2026-11-12T10:00:00.000Z",
  "lastMessageAt": "2026-11-12T10:00:00.000Z",
  "latestCode": "482913",
  "cancellableFrom": "2026-11-12T10:00:00.000Z",
  "cancellableUntil": "2026-11-13T10:00:00.000Z",
  "lastCheckedAt": "2026-11-12T10:00:00.000Z",
  "services": [
    "string"
  ],
  "endedAt": "2026-11-12T10:00:00.000Z",
  "endReason": "expired",
  "source": "web",
  "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
  "createdAt": "2026-11-12T10:00:00.000Z"
}

Rename a number

PATCH/v1/numbers/{numberId}

Set a nickname, or null to remove it.

Needs a key that can manage numbers.

Parameters

numberIdin the path
string

Body

nickname
string or null

Returns

200
The number: Number

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundrate_limitedinternal_error

Request
curl -X PATCH https://api.verifywith.io/v1/numbers/NUMBER_ID \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"nickname":"Work chat"}'
Response200
{
  "object": "number",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "phoneNumber": "+16285550113",
  "displayNumber": "(628) 555-0113",
  "status": "active",
  "state": "waiting_for_text",
  "duration": "day",
  "startsAt": "2026-11-12T10:00:00.000Z",
  "endsAt": "2026-11-13T10:00:00.000Z",
  "autoRenew": false,
  "renewDuration": "day",
  "renewalAmount": 1299,
  "currency": "usd",
  "nickname": "Work chat",
  "messageCount": 1,
  "firstMessageAt": "2026-11-12T10:00:00.000Z",
  "lastMessageAt": "2026-11-12T10:00:00.000Z",
  "latestCode": "482913",
  "cancellableFrom": "2026-11-12T10:00:00.000Z",
  "cancellableUntil": "2026-11-13T10:00:00.000Z",
  "lastCheckedAt": "2026-11-12T10:00:00.000Z",
  "services": [
    "string"
  ],
  "endedAt": "2026-11-12T10:00:00.000Z",
  "endReason": "expired",
  "source": "web",
  "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
  "createdAt": "2026-11-12T10:00:00.000Z"
}

Turn auto-renew on or off

PUT/v1/numbers/{numberId}/auto-renew

Renewals repeat the duration bought at the price it was bought at. Choosing a different duration resets that locked price to today's price for it. Renewals never count against an API key's spending limits.

Needs a key that can manage numbers.

Parameters

numberIdin the path
string

Body

autoRenew
boolean
durationoptional
one of day, week, month, year

Returns

200
The number: Number

Errors

invalid_requestunauthenticatedcard_requiredinsufficient_scopenot_foundnumber_not_activenumber_busyauto_renew_unavailablerate_limitedinternal_error

Request
curl -X PUT https://api.verifywith.io/v1/numbers/NUMBER_ID/auto-renew \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"autoRenew":false}'
Response200
{
  "object": "number",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "phoneNumber": "+16285550113",
  "displayNumber": "(628) 555-0113",
  "status": "active",
  "state": "waiting_for_text",
  "duration": "day",
  "startsAt": "2026-11-12T10:00:00.000Z",
  "endsAt": "2026-11-13T10:00:00.000Z",
  "autoRenew": false,
  "renewDuration": "day",
  "renewalAmount": 1299,
  "currency": "usd",
  "nickname": "Work chat",
  "messageCount": 1,
  "firstMessageAt": "2026-11-12T10:00:00.000Z",
  "lastMessageAt": "2026-11-12T10:00:00.000Z",
  "latestCode": "482913",
  "cancellableFrom": "2026-11-12T10:00:00.000Z",
  "cancellableUntil": "2026-11-13T10:00:00.000Z",
  "lastCheckedAt": "2026-11-12T10:00:00.000Z",
  "services": [
    "string"
  ],
  "endedAt": "2026-11-12T10:00:00.000Z",
  "endReason": "expired",
  "source": "web",
  "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
  "createdAt": "2026-11-12T10:00:00.000Z"
}

Extend a number

POST/v1/numbers/{numberId}/extend

Adds the duration to the number's end date and charges the saved card at once (within the key's spending limits). 202 requires_action: hand the person nextAction.url, then repeat with the same Idempotency-Key.

Needs a full-access key. Charged within the key’s spending limits.

Parameters

numberIdin the path
string
Idempotency-Keyheader
string, 8 to 255 charactersAny unique string (8 to 255 printable characters) you choose per purchase. Repeating a request with the same key returns the same purchase instead of buying again, for 24 hours. Reusing a key for a different request answers idempotency_key_reused.

Body

duration
one of day, week, month, year

Returns

200
Extended
202
Paid and being applied, or the person must confirm
Fields in the response
object
"extension"
status
one of extended, pending, requires_action
number
Number
payment
Payment
nextAction
NextAction or null
clientSecret
string or null
message
string or null

Errors

invalid_requestunauthenticatedcard_requiredpayment_declinedinsufficient_scopenot_foundnumber_not_activenumber_busyidempotency_key_reusedspending_limit_exceededrate_limitedinternal_errorsupplier_unavailable

Request
curl -X POST https://api.verifywith.io/v1/numbers/NUMBER_ID/extend \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"duration":"day"}'
Response200
{
  "object": "extension",
  "status": "extended",
  "number": {
    "object": "number",
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "phoneNumber": "+16285550113",
    "displayNumber": "(628) 555-0113",
    "status": "active",
    "state": "waiting_for_text",
    "duration": "day",
    "startsAt": "2026-11-12T10:00:00.000Z",
    "endsAt": "2026-11-13T10:00:00.000Z",
    "autoRenew": false,
    "renewDuration": "day",
    "renewalAmount": 1299,
    "currency": "usd",
    "nickname": "Work chat",
    "messageCount": 1,
    "firstMessageAt": "2026-11-12T10:00:00.000Z",
    "lastMessageAt": "2026-11-12T10:00:00.000Z",
    "latestCode": "482913",
    "cancellableFrom": "2026-11-12T10:00:00.000Z",
    "cancellableUntil": "2026-11-13T10:00:00.000Z",
    "lastCheckedAt": "2026-11-12T10:00:00.000Z",
    "services": [
      "string"
    ],
    "endedAt": "2026-11-12T10:00:00.000Z",
    "endReason": "expired",
    "source": "web",
    "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
    "createdAt": "2026-11-12T10:00:00.000Z"
  },
  "payment": {
    "object": "payment",
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "kind": "purchase",
    "status": "succeeded",
    "amount": 1299,
    "refundedAmount": 1299,
    "currency": "usd",
    "duration": "day",
    "card": {
      "brand": "visa",
      "last4": "4242",
      "expMonth": 4,
      "expYear": 2030
    },
    "receiptUrl": "https://example.com",
    "numberId": "665f1c2a9b3e4d0012a7c3f1",
    "orderId": "665f1c2a9b3e4d0012a7c3f1",
    "createdAt": "2026-11-12T10:00:00.000Z"
  },
  "nextAction": {
    "type": "confirm_payment",
    "url": "https://your-app.example/hooks"
  },
  "clientSecret": "string",
  "message": "What went wrong, in a sentence a person can read."
}

Check for new texts now

POST/v1/numbers/{numberId}/messages/check

Asks the carrier for this number's texts now and stores any that were missed. Texts normally arrive by themselves within seconds; this is for a person (or an agent) who would rather not wait. One number is asked at most once every 15 seconds: a call in between answers at once with asked: false. Each account gets 12 checks a minute; beyond that, rate_limited with a Retry-After.

Any key can call this.

Parameters

numberIdin the path
string

Returns

200
What the check found
Fields in the response
object
"inbox_check"
asked
boolean
found
integer
nextCheckAt
string

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundnumber_not_activerate_limitedinternal_error

Request
curl -X POST https://api.verifywith.io/v1/numbers/NUMBER_ID/messages/check \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "inbox_check",
  "asked": false,
  "found": 0,
  "nextCheckAt": "2026-11-12T10:00:00.000Z"
}

Try a declined renewal payment again

POST/v1/numbers/{numberId}/renewal/retry

After a renewal was declined (the number's state is renewal_payment_failed), charges the saved cards again for the same renewal, now, on the same payment, so it is never paid twice. Use it after the card holder fixed or added a card. 202 requires_action: the bank wants the card holder to confirm at nextAction.url. Renewals never count against an API key's spending limits.

Needs a full-access key. Charged within the key’s spending limits.

Parameters

numberIdin the path
string

Returns

200
Renewed
202
Paid and being applied, or the card holder must confirm
Fields in the response
object
"renewal_retry"
status
one of renewed, pending, requires_action
number
Number
paymentId
string or null
nextAction
NextAction or null
message
string or null

Errors

invalid_requestunauthenticatedcard_requiredpayment_declinedinsufficient_scopenot_foundconflictnumber_not_activenumber_busyrate_limitedinternal_errorsupplier_unavailable

Request
curl -X POST https://api.verifywith.io/v1/numbers/NUMBER_ID/renewal/retry \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "renewal_retry",
  "status": "renewed",
  "number": {
    "object": "number",
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "phoneNumber": "+16285550113",
    "displayNumber": "(628) 555-0113",
    "status": "active",
    "state": "waiting_for_text",
    "duration": "day",
    "startsAt": "2026-11-12T10:00:00.000Z",
    "endsAt": "2026-11-13T10:00:00.000Z",
    "autoRenew": false,
    "renewDuration": "day",
    "renewalAmount": 1299,
    "currency": "usd",
    "nickname": "Work chat",
    "messageCount": 1,
    "firstMessageAt": "2026-11-12T10:00:00.000Z",
    "lastMessageAt": "2026-11-12T10:00:00.000Z",
    "latestCode": "482913",
    "cancellableFrom": "2026-11-12T10:00:00.000Z",
    "cancellableUntil": "2026-11-13T10:00:00.000Z",
    "lastCheckedAt": "2026-11-12T10:00:00.000Z",
    "services": [
      "string"
    ],
    "endedAt": "2026-11-12T10:00:00.000Z",
    "endReason": "expired",
    "source": "web",
    "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
    "createdAt": "2026-11-12T10:00:00.000Z"
  },
  "paymentId": "665f1c2a9b3e4d0012a7c3f1",
  "nextAction": {
    "type": "confirm_payment",
    "url": "https://your-app.example/hooks"
  },
  "message": "What went wrong, in a sentence a person can read."
}

Cancel a number (with a refund when eligible)

POST/v1/numbers/{numberId}/cancel

Offered on numbers with no texts, in their first 24 hours, from cancellableFrom. We ask our provider first: when it accepts, everything paid for the number goes back (a hold is released, so it never shows as a charge). When it refuses, the number stays active and paid (not_cancellable).

Needs a key that can manage numbers.

Parameters

numberIdin the path
string

Body

reasonoptional
string, at most 500 characters

Returns

200
Cancelled
202
Our provider has not answered yet; we keep asking
Fields in the response
object
"cancellation"
status
one of cancelled, in_progress
number
Number
refund
object or null
message
string or null

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundnot_cancellablecancel_too_sooncancel_window_closednumber_not_activenumber_busyrate_limitedinternal_error

Request
curl -X POST https://api.verifywith.io/v1/numbers/NUMBER_ID/cancel \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "cancellation",
  "status": "cancelled",
  "number": {
    "object": "number",
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "phoneNumber": "+16285550113",
    "displayNumber": "(628) 555-0113",
    "status": "active",
    "state": "waiting_for_text",
    "duration": "day",
    "startsAt": "2026-11-12T10:00:00.000Z",
    "endsAt": "2026-11-13T10:00:00.000Z",
    "autoRenew": false,
    "renewDuration": "day",
    "renewalAmount": 1299,
    "currency": "usd",
    "nickname": "Work chat",
    "messageCount": 1,
    "firstMessageAt": "2026-11-12T10:00:00.000Z",
    "lastMessageAt": "2026-11-12T10:00:00.000Z",
    "latestCode": "482913",
    "cancellableFrom": "2026-11-12T10:00:00.000Z",
    "cancellableUntil": "2026-11-13T10:00:00.000Z",
    "lastCheckedAt": "2026-11-12T10:00:00.000Z",
    "services": [
      "string"
    ],
    "endedAt": "2026-11-12T10:00:00.000Z",
    "endReason": "expired",
    "source": "web",
    "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
    "createdAt": "2026-11-12T10:00:00.000Z"
  },
  "refund": {
    "status": "returned",
    "amount": 1299,
    "released": false
  },
  "message": "What went wrong, in a sentence a person can read."
}

Messages

List the texts a number received

GET/v1/numbers/{numberId}/messages

Newest first, with the full text and the code found in it.

Any key can call this.

Parameters

numberIdin the path
string
limitqueryoptional
integer, 1 to 100
cursorqueryoptional
string, 1 to 512 characters

Returns

200
A page of messages: a page of Message

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundrate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/numbers/NUMBER_ID/messages \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "list",
  "data": [
    {
      "object": "message",
      "id": "665f1c2a9b3e4d0012a7c3f1",
      "numberId": "665f1c2a9b3e4d0012a7c3f1",
      "sender": "WhatsApp",
      "body": "Your WhatsApp code is 482-913. Don't share it.",
      "code": "482913",
      "receivedAt": "2026-11-12T10:00:00.000Z"
    }
  ],
  "nextCursor": null
}

Wait for the next text and its code

GET/v1/numbers/{numberId}/messages/next

Holds the request open until a text arrives (then answers at once with it and its code) or until timeoutSeconds (at most 90) pass, then answers timeout. Pass the last message id you saw as afterMessageId so a text that arrived between two calls is never missed.

Any key can call this.

Parameters

numberIdin the path
string
timeoutSecondsqueryoptional
integer, 1 to 90
afterMessageIdqueryoptional
string

Returns

200
A text arrived, or the wait timed out
Fields in the response
object
"code_wait"
status
one of received, timeout
message
Message or null
code
string or null

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundnumber_not_activerate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/numbers/NUMBER_ID/messages/next \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "code_wait",
  "status": "received",
  "message": "What went wrong, in a sentence a person can read.",
  "code": "482913"
}

Say someone is looking at this number

POST/v1/numbers/{numberId}/watch

Call about every 30 seconds while a screen shows the number and waits for a text, when not using the live stream's watch. It makes us check with our provider if a text seems late.

Any key can call this.

Parameters

numberIdin the path
string

Returns

204
Noted

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundrate_limitedinternal_error

Request
curl -X POST https://api.verifywith.io/v1/numbers/NUMBER_ID/watch \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"

Account

Who is calling

GET/v1/me

The account, and either the website session or the API key in use.

Needs an API key.

Returns

200
The caller
Fields in the response
object
"me"
user
object
session
object or null
apiKey
object or null

Errors

invalid_requestunauthenticatedrate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/me \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "me",
  "user": {
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "email": "[email protected]",
    "emailVerified": false,
    "name": "Laptop scripts",
    "createdAt": "2026-11-12T10:00:00.000Z"
  },
  "session": {
    "expiresAt": "2026-11-13T10:00:00.000Z"
  },
  "apiKey": {
    "id": "665f1c2a9b3e4d0012a7c3f1",
    "name": "Laptop scripts",
    "scopes": [
      "read"
    ]
  }
}

Payments

Payment history

GET/v1/payments

Every charge against its number, newest first. Refunds are on the same payment (refundedAmount).

Any key can call this.

Parameters

limitqueryoptional
integer, 1 to 100
cursorqueryoptional
string, 1 to 512 characters
numberIdqueryoptional
string

Returns

200
A page of payments: a page of Payment

Errors

invalid_requestunauthenticatedinsufficient_scoperate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/payments \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "list",
  "data": [
    {
      "object": "payment",
      "id": "665f1c2a9b3e4d0012a7c3f1",
      "kind": "purchase",
      "status": "succeeded",
      "amount": 1299,
      "refundedAmount": 1299,
      "currency": "usd",
      "duration": "day",
      "card": {
        "brand": null,
        "last4": null,
        "expMonth": null,
        "expYear": null
      },
      "receiptUrl": "https://example.com",
      "numberId": "665f1c2a9b3e4d0012a7c3f1",
      "orderId": "665f1c2a9b3e4d0012a7c3f1",
      "createdAt": "2026-11-12T10:00:00.000Z"
    }
  ],
  "nextCursor": null
}

Cards

Saved cards

GET/v1/cards

In charging order: the default first, then backups in the order they are tried. fresh=true re-reads them from the payment processor.

Any key can call this.

Parameters

freshqueryoptional
one of true, false

Returns

200
Cards: a page of Card

Errors

invalid_requestunauthenticatedinsufficient_scoperate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/cards \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "list",
  "data": [
    {
      "brand": "visa",
      "last4": "4242",
      "expMonth": 4,
      "expYear": 2030,
      "object": "card",
      "id": "665f1c2a9b3e4d0012a7c3f1",
      "isDefault": false,
      "expiresBeforeRenewal": false
    }
  ]
}

Webhooks

List webhook endpoints

GET/v1/webhook-endpoints

Any key can call this.

Returns

200
Endpoints, newest first: a page of WebhookEndpoint

Errors

invalid_requestunauthenticatedinsufficient_scoperate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/webhook-endpoints \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "list",
  "data": [
    {
      "object": "webhook_endpoint",
      "id": "665f1c2a9b3e4d0012a7c3f1",
      "url": "https://your-app.example/hooks",
      "description": "string",
      "events": [
        "number.active"
      ],
      "enabled": false,
      "createdAt": "2026-11-12T10:00:00.000Z"
    }
  ]
}

Add a webhook endpoint

POST/v1/webhook-endpoints

We POST each subscribed event to the https URL, signed with the Standard Webhooks spec (webhook-id, webhook-timestamp, webhook-signature), retrying for about a day. The signing secret is shown once.

Needs a key that can manage numbers.

Body

url
URL, at most 2048 characters
events
list of one of number.active, number.failed, number.renewed, number.renewal_failed, number.expired, number.cancelled, message.received, payment.action_required
descriptionoptional
string, at most 200 characters

Returns

201
The endpoint and its signing secret (shown once)
Fields in the response
object
"webhook_endpoint"
id
string
url
URL
description
string or null
events
list of one of number.active, number.failed, number.renewed, number.renewal_failed, number.expired, number.cancelled, message.received, payment.action_required
enabled
boolean
createdAt
date-time
secret
string

Errors

invalid_requestunauthenticatedinsufficient_scopeconflictrate_limitedinternal_error

Request
curl -X POST https://api.verifywith.io/v1/webhook-endpoints \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-app.example/hooks","events":["number.active"]}'
Response201
{
  "object": "webhook_endpoint",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "url": "https://your-app.example/hooks",
  "description": "string",
  "events": [
    "number.active"
  ],
  "enabled": false,
  "createdAt": "2026-11-12T10:00:00.000Z",
  "secret": "string"
}

Get a webhook endpoint

GET/v1/webhook-endpoints/{endpointId}

Any key can call this.

Parameters

endpointIdin the path
string

Returns

200
The endpoint: WebhookEndpoint

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundrate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/webhook-endpoints/ENDPOINT_ID \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "webhook_endpoint",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "url": "https://your-app.example/hooks",
  "description": "string",
  "events": [
    "number.active"
  ],
  "enabled": false,
  "createdAt": "2026-11-12T10:00:00.000Z"
}

Change a webhook endpoint

PATCH/v1/webhook-endpoints/{endpointId}

Turning an endpoint back on (enabled: true) also clears its failure count.

Needs a key that can manage numbers.

Parameters

endpointIdin the path
string

Body

urloptional
URL, at most 2048 characters
eventsoptional
list of one of number.active, number.failed, number.renewed, number.renewal_failed, number.expired, number.cancelled, message.received, payment.action_required
descriptionoptional
string or null
enabledoptional
boolean

Returns

200
The endpoint: WebhookEndpoint

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundrate_limitedinternal_error

Request
curl -X PATCH https://api.verifywith.io/v1/webhook-endpoints/ENDPOINT_ID \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://your-app.example/hooks","events":["number.active"]}'
Response200
{
  "object": "webhook_endpoint",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "url": "https://your-app.example/hooks",
  "description": "string",
  "events": [
    "number.active"
  ],
  "enabled": false,
  "createdAt": "2026-11-12T10:00:00.000Z"
}

Delete a webhook endpoint

DELETE/v1/webhook-endpoints/{endpointId}

Deliveries still waiting are dropped.

Needs a key that can manage numbers.

Parameters

endpointIdin the path
string

Returns

204
Deleted

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundrate_limitedinternal_error

Request
curl -X DELETE https://api.verifywith.io/v1/webhook-endpoints/ENDPOINT_ID \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"

Recent deliveries to an endpoint

GET/v1/webhook-endpoints/{endpointId}/deliveries

Newest first, up to limit (at most 100). Deliveries are kept 7 days.

Any key can call this.

Parameters

endpointIdin the path
string
limitqueryoptional
integer, 1 to 100

Returns

200
Deliveries: a page of WebhookDelivery

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundrate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/webhook-endpoints/ENDPOINT_ID/deliveries \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response200
{
  "object": "list",
  "data": [
    {
      "object": "webhook_delivery",
      "id": "665f1c2a9b3e4d0012a7c3f1",
      "endpointId": "665f1c2a9b3e4d0012a7c3f1",
      "eventId": "665f1c2a9b3e4d0012a7c3f1",
      "eventType": "number.active",
      "status": "succeeded",
      "attempts": 0,
      "responseStatus": 0,
      "error": "string",
      "createdAt": "2026-11-12T10:00:00.000Z",
      "lastAttemptAt": "2026-11-12T10:00:00.000Z",
      "nextAttemptAt": "2026-11-12T10:00:00.000Z",
      "deliveredAt": "2026-11-12T10:00:00.000Z"
    }
  ]
}

Send an event again

POST/v1/webhook-deliveries/{deliveryId}/resend

A new attempt within seconds, with the same webhook-id, so a receiver that already handled it can tell.

Needs a key that can manage numbers.

Parameters

deliveryIdin the path
string

Returns

202
Queued: WebhookDelivery

Errors

invalid_requestunauthenticatedinsufficient_scopenot_foundconflictrate_limitedinternal_error

Request
curl -X POST https://api.verifywith.io/v1/webhook-deliveries/DELIVERY_ID/resend \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"
Response202
{
  "object": "webhook_delivery",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "endpointId": "665f1c2a9b3e4d0012a7c3f1",
  "eventId": "665f1c2a9b3e4d0012a7c3f1",
  "eventType": "number.active",
  "status": "succeeded",
  "attempts": 0,
  "responseStatus": 0,
  "error": "string",
  "createdAt": "2026-11-12T10:00:00.000Z",
  "lastAttemptAt": "2026-11-12T10:00:00.000Z",
  "nextAttemptAt": "2026-11-12T10:00:00.000Z",
  "deliveredAt": "2026-11-12T10:00:00.000Z"
}

Connected apps

Read an app's request to connect

GET/v1/connect/request

Used by the website's consent page when an AI app connects through the browser (OAuth). request is the query string the sign-in server sent the browser to the page with.

No key needed.

Parameters

requestquery
string, 1 to 8192 characters

Returns

200
Who is asking, and what for
Fields in the response
object
"connect_request"
app
object
maxLevel
one of read, manage, buy
defaultLevel
one of read, manage, buy
defaultLimits
object
expiresAt
date-time

Errors

invalid_requestrequest_expiredrate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/connect/request
Response200
{
  "object": "connect_request",
  "app": {
    "name": "Laptop scripts",
    "clientId": "665f1c2a9b3e4d0012a7c3f1",
    "identifiedBy": "metadata_document",
    "host": "string",
    "verified": false,
    "redirectHost": "string"
  },
  "maxLevel": "read",
  "defaultLevel": "read",
  "defaultLimits": {
    "perNumber": 1299,
    "perDay": 4000
  },
  "expiresAt": "2026-11-13T10:00:00.000Z"
}

Live

Live updates (server-sent events)

GET/v1/stream

One text/event-stream for the whole account: message.received (the number and the text) and number.updated (the number), then ready once caught up. Reconnect with Last-Event-ID and every event since is replayed first; resync means the gap was too long, so re-read your numbers. Comments (: ping) every 25 seconds keep proxies from closing it. At most 10 streams per person.

Any key can call this.

Parameters

watchqueryoptional
string, at most 200 characters
lastEventIdqueryoptional
string, at most 40 characters

Returns

200
An event stream. Each event's data is one of these, its event is the type.

Errors

invalid_requestunauthenticatedinsufficient_scoperate_limitedinternal_error

Request
curl https://api.verifywith.io/v1/stream \
  -H "Authorization: Bearer $VERIFYWITH_API_KEY"

Objects

Price

object
"price"
id
string
duration
one of day, week, month, year
label
string
amount
integer
currency
"usd"
version
integer
activeFrom
date-time
renewable
boolean
Price
{
  "object": "price",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "duration": "day",
  "label": "string",
  "amount": 1299,
  "currency": "usd",
  "version": 0,
  "activeFrom": "2026-11-12T10:00:00.000Z",
  "renewable": false
}

Order

object
"order"
id
string
status
one of pending_payment, submitting, provisioning, reconciling, completed, failed, abandoned
duration
one of day, week, month, year
amount
integer
currency
"usd"
autoRenew
boolean
numberId
string
paymentId
string or null
source
one of web, api, mcp
apiKeyId
string or null
failure
object or null
nextAction
NextAction or null
createdAt
date-time
completedAt
date-time or null
Order
{
  "object": "order",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "status": "pending_payment",
  "duration": "day",
  "amount": 1299,
  "currency": "usd",
  "autoRenew": false,
  "numberId": "665f1c2a9b3e4d0012a7c3f1",
  "paymentId": "665f1c2a9b3e4d0012a7c3f1",
  "source": "web",
  "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
  "failure": {
    "code": "482913",
    "message": "What went wrong, in a sentence a person can read."
  },
  "nextAction": {
    "type": "confirm_payment",
    "url": "https://your-app.example/hooks"
  },
  "createdAt": "2026-11-12T10:00:00.000Z",
  "completedAt": "2026-11-12T10:00:00.000Z"
}

NextAction

type
one of confirm_payment, add_card
url
URL
NextAction
{
  "type": "confirm_payment",
  "url": "https://your-app.example/hooks"
}

Number

object
"number"
id
string
phoneNumber
string or null
displayNumber
string or null
status
one of provisioning, active, cancelling, cancelled, expired, failed
state
one of getting_number, waiting_for_text, code_arrived, more_messages, ending_soon, ended, renewal_payment_failed, cancelled, failed
duration
one of day, week, month, year
startsAt
date-time or null
endsAt
date-time or null
autoRenew
boolean
renewDuration
one of day, week, month, year
renewalAmount
integer
currency
"usd"
nickname
string or null
messageCount
integer
firstMessageAt
date-time or null
lastMessageAt
date-time or null
latestCode
string or null
cancellableFrom
date-time or null
cancellableUntil
date-time or null
lastCheckedAtoptional
date-time or null
servicesoptional
list of string
endedAt
date-time or null
endReason
one of expired, renewal_unpaid, cancelled, cancelled_by_staff, cancelled_by_supplier, supplier_failed, payment_failed or null
source
one of web, api, mcp
apiKeyId
string or null
createdAt
date-time
Number
{
  "object": "number",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "phoneNumber": "+16285550113",
  "displayNumber": "(628) 555-0113",
  "status": "active",
  "state": "waiting_for_text",
  "duration": "day",
  "startsAt": "2026-11-12T10:00:00.000Z",
  "endsAt": "2026-11-13T10:00:00.000Z",
  "autoRenew": false,
  "renewDuration": "day",
  "renewalAmount": 1299,
  "currency": "usd",
  "nickname": "Work chat",
  "messageCount": 1,
  "firstMessageAt": "2026-11-12T10:00:00.000Z",
  "lastMessageAt": "2026-11-12T10:00:00.000Z",
  "latestCode": "482913",
  "cancellableFrom": "2026-11-12T10:00:00.000Z",
  "cancellableUntil": "2026-11-13T10:00:00.000Z",
  "lastCheckedAt": "2026-11-12T10:00:00.000Z",
  "services": [
    "string"
  ],
  "endedAt": "2026-11-12T10:00:00.000Z",
  "endReason": "expired",
  "source": "web",
  "apiKeyId": "665f1c2a9b3e4d0012a7c3f1",
  "createdAt": "2026-11-12T10:00:00.000Z"
}

Payment

object
"payment"
id
string
kind
one of purchase, renewal, extension
status
one of pending, requires_action, processing, authorized, succeeded, failed, canceled, partially_refunded, refunded
amount
integer
refundedAmount
integer
currency
"usd"
duration
one of day, week, month, year
card
CardSummary or null
receiptUrl
URL or null
numberId
string or null
orderId
string or null
createdAt
date-time
Payment
{
  "object": "payment",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "kind": "purchase",
  "status": "succeeded",
  "amount": 1299,
  "refundedAmount": 1299,
  "currency": "usd",
  "duration": "day",
  "card": {
    "brand": "visa",
    "last4": "4242",
    "expMonth": 4,
    "expYear": 2030
  },
  "receiptUrl": "https://example.com",
  "numberId": "665f1c2a9b3e4d0012a7c3f1",
  "orderId": "665f1c2a9b3e4d0012a7c3f1",
  "createdAt": "2026-11-12T10:00:00.000Z"
}

CardSummary

brand
string
last4
string
expMonth
integer, 1 to 12
expYear
integer, 2000 to 2200
CardSummary
{
  "brand": "visa",
  "last4": "4242",
  "expMonth": 4,
  "expYear": 2030
}

Message

object
"message"
id
string
numberId
string
sender
string
body
string
code
string or null
receivedAt
date-time
Message
{
  "object": "message",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "numberId": "665f1c2a9b3e4d0012a7c3f1",
  "sender": "WhatsApp",
  "body": "Your WhatsApp code is 482-913. Don't share it.",
  "code": "482913",
  "receivedAt": "2026-11-12T10:00:00.000Z"
}

Card

brand
string
last4
string
expMonth
integer, 1 to 12
expYear
integer, 2000 to 2200
object
"card"
id
string
isDefault
boolean
expiresBeforeRenewal
boolean
Card
{
  "brand": "visa",
  "last4": "4242",
  "expMonth": 4,
  "expYear": 2030,
  "object": "card",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "isDefault": false,
  "expiresBeforeRenewal": false
}

ApiKey

object
"api_key"
id
string
kind
one of key, connection
name
string
prefix
string
scopes
list of one of read, manage, buy
limits
object
app
object or null
numbersBought
integer
createdAt
date-time
lastUsedAt
date-time or null
ApiKey
{
  "object": "api_key",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "kind": "key",
  "name": "Laptop scripts",
  "prefix": "string",
  "scopes": [
    "read"
  ],
  "limits": {
    "perNumber": 1299,
    "perDay": 4000
  },
  "app": {
    "name": "Laptop scripts",
    "clientId": "665f1c2a9b3e4d0012a7c3f1",
    "identifiedBy": "metadata_document",
    "host": "string",
    "verified": false,
    "redirectHost": "string"
  },
  "numbersBought": 0,
  "createdAt": "2026-11-12T10:00:00.000Z",
  "lastUsedAt": "2026-11-12T10:00:00.000Z"
}

WebhookEndpoint

object
"webhook_endpoint"
id
string
url
URL
description
string or null
events
list of one of number.active, number.failed, number.renewed, number.renewal_failed, number.expired, number.cancelled, message.received, payment.action_required
enabled
boolean
createdAt
date-time
WebhookEndpoint
{
  "object": "webhook_endpoint",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "url": "https://your-app.example/hooks",
  "description": "string",
  "events": [
    "number.active"
  ],
  "enabled": false,
  "createdAt": "2026-11-12T10:00:00.000Z"
}

WebhookDelivery

object
"webhook_delivery"
id
string
endpointId
string
eventId
string
eventType
one of number.active, number.failed, number.renewed, number.renewal_failed, number.expired, number.cancelled, message.received, payment.action_required
status
one of pending, retrying, succeeded, failed
attempts
integer
responseStatus
integer or null
error
string or null
createdAt
date-time
lastAttemptAt
date-time or null
nextAttemptAt
date-time or null
deliveredAt
date-time or null
WebhookDelivery
{
  "object": "webhook_delivery",
  "id": "665f1c2a9b3e4d0012a7c3f1",
  "endpointId": "665f1c2a9b3e4d0012a7c3f1",
  "eventId": "665f1c2a9b3e4d0012a7c3f1",
  "eventType": "number.active",
  "status": "succeeded",
  "attempts": 0,
  "responseStatus": 0,
  "error": "string",
  "createdAt": "2026-11-12T10:00:00.000Z",
  "lastAttemptAt": "2026-11-12T10:00:00.000Z",
  "nextAttemptAt": "2026-11-12T10:00:00.000Z",
  "deliveredAt": "2026-11-12T10:00:00.000Z"
}

Error

error
one of invalid_request, unauthenticated, card_required, payment_declined, forbidden, insufficient_scope, account_suspended, not_found, conflict, card_in_use, request_expired, idempotency_key_reused, idempotency_in_progress, not_cancellable, cancel_too_soon, cancel_window_closed, number_not_active, number_busy, auto_renew_unavailable, spending_limit_exceeded, purchase_limit_exceeded, rate_limited, internal_error, supplier_unavailable, sales_paused
message
string
requestId
string
urloptional
URL
Error
{
  "error": "invalid_request",
  "message": "What went wrong, in a sentence a person can read.",
  "requestId": "req_7f3a2c1e",
  "url": "https://your-app.example/hooks"
}