Skip to content
WbizTool

API v2 (beta)

API v2

API v2 is the new WbizTool REST API: one key per app with only the access it needs, resource-style URLs, real HTTP status codes, signed JSON webhooks and safe retries. This page covers what every request has in common. The resource pages cover each endpoint.

Base URL#

https://wbiztool.com/api/v2

Paths have no trailing slash (/api/v2/messages), but one is accepted too. An address that doesn't exist answers 404 with the code not_found.

Quick example#

Check your key with GET /me. It works with any scope and sends nothing:

curl https://wbiztool.com/api/v2/me \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY"
{
  "workspace": { "id": 5012, "name": "Acme Bakery" },
  "key": {
    "id": 7,
    "name": "Order system",
    "mode": "live",
    "scopes": ["messages:send", "messages:read"],
    "limited_to_numbers": false,
    "number_ids": [],
    "expires_at": null
  },
  "plan": { "name": "Starter" },
  "limits": {
    "requests_per_minute": 60,
    "requests_per_day": 10000,
    "messages_per_number_per_day": 1000,
    "messages_remaining": null
  }
}

messages_remaining is null when the plan sends unlimited messages. plan is null when the workspace has no active plan.

Keys#

Send your key in the Authorization header on every request:

Authorization: Bearer wbz_live_…

There's no client_id: the key names its workspace. A v2 key starts with wbz_live_ or wbz_test_, followed by 38 letters and digits. v1 keys don't work on v2 (they get v1_key_on_v2), and v2 keys don't work on v1.

Make a key#

Owners make keys on Settings → API keys, in the API v2 keys card: click Create v2 key, then choose:

  • Name: the app that will use it, up to 50 characters.
  • Kind of key: Live sends real messages; Test checks everything and sends nothing (see Test mode).
  • What it can do: the scopes, or Everything.
  • Numbers it can use: all numbers, including ones added later, or only some (see Keys limited to some numbers).
  • Expires: never, or in 30 days, 90 days or 1 year.

The key is shown once. Copy it straight away and store it on your server, for example in an environment variable. WbizTool keeps only a hash of it and its last four characters, so a lost key can't be shown again: revoke it and make a new one.

A workspace can have up to 10 v2 keys that aren't revoked. The card shows each key's last four characters, scopes, numbers, expiry, and when and from which IP address it was last used. Revoke stops a key at once, and with it the webhook endpoints it made. A key past its expiry date gets api_key_expired.

Scopes#

Each key can do only what its scopes allow. A request outside them answers 403 with missing_scope, and required_scope in the error says which one it needs.

ScopeWhat it allows
messages:sendSend messages and batches, and cancel waiting messages: POST /messages, POST /messages/batch, DELETE /messages/{id}
messages:readRead messages and their status (GET /messages, GET /messages/{id}), and manage webhook endpoints
remindersCreate, list, change, pause and delete reminders
numbersList numbers, see their connection, and connect WhatsApp numbers
mediaUpload, list and delete files in the Media directory
contactsNumber checks (contact endpoints come later)

GET /me needs no particular scope. Sending to a saved contact with to.contact_id needs only messages:send.

Keys limited to some numbers#

A key limited to some numbers works only with them:

  • It sends only from those numbers, and lists, reads and cancels only their messages. Another number in from gets number_not_allowed.
  • GET /numbers lists only those numbers. It can reconnect them but can't add a new number.
  • It sees and changes only the reminders that send from those numbers, and a new reminder must name one of them as from.
  • It can't manage webhook endpoints at all, because they receive the whole workspace's events.

GET /me shows limited_to_numbers and the number_ids the key may use.

Requests#

  • JSON in and out. Send bodies as Content-Type: application/json. The one exception is uploading a file, which is multipart/form-data. Every answer is JSON, errors included.
  • Field names are snake_case, and unknown fields are refused: a typo such as txt answers validation_failed instead of being ignored.
  • Phone numbers are in international format with + (E.164), for example +919876543210. Spaces and dashes are allowed; a number without + is refused with invalid_phone.
  • Times are ISO 8601 with an offset, for example 2026-10-20T09:30:00+05:30 or 2026-10-20T04:00:00Z. A time without an offset is refused with invalid_datetime. Times in answers are in UTC and end in Z, except a reminder's next_run_at, which is in the reminder's time zone.
  • IDs are integers, such as a number's id (from when you send) or a message's id.
  • Status codes mean what they say: 200 OK, 201 created, 202 accepted (queued), 400 the request is wrong, 401 the key is wrong, 402 no messages left, 403 not allowed, 404 not found, 409 conflict, 413 too large, 415 wrong content type, 422 a value can't be used, 429 too many requests, 5xx our side.

Answers carry Cache-Control: no-store.

Errors#

Every error has the same shape:

{
  "error": {
    "code": "invalid_phone",
    "message": "This is not a valid phone number. Write it in international format with +, e.g. +919876543210.",
    "field": "to.phone",
    "docs": "https://wbiztool.com/docs/api/v2/errors/#invalid_phone",
    "request_id": "req_4f1c2a9b8e7d6c5b4a392817"
  }
}
  • code is stable: branch on it in your code. message is English text for the developer and may change.
  • field names the field the error is about (to.phone, to[3].phone, schedule.cron), or is null.
  • docs links to the code on the errors page.
  • request_id is the same as the X-Request-Id header. Quote it if you contact us.
  • Some errors add details, for example status on not_cancellable or used and limit on storage_full. When a request has several problems, errors lists the others, each with field and message.

Every code, its HTTP status and what to do are on the errors page.

Idempotency-Key#

A network error or a timeout leaves you unsure whether a request worked. Send an Idempotency-Key header with a POST that creates something, and you can safely send it again:

curl -X POST https://wbiztool.com/api/v2/messages \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-ready" \
  -d '{"to": {"phone": "+919876543210"}, "text": "Hi Asha, your order #1042 is ready."}'
  • The value is yours: any unique string of 1 to 255 printable characters, such as a UUID or your own order id. Make a new one for each new request, and reuse it only for a retry of the same request.
  • A retry gets the first answer back with the header Idempotent-Replayed: true. Nothing is done twice: no second message, reminder, file or endpoint.
  • Keys last 24 hours from the first request, and belong to your API key: another API key of the workspace can use the same value without clashing.
  • Same key, different request: a request with a key already used for a different method, path or body answers 422 idempotency_key_reused.
  • Still running: while the first request with the key is still being handled, a retry answers 409 idempotency_in_progress with Retry-After: 1. Wait a moment and send it again.
  • Errors aren't kept. If the first request answered with an error, nothing is stored: fix the request and send it again with the same key.
  • Secrets aren't replayed. A replay of POST /webhooks leaves out the endpoint's secret, which is shown only in the first answer.

It works on POST /messages, POST /messages/batch, POST /numbers, POST /reminders, POST /media, POST /number-checks and POST /webhooks, and is optional on each. Other requests ignore the header. A value that's empty or too long answers 400 invalid_idempotency_key.

Rate limits#

Each key can make 60 requests a minute and 10,000 requests a day. These are fixed windows: the minute window starts on the clock minute, the day window at 00:00 UTC. Over a limit, the request answers 429 rate_limited.

Answers to requests with a valid key carry these headers:

HeaderMeaning
RateLimit-LimitRequests allowed in the current window (60, or 10000 once the day limit is reached)
RateLimit-RemainingRequests left in that window
RateLimit-ResetSeconds until that window starts again
RateLimit-PolicyBoth limits: 60;w=60, 10000;w=86400
Retry-AfterOn a 429: seconds to wait before you try again

Separately from these:

  • Wrong keys: more than 30 requests a minute with a wrong key from one IP address answer 429 too_many_auth_failures with Retry-After: 60.
  • Messages per number: a number sends at most 1,000 messages in 24 hours (fair use). A send over it answers 429 number_daily_cap_reached.

Pagination#

Lists that can grow (GET /messages, /reminders, /media, /number-checks and a check's /results) come in pages, newest first:

{
  "data": [ … ],
  "next_cursor": "eyJpZCI6OTgxNzI2MywidCI6IjIwMjYtMTAtMThUMTA6MTU6MDIuMzExWiIsImYiOiI0NGIxMzZmYTM1NWIifQ"
}
  • limit sets the page size, from 1 to 100 (default 25).
  • To get the next page, send the same request with cursor set to the last page's next_cursor. next_cursor is null on the last page.
  • Keep the other filters the same: a cursor used with different filters answers 400 invalid_cursor. Start again without a cursor.
  • Treat the cursor as an opaque string. Items added while you page don't shift the pages.
curl "https://wbiztool.com/api/v2/messages?limit=100&cursor=eyJpZCI6OTgxNzI2MywidCI6..." \
  -H "Authorization: Bearer $WBIZTOOL_API_KEY"

GET /numbers and GET /webhooks return everything in one data list.

Request ids#

Every answer, errors included, has an X-Request-Id header such as req_4f1c2a9b8e7d6c5b4a392817. Errors carry the same value as request_id. Log it, and quote it if you contact us: it lets us find the request in our logs, which record your key's id but never the key.

If you send your own X-Request-Id (up to 100 printable characters), it comes back as X-Client-Request-Id, so you can match answers to your own logs.

Test mode#

A test key (wbz_test_…) lets you build and test an integration without sending anything. Make one on Settings → API keys with Kind of key: Test. GET /me shows "mode": "test".

What a test key does like a live key:

  • POST /messages and POST /messages/batch check everything a live send checks: the recipient, the text, media, send_at and expires_at, the sending number, and the key's scopes and numbers. The same mistakes get the same errors.
  • Scopes, numbers, rate limits and Idempotency-Key work the same.
  • Every read of the rest of the workspace (GET /me, /numbers, /reminders, /media, /number-checks, /webhooks) shows its real data, read-only.

What's different:

  • Nothing is sent. A test send is kept as a test message instead, and doesn't use the plan's messages or the number's daily 1,000, so it never gets no_credits or number_daily_cap_reached.
  • Its status moves on by itself: queued, then sent 5 seconds later and delivered 15 seconds after it was made (counted from send_at for a scheduled one). A message whose expires_at comes before it would be sent becomes expired. Test messages never become read or failed.
  • A test key sees only its own test messages. GET /messages and GET /messages/{id} show them and nothing else, and DELETE /messages/{id} cancels one while it's queued or scheduled. Live keys never see test messages. The status filter of GET /messages isn't supported for test keys and answers test_mode_unsupported; number, since and until work.
  • Other writes are refused with 403 test_mode_unsupported: connecting a number, creating, changing or deleting reminders, uploading or deleting media, starting a number check, and adding, removing, rotating or testing webhook endpoints.
  • No webhook events are sent for test messages.
  • Test messages are kept for 30 days.

Resources#

  • Messages: send now or later, send to up to 1,000 recipients, read the history, cancel.
  • Numbers: list your WhatsApp numbers and Telegram accounts, and connect a WhatsApp number by QR code or pairing code.
  • Reminders: repeating messages on a cron schedule.
  • Media: upload files once and send them by media_id.
  • Number checks: find out which numbers are on WhatsApp.
  • Webhooks: signed events when a message is sent, fails, is cancelled or expires.
  • Errors: every error code.
  • Moving from v1 to v2: each v1 endpoint and field, and its v2 equivalent.