Msgera API Reference
A production-ready reference for sending WhatsApp messages, managing contacts, launching campaigns, and streaming delivery events from one consistent HTTP API.
- Endpoints
- 96
- Groups
- 16
- Base path
- /v1
Auth
API key
Format
JSON
Retries
Webhook-safe
Latency
Async sends
Connection
Copy once, use on every request.
BASE URL
https://api.msgera.io/v1AUTH HEADER
X-API-Key: wak_your_api_key_herePOST /developer/messages
Content-Type: application/json
Request contract
Shared rules across every endpoint.
- Authenticate every request with the X-API-Key header. API Keys endpoints are the exception: they are managed from the dashboard and use the dashboard session.
- A key only reaches the services it was given (messages, campaigns, contacts, ...). Anything else returns 403 API_KEY_PERMISSION_DENIED with the missing service. Domains, email and wallet can only be granted by active resellers.
- A key limited to certain devices or chats can only use those devices and chats; other requests return 403.
- Send JSON with Content-Type: application/json. Fields the endpoint doesn't know are ignored, not rejected, so check spelling: a misspelled optional field is silently dropped.
- Phone numbers are digits with an optional leading + and country code, 7 to 15 digits, for example +201001234567.
- Dates are ISO 8601, for example 2026-05-23T14:30:00Z.
- Uploads are limited to 16 MB per file.
01
Spintax lets one text produce many wordings: {Hi|Hello|Hey} picks one option per message.
02
Every number uses the same routes. Linked (QR) numbers and Meta numbers share the send, campaign, scheduling and device endpoints. If a number can't do something, you get 422 CAPABILITY_UNAVAILABLE naming the capability.
03
Meta numbers can only send free-form messages to contacts who wrote to them in the last 24 hours. Anyone else needs an approved template: pass templateId (see List Templates), otherwise the send returns 422 TEMPLATE_REQUIRED.
04
Sends are never refused for account safety limits. When a linked device is at its daily, hourly or per-minute budget, or a new contact is over the daily cap, the send is accepted with status QUEUED and a retryAt time, and goes out automatically later.
05
TEXT campaigns need enough different wordings: one per 8 recipients. On linked devices a campaign that falls short waits in AWAITING_REVIEW while variations are generated for you to approve, or you can review them first with Draft Variations and pass variationSegments.
06
Linked devices: the same wording may reach at most 8 different numbers per device in 24 hours (422 DUPLICATE_BROADCAST).
07
Most lists are paginated with page (starting at 1) and limit, and return { data, total, page, limit }.
08
Errors always have the same shape: { statusCode, error, message, timestamp, path }, plus a code (and related fields) for errors you can handle in code.
Create an API key
Generate a key in the dashboard and send it as X-API-Key.
Pick a device
List your numbers and copy the deviceUid of the one you want to send from.
Send or automate
Send a message, schedule one, or launch a campaign. A send may be queued for later; the response tells you.
Track events
Use message history and webhooks to reconcile delivery states.
API docs workspace
96 endpoints across 16 feature groups
https://api.msgera.io/v1Overview
What you can do with the API, in plain words - for engineers and non-engineers alike.
Msgera lets your own software send and receive WhatsApp messages through one HTTP API. Send a single message, run a campaign, keep contacts in sync, or have us call your server when a customer writes to you. Every endpoint uses the same JSON format, the same API key and the same error shape.
Send your first message
Text, template, media and voice messages from any of your numbers.
Manage contacts
Create and list the people you message.
Contact groups & tags
Sort contacts into groups and tags, then use their IDs to pick a campaign's audience.
Run campaigns
Send one message to many contacts on a schedule and follow delivery.
Listen with webhooks
Have us call your server when a message arrives or its status changes.
Account safety limits
How many messages a linked number can send per day, hour and minute, and what happens when it reaches the limit.
Sends are accepted, then delivered
A send returns 202 right away. Either it already went out (status SENT, with a messageId), or the number has to wait for its safety limits and the message is queued (status QUEUED, with retryAt and reason) and goes out automatically. Keep the queueId to follow it. Real problems, like a wrong field or a used-up plan limit, come back as errors and nothing is sent.
To sign your requests, create an API key in the dashboard (Developer > API Keys) and send it in the `X-API-Key` header. A key only sees your own data, only the services you allowed it, and, if you limited it, only some of your numbers or chats.
curl "https://api.msgera.io/v1/developer/devices" \
-H "X-API-Key: wak_your_api_key_here"API Keys
An API key is the password your app sends with every request (X-API-Key). These endpoints create and manage keys. They are used from the dashboard (Developer > API Keys) with your signed-in session, not with an API key: a request made with an API key gets 403. Each key has permissions, the services it may use, each fully on or off; calling anything else returns 403 API_KEY_PERMISSION_DENIED naming the missing service. You can also limit a key to some numbers (allowedAccountIds) or some chats (allowedChats) and give it its own rate limit. A key limited to certain numbers works on sends (text, media, voice), check-whatsapp, message history, scheduled messages, webhooks and media upload, and must pass deviceUid where a list accepts it. A key limited to certain chats works on sends, check-whatsapp, message history and scheduled messages. Routes that cover every number, such as media list, contact groups and tags, and reseller routes, refuse limited keys with 403.
GET/v1/api-keysLists all your keys, newest first, with their permissions and limits. The secret itself is never shown again after creation.
0 required1 response
/v1/api-keysLists all your keys, newest first, with their permissions and limits. The secret itself is never shown again after creation.
Request Examples
curl -X GET "https://api.msgera.io/v1/api-keys" \
-H "Authorization: Bearer <dashboard session token>"Responses
{
"data": [{
"id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"userId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"name": "Support bot",
"isActive": true,
"allowedAccountIds": ["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"],
"allowedChats": ["+201001234567", "[email protected]"],
"rateLimitPerMinute": 60,
"permissions": ["messages", "contacts"],
"lastUsedAt": null,
"createdAt": "2026-10-02T09:00:00.000Z",
"updatedAt": "2026-10-02T09:00:00.000Z"
}],
"total": 1
}Status Codes
Success
Unauthorized - missing or expired dashboard session
Forbidden - the request used an API key; keys can't manage keys
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or expired dashboard session |
| 403 | Forbidden - the request used an API key; keys can't manage keys |
| 500 | Internal Server Error |
POST/v1/api-keysCreates a key and returns its secret (key) once. Copy it now and store it safely: we only keep a hash, so it can't be shown again. Needs API access on your plan and counts toward your plan's key limit.
0 required2 responses
/v1/api-keysCreates a key and returns its secret (key) once. Copy it now and store it safely: we only keep a hash, so it can't be shown again. Needs API access on your plan and counts toward your plan's key limit.
Request Fields
name
Optionalstring, 1-100 characters • body
A label so you can tell your keys apart, for example the app that uses it. Defaults to Default.
Example:Support botallowedAccountIds
Optionalstring[], up to 200 • body
Limit the key to some of your WhatsApp numbers. These are account ids (not deviceUid); the dashboard fills them in when you pick numbers. Empty or left out allows every number, including ones you connect later. Unknown ids return 400.
Example:["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"]allowedChats
Optionalstring[], up to 500 • body
Limit the key to certain contacts and groups: phone numbers with country code, or group IDs ending in @g.us. Empty or left out allows every chat.
Example:["+201001234567", "[email protected]"]rateLimitPerMinute
Optionalinteger 1-6000, or null • body
How many requests per minute this key may make. null or left out uses the default of 120. Going over returns 429 API_KEY_RATE_LIMITED with retryAfterSeconds.
Example:60permissions
Optionalstring[], at least 1 • body
Which services the key may use, each fully on or off. numbers covers devices, check-whatsapp and spintax. domains, email and wallet are only for active resellers. Left out on create grants every non-reseller service. See List Grantable Services for what you can grant.
Allowed:messages · campaigns · contacts · scheduled · auto_reply · ai_agents · media · templates · webhooks · numbers · domains · email · walletExample: ["messages", "contacts"]| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| name | string, 1-100 characters | body | No | A label so you can tell your keys apart, for example the app that uses it. Defaults to Default.Example: Support bot |
| allowedAccountIds | string[], up to 200 | body | No | Limit the key to some of your WhatsApp numbers. These are account ids (not deviceUid); the dashboard fills them in when you pick numbers. Empty or left out allows every number, including ones you connect later. Unknown ids return 400.Example: ["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"] |
| allowedChats | string[], up to 500 | body | No | Limit the key to certain contacts and groups: phone numbers with country code, or group IDs ending in @g.us. Empty or left out allows every chat.Example: ["+201001234567", "[email protected]"] |
| rateLimitPerMinute | integer 1-6000, or null | body | No | How many requests per minute this key may make. null or left out uses the default of 120. Going over returns 429 API_KEY_RATE_LIMITED with retryAfterSeconds.Example: 60 |
| permissions | string[], at least 1 | body | No | Which services the key may use, each fully on or off. numbers covers devices, check-whatsapp and spintax. domains, email and wallet are only for active resellers. Left out on create grants every non-reseller service. See List Grantable Services for what you can grant.Allowed: messages · campaigns · contacts · scheduled · auto_reply · ai_agents · media · templates · webhooks · numbers · domains · email · walletExample: ["messages", "contacts"] |
Request Body
{
"name": "Support bot",
"allowedAccountIds": ["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"],
"allowedChats": ["+201001234567", "[email protected]"],
"rateLimitPerMinute": 60,
"permissions": ["messages", "contacts"]
}Request Examples
curl -X POST "https://api.msgera.io/v1/api-keys" \
-H "Authorization: Bearer <dashboard session token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Support bot",
"allowedAccountIds": ["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"],
"allowedChats": ["+201001234567", "[email protected]"],
"rateLimitPerMinute": 60,
"permissions": ["messages", "contacts"]
}'Responses
{
"id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"userId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"name": "Support bot",
"isActive": true,
"allowedAccountIds": ["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"],
"allowedChats": ["+201001234567", "[email protected]"],
"rateLimitPerMinute": 60,
"permissions": ["messages", "contacts"],
"lastUsedAt": null,
"createdAt": "2026-10-02T09:00:00.000Z",
"updatedAt": "2026-10-02T09:00:00.000Z",
"key": "wak_3f8a1c9e2b7d4f6a0c5e8b1d2a9f7c34"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Unknown WhatsApp account id(s): 00000000-0000-4000-8000-000000000000",
"timestamp": "2026-10-02T09:00:00.000Z",
"path": "/v1/api-keys"
}Status Codes
Created
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or expired dashboard session
Forbidden - the request used an API key (keys can't manage keys), your plan doesn't include API access or its key limit is reached, or you granted a reseller service without being an active reseller (RESELLER_PERMISSION_DENIED)
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or expired dashboard session |
| 403 | Forbidden - the request used an API key (keys can't manage keys), your plan doesn't include API access or its key limit is reached, or you granted a reseller service without being an active reseller (RESELLER_PERMISSION_DENIED) |
| 500 | Internal Server Error |
GET/v1/api-keys/servicesLists the services you can give a key, with a label and short description for each. Use it to build a permission picker. Reseller services (domains, email, wallet) appear only while you are an active reseller.
0 required1 response
/v1/api-keys/servicesLists the services you can give a key, with a label and short description for each. Use it to build a permission picker. Reseller services (domains, email, wallet) appear only while you are an active reseller.
Request Examples
curl -X GET "https://api.msgera.io/v1/api-keys/services" \
-H "Authorization: Bearer <dashboard session token>"Responses
{
"services": [
{
"key": "messages",
"label": "Messages",
"description": "Send text, media and voice messages and read message history.",
"resellerOnly": false
},
{
"key": "numbers",
"label": "Numbers",
"description": "List and manage WhatsApp numbers, check numbers on WhatsApp and preview spintax.",
"resellerOnly": false
}
],
"resellerActive": false
}Status Codes
Success
Unauthorized - missing or expired dashboard session
Forbidden - the request used an API key; keys can't manage keys
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or expired dashboard session |
| 403 | Forbidden - the request used an API key; keys can't manage keys |
| 500 | Internal Server Error |
PATCH/v1/api-keys/:idChanges a key's name, permissions, numbers, chats or rate limit. Fields you leave out stay the same; send an empty array to remove a number or chat limit, or null to go back to the default rate limit. The change applies from the key's next request. The secret doesn't change.
1 required1 response
/v1/api-keys/:idChanges a key's name, permissions, numbers, chats or rate limit. Fields you leave out stay the same; send an empty array to remove a number or chat limit, or null to go back to the default rate limit. The change applies from the key's next request. The secret doesn't change.
Request Fields
id
Requiredstring (UUID) • path
The key to act on. Use the id from List API Keys.
Example:7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4fname
Optionalstring, 1-100 characters • body
A label so you can tell your keys apart, for example the app that uses it. Defaults to Default.
Example:Support botallowedAccountIds
Optionalstring[], up to 200 • body
Limit the key to some of your WhatsApp numbers. These are account ids (not deviceUid); the dashboard fills them in when you pick numbers. Empty or left out allows every number, including ones you connect later. Unknown ids return 400.
Example:["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"]allowedChats
Optionalstring[], up to 500 • body
Limit the key to certain contacts and groups: phone numbers with country code, or group IDs ending in @g.us. Empty or left out allows every chat.
Example:["+201001234567", "[email protected]"]rateLimitPerMinute
Optionalinteger 1-6000, or null • body
How many requests per minute this key may make. null or left out uses the default of 120. Going over returns 429 API_KEY_RATE_LIMITED with retryAfterSeconds.
Example:60permissions
Optionalstring[], at least 1 • body
Which services the key may use, each fully on or off. numbers covers devices, check-whatsapp and spintax. domains, email and wallet are only for active resellers. Left out on create grants every non-reseller service. See List Grantable Services for what you can grant.
Allowed:messages · campaigns · contacts · scheduled · auto_reply · ai_agents · media · templates · webhooks · numbers · domains · email · walletExample: ["messages", "contacts"]| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The key to act on. Use the id from List API Keys.Example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f |
| name | string, 1-100 characters | body | No | A label so you can tell your keys apart, for example the app that uses it. Defaults to Default.Example: Support bot |
| allowedAccountIds | string[], up to 200 | body | No | Limit the key to some of your WhatsApp numbers. These are account ids (not deviceUid); the dashboard fills them in when you pick numbers. Empty or left out allows every number, including ones you connect later. Unknown ids return 400.Example: ["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"] |
| allowedChats | string[], up to 500 | body | No | Limit the key to certain contacts and groups: phone numbers with country code, or group IDs ending in @g.us. Empty or left out allows every chat.Example: ["+201001234567", "[email protected]"] |
| rateLimitPerMinute | integer 1-6000, or null | body | No | How many requests per minute this key may make. null or left out uses the default of 120. Going over returns 429 API_KEY_RATE_LIMITED with retryAfterSeconds.Example: 60 |
| permissions | string[], at least 1 | body | No | Which services the key may use, each fully on or off. numbers covers devices, check-whatsapp and spintax. domains, email and wallet are only for active resellers. Left out on create grants every non-reseller service. See List Grantable Services for what you can grant.Allowed: messages · campaigns · contacts · scheduled · auto_reply · ai_agents · media · templates · webhooks · numbers · domains · email · walletExample: ["messages", "contacts"] |
Request Body
{
"allowedChats": [],
"rateLimitPerMinute": null,
"permissions": ["messages", "contacts", "campaigns"]
}Request Examples
curl -X PATCH "https://api.msgera.io/v1/api-keys/{id}" \
-H "Authorization: Bearer <dashboard session token>" \
-H "Content-Type: application/json" \
-d '{
"allowedChats": [],
"rateLimitPerMinute": null,
"permissions": ["messages", "contacts", "campaigns"]
}'Responses
{
"id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"userId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"name": "Support bot",
"isActive": true,
"allowedAccountIds": ["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"],
"allowedChats": ["+201001234567", "[email protected]"],
"rateLimitPerMinute": 60,
"permissions": ["messages", "contacts"],
"lastUsedAt": null,
"createdAt": "2026-10-02T09:00:00.000Z",
"updatedAt": "2026-10-02T09:00:00.000Z"
}Status Codes
Success
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or expired dashboard session
Forbidden - the request used an API key, or you granted a reseller service without being an active reseller (RESELLER_PERMISSION_DENIED)
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or expired dashboard session |
| 403 | Forbidden - the request used an API key, or you granted a reseller service without being an active reseller (RESELLER_PERMISSION_DENIED) |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/api-keys/:id/rotateGives the key a new secret, returned once in key, and returns the full key with its limits. Use it if a secret leaked. The old secret stops working immediately, so update your app right away. Permissions and limits stay the same; lastUsedAt starts again from null.
1 required1 response
/v1/api-keys/:id/rotateGives the key a new secret, returned once in key, and returns the full key with its limits. Use it if a secret leaked. The old secret stops working immediately, so update your app right away. Permissions and limits stay the same; lastUsedAt starts again from null.
Request Fields
id
Requiredstring (UUID) • path
The key to act on. Use the id from List API Keys.
Example:7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The key to act on. Use the id from List API Keys.Example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f |
Request Examples
curl -X POST "https://api.msgera.io/v1/api-keys/{id}/rotate" \
-H "Authorization: Bearer <dashboard session token>"Responses
{
"id": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"userId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"name": "Support bot",
"isActive": true,
"allowedAccountIds": ["1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6"],
"allowedChats": ["+201001234567", "[email protected]"],
"rateLimitPerMinute": 60,
"permissions": ["messages", "contacts"],
"lastUsedAt": null,
"createdAt": "2026-10-02T09:00:00.000Z",
"updatedAt": "2026-10-05T14:20:00.000Z",
"key": "wak_9b2e7c4a1f8d3e6b0a5c2f9e1d7b4a63"
}Status Codes
Created
Unauthorized - missing or expired dashboard session
Forbidden - the request used an API key; keys can't manage keys
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 401 | Unauthorized - missing or expired dashboard session |
| 403 | Forbidden - the request used an API key; keys can't manage keys |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
GET/v1/api-keys/auditShows who created, changed, rotated or revoked keys, newest first, with the fields that changed and the IP address. Use it to review changes to your keys.
0 required1 response
/v1/api-keys/auditShows who created, changed, rotated or revoked keys, newest first, with the fields that changed and the IP address. Use it to review changes to your keys.
Request Fields
keyId
Optionalstring (UUID) • query
Only show activity for one key. Use the id from List API Keys.
Example:7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4fpage
Optionalinteger, from 1 • query
Which page of results to return. Starts at 1.
Example:1limit
Optionalinteger, 1-100 • query
How many entries per page. Default 20.
Example:20| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| keyId | string (UUID) | query | No | Only show activity for one key. Use the id from List API Keys.Example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f |
| page | integer, from 1 | query | No | Which page of results to return. Starts at 1.Example: 1 |
| limit | integer, 1-100 | query | No | How many entries per page. Default 20.Example: 20 |
Request Examples
curl -X GET "https://api.msgera.io/v1/api-keys/audit" \
-H "Authorization: Bearer <dashboard session token>"Responses
{
"data": [
{
"id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"action": "api_key.updated",
"apiKeyId": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"metadata": {
"name": "Support bot",
"changes": { "permissions": { "added": ["campaigns"], "removed": [] } }
},
"ipAddress": "203.0.113.7",
"createdAt": "2026-10-02T09:05:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}Status Codes
Success
Unauthorized - missing or expired dashboard session
Forbidden - the request used an API key; keys can't manage keys
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or expired dashboard session |
| 403 | Forbidden - the request used an API key; keys can't manage keys |
| 500 | Internal Server Error |
GET/v1/api-keys/:id/auditSame as API Key Activity, for one key. It still works after the key is revoked, so you can see its full history. An unknown id returns an empty list.
1 required1 response
/v1/api-keys/:id/auditSame as API Key Activity, for one key. It still works after the key is revoked, so you can see its full history. An unknown id returns an empty list.
Request Fields
id
Requiredstring (UUID) • path
The key to act on. Use the id from List API Keys.
Example:7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4fpage
Optionalinteger, from 1 • query
Which page of results to return. Starts at 1.
Example:1limit
Optionalinteger, 1-100 • query
How many entries per page. Default 20.
Example:20| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The key to act on. Use the id from List API Keys.Example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f |
| page | integer, from 1 | query | No | Which page of results to return. Starts at 1.Example: 1 |
| limit | integer, 1-100 | query | No | How many entries per page. Default 20.Example: 20 |
Request Examples
curl -X GET "https://api.msgera.io/v1/api-keys/{id}/audit" \
-H "Authorization: Bearer <dashboard session token>"Responses
{
"data": [
{
"id": "2b3c4d5e-6f7a-4b8c-9d0e-1f2a3b4c5d6e",
"action": "api_key.updated",
"apiKeyId": "7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f",
"metadata": {
"name": "Support bot",
"changes": { "permissions": { "added": ["campaigns"], "removed": [] } }
},
"ipAddress": "203.0.113.7",
"createdAt": "2026-10-02T09:05:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}Status Codes
Success
Unauthorized - missing or expired dashboard session
Forbidden - the request used an API key; keys can't manage keys
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or expired dashboard session |
| 403 | Forbidden - the request used an API key; keys can't manage keys |
| 500 | Internal Server Error |
DELETE/v1/api-keys/:idDeletes a key for good. Requests that use it fail with 401 straight away. This can't be undone; create a new key if you need one.
1 required1 response
/v1/api-keys/:idDeletes a key for good. Requests that use it fail with 401 straight away. This can't be undone; create a new key if you need one.
Request Fields
id
Requiredstring (UUID) • path
The key to act on. Use the id from List API Keys.
Example:7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The key to act on. Use the id from List API Keys.Example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f |
Request Examples
curl -X DELETE "https://api.msgera.io/v1/api-keys/{id}" \
-H "Authorization: Bearer <dashboard session token>"Responses
{ "deleted": true }Status Codes
Success
Unauthorized - missing or expired dashboard session
Forbidden - the request used an API key; keys can't manage keys
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or expired dashboard session |
| 403 | Forbidden - the request used an API key; keys can't manage keys |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
Devices
Your WhatsApp numbers. A device is one number you connected, either by scanning a QR code (linked device) or through Meta. Every device has a deviceUid (it looks like wa_dev_ followed by 32 characters); you pass it to send messages, run campaigns and set up webhooks. capabilities tells you what each number can do; asking a number for something it can't do returns 422 CAPABILITY_UNAVAILABLE. Linked devices also show their safety budget: how many more messages they can send today, this hour and this minute. These endpoints need the numbers permission on the key.
GET/v1/developer/devicesLists all your numbers, oldest first. Use it to find the deviceUid to send from and to check a number is CONNECTED. For linked devices, look at safety.canSend and safety.dailyRemaining before a big send; sends over the budget are queued, not refused. Meta numbers have no per-device budget, so safety is null. A key limited to certain devices only sees those.
0 required1 response
/v1/developer/devicesLists all your numbers, oldest first. Use it to find the deviceUid to send from and to check a number is CONNECTED. For linked devices, look at safety.canSend and safety.dailyRemaining before a big send; sends over the budget are queued, not refused. Meta numbers have no per-device budget, so safety is null. A key limited to certain devices only sees those.
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/devices" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"items": [
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"name": "Sales line",
"status": "CONNECTED",
"phone": "+201009876543",
"createdAt": "2026-05-21T10:00:00.000Z",
"capabilities": {
"qrPairing": true,
"reconnect": true,
"logout": true,
"registerPhone": false,
"campaigns": true,
"groups": true,
"numberCheck": true,
"templates": false,
"freeFormMessaging": true
},
"safety": {
"dailyLimit": 300,
"dailySent": 12,
"dailyRemaining": 288,
"hourlyLimit": 60,
"hourlySent": 4,
"hourlyRemaining": 56,
"minuteLimit": 3,
"minuteSent": 0,
"minuteRemaining": 3,
"accountAgeDays": 2,
"canSend": true,
"blockedReason": null,
"resetsAt": "2026-05-23T23:59:59.999Z",
"campaignDelayMinSec": 15,
"campaignDelayMaxSec": 50,
"variantRecipientsPerVariant": 8
}
},
{
"deviceUid": "wa_dev_TWV0YU51bWJlcklkMDAwMDAwMDAwMDAw",
"name": "Support line",
"status": "CONNECTED",
"phone": "+15551234567",
"createdAt": "2026-06-02T08:30:00.000Z",
"capabilities": {
"qrPairing": false,
"reconnect": true,
"logout": false,
"registerPhone": true,
"campaigns": true,
"groups": false,
"numberCheck": false,
"templates": true,
"freeFormMessaging": true
},
"safety": null
}
]
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
GET/v1/developer/devices/:deviceUidReturns one number with the same fields as List Devices. Call it right before a large send to read the live safety budget. status is CONNECTED when the number can send; other values such as CONNECTING or DISCONNECTED mean it needs attention in the dashboard.
1 required2 responses
/v1/developer/devices/:deviceUidReturns one number with the same fields as List Devices. Call it right before a large send to read the live safety budget. status is CONNECTED when the number can send; other values such as CONNECTING or DISCONNECTED mean it needs attention in the dashboard.
Request Fields
deviceUid
Requiredstring • path
The number to read. Copy it from List Devices or the Accounts page in the dashboard.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | path | Yes | The number to read. Copy it from List Devices or the Accounts page in the dashboard.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/devices/{deviceUid}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"name": "Sales line",
"status": "CONNECTED",
"phone": "+201009876543",
"createdAt": "2026-05-21T10:00:00.000Z",
"capabilities": {
"qrPairing": true,
"reconnect": true,
"logout": true,
"registerPhone": false,
"campaigns": true,
"groups": true,
"numberCheck": true,
"templates": false,
"freeFormMessaging": true
},
"safety": {
"dailyLimit": 300,
"dailySent": 12,
"dailyRemaining": 288,
"hourlyLimit": 60,
"hourlySent": 4,
"hourlyRemaining": 56,
"minuteLimit": 3,
"minuteSent": 0,
"minuteRemaining": 3,
"accountAgeDays": 2,
"canSend": true,
"blockedReason": null,
"resetsAt": "2026-05-23T23:59:59.999Z",
"campaignDelayMinSec": 15,
"campaignDelayMaxSec": 50,
"variantRecipientsPerVariant": 8
}
}{
"statusCode": 404,
"error": "Not Found",
"message": "Device wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw not found or not owned by you",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/devices/wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw"
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the key lacks the numbers permission or is limited to other devices
Not Found - the resource doesn't exist or isn't yours
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the key lacks the numbers permission or is limited to other devices |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
Messaging
Send WhatsApp messages and read your message history. The same routes work for every number; we pick how to deliver from the device. How a send works: (1) We check your request right away. If something is wrong (bad field, plan limit, a Meta number that needs a template, the same wording sent to too many numbers) you get an error and nothing is sent. (2) Otherwise you get 202 and we try to send at once. status SENT or DISPATCHED with a messageId means it went to WhatsApp. (3) If the number is busy, paused, reconnecting, at its safety limit, or over its daily cap for new contacts, the send is not refused: you get status QUEUED, messageId null, a retryAt time and a reason, and it goes out automatically later (queued messages expire after 30 days). (4) Keep the queueId from the response and track it with GET /v1/developer/scheduled-messages/:id (needs the scheduled permission): it shows PENDING while waiting, then the messageId once sent. With the messageId, use Get Message or the message.status webhook for delivered and read. accepted false with status FAILED means WhatsApp refused it; UNKNOWN means we couldn't confirm the outcome, so check before resending.
POST/v1/developer/messages/sendSends a text message, or an approved template when you pass templateId. Use it for one-off messages like order updates or replies. On a Meta number, a contact who hasn't written to you in the last 24 hours can only get a template: text returns 422 TEMPLATE_REQUIRED, so send again with a templateId (see List Templates). Template recipients must have WhatsApp opt-in recorded on their contact. Templates are sent straight away (status DISPATCHED, queueId null) and are never queued. On linked devices, the same wording may reach at most 8 different numbers per device in 24 hours (422 DUPLICATE_BROADCAST); placeholders like {firstName} don't make wordings different. Text with spintax such as {Hi|Hello} is checked when each wording is picked, so that send ends FAILED instead. Numbers that never messaged the device are limited per day; over the cap the send is QUEUED and goes out on the following days.
2 required9 responses
/v1/developer/messages/sendSends a text message, or an approved template when you pass templateId. Use it for one-off messages like order updates or replies. On a Meta number, a contact who hasn't written to you in the last 24 hours can only get a template: text returns 422 TEMPLATE_REQUIRED, so send again with a templateId (see List Templates). Template recipients must have WhatsApp opt-in recorded on their contact. Templates are sent straight away (status DISPATCHED, queueId null) and are never queued. On linked devices, the same wording may reach at most 8 different numbers per device in 24 hours (422 DUPLICATE_BROADCAST); placeholders like {firstName} don't make wordings different. Text with spintax such as {Hi|Hello} is checked when each wording is picked, so that send ends FAILED instead. Numbers that never messaged the device are limited per day; over the cap the send is QUEUED and goes out on the following days.
Request Fields
deviceUid
Requiredstring • body
Which of your WhatsApp numbers sends the message. Copy it from List Devices or the Accounts page in the dashboard.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwto
Requiredstring, 7-15 digits with optional leading + • body
The phone number that receives the message, with country code. Group IDs are not accepted here.
Example:+201001234567text
Optionalstring • body
The message to send. Required unless you send a template. You can use spintax like {Hi|Hello} to vary the wording.
Example:Hi Sara, your order #1042 has shipped.templateId
Optionalstring (UUID) • body
An approved template to send instead of text (Meta numbers only). Needed when the contact hasn't written to you in the last 24 hours. Get the id from List Templates.
Example:8b7d1c2e-3f4a-4b5c-9d6e-7f8a9b0c1d2evariables
Optionalstring[] • body
The values that fill the template's placeholders {{1}}, {{2}}, … in order. Send as many as the template's variableCount.
Example:["Sara", "#1042"]clientMessageId
Optionalstring, 1-128 characters • body
Your own unique ID for this message. If your request times out and you retry with the same ID, we won't send it twice. Reusing it for a different message returns 409.
Example:order-1042-confirmation| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | body | Yes | Which of your WhatsApp numbers sends the message. Copy it from List Devices or the Accounts page in the dashboard.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| to | string, 7-15 digits with optional leading + | body | Yes | The phone number that receives the message, with country code. Group IDs are not accepted here.Example: +201001234567 |
| text | string | body | No | The message to send. Required unless you send a template. You can use spintax like {Hi|Hello} to vary the wording.Example: Hi Sara, your order #1042 has shipped. |
| templateId | string (UUID) | body | No | An approved template to send instead of text (Meta numbers only). Needed when the contact hasn't written to you in the last 24 hours. Get the id from List Templates.Example: 8b7d1c2e-3f4a-4b5c-9d6e-7f8a9b0c1d2e |
| variables | string[] | body | No | The values that fill the template's placeholders {{1}}, {{2}}, … in order. Send as many as the template's variableCount.Example: ["Sara", "#1042"] |
| clientMessageId | string, 1-128 characters | body | No | Your own unique ID for this message. If your request times out and you retry with the same ID, we won't send it twice. Reusing it for a different message returns 409.Example: order-1042-confirmation |
Request Body
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"to": "+201001234567",
"text": "Hi Sara, your order #1042 has shipped.",
"clientMessageId": "order-1042-confirmation"
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/messages/send" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"to": "+201001234567",
"text": "Hi Sara, your order #1042 has shipped.",
"clientMessageId": "order-1042-confirmation"
}'Responses
{
"accepted": true,
"queued": false,
"status": "SENT",
"queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"messageId": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
"providerMessageId": "3EB0C767D26A1D8E4F2B",
"retryAt": null,
"reason": null
}{
"accepted": true,
"queued": true,
"status": "QUEUED",
"queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"messageId": null,
"providerMessageId": null,
"retryAt": "2026-05-24T00:00:05.000Z",
"reason": "Daily account safety limit reached (300 messages per day for accounts at this age). Try again after 2026-05-23T23:59:59.999Z."
}{
"accepted": true,
"queued": false,
"status": "DISPATCHED",
"queueId": null,
"messageId": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
"providerMessageId": "wamid.HBgMMjAxMDAxMjM0NTY3FQIAERgS",
"retryAt": null,
"reason": null
}{
"statusCode": 409,
"error": "Conflict",
"message": "clientMessageId was already used for a different message",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send"
}{
"statusCode": 403,
"error": "Forbidden",
"code": "PLAN_LIMIT_EXCEEDED",
"message": "Monthly message limit reached for your plan.",
"planKey": "starter",
"limitKey": "sentMessagesPerMonth",
"limit": 2500,
"current": 2500,
"requested": 1,
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send"
}{
"statusCode": 422,
"error": "Template Required",
"code": "TEMPLATE_REQUIRED",
"message": "This contact hasn't messaged you in 24 hours. Send it again with a templateId.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send"
}{
"statusCode": 422,
"error": "Error",
"code": "TEMPLATE_SEND_INVALID",
"message": "Template requires 2 non-empty body variable(s)",
"issues": [{ "code": "TEMPLATE_VARIABLE_MISSING", "message": "Template requires 2 non-empty body variable(s)" }],
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send"
}{
"statusCode": 422,
"error": "Capability Unavailable",
"code": "CAPABILITY_UNAVAILABLE",
"capability": "templates",
"message": "This feature isn't available for this number.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send"
}{
"statusCode": 422,
"error": "Duplicate Broadcast",
"code": "DUPLICATE_BROADCAST",
"message": "The same text was already sent to 8 different numbers from this device in the last 24h. Vary the wording (e.g. {Hi|Hello} spintax) or send it as a campaign so variations are generated for you.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send"
}Status Codes
Accepted - the request was taken. Check status in the body: it may already be sent, or queued for later.
Bad Request - a field is invalid, or you already have 1,000 messages waiting in the queue (cancel some or wait for them to send)
Unauthorized - missing or invalid API key
Forbidden - your monthly message limit is used up (PLAN_LIMIT_EXCEEDED), the key lacks the messages permission, or the key is limited to other devices or chats
Not Found - the device (or template, or media file) doesn't exist or isn't yours
Conflict - this clientMessageId was already used for a different message
Unprocessable Entity - the number can't do this (CAPABILITY_UNAVAILABLE), a Meta number needs a template (TEMPLATE_REQUIRED), the template can't be sent as configured (TEMPLATE_SEND_INVALID), or the same wording already reached too many numbers (DUPLICATE_BROADCAST).
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 202 | Accepted - the request was taken. Check status in the body: it may already be sent, or queued for later. |
| 400 | Bad Request - a field is invalid, or you already have 1,000 messages waiting in the queue (cancel some or wait for them to send) |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your monthly message limit is used up (PLAN_LIMIT_EXCEEDED), the key lacks the messages permission, or the key is limited to other devices or chats |
| 404 | Not Found - the device (or template, or media file) doesn't exist or isn't yours |
| 409 | Conflict - this clientMessageId was already used for a different message |
| 422 | Unprocessable Entity - the number can't do this (CAPABILITY_UNAVAILABLE), a Meta number needs a template (TEMPLATE_REQUIRED), the template can't be sent as configured (TEMPLATE_SEND_INVALID), or the same wording already reached too many numbers (DUPLICATE_BROADCAST). |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
POST/v1/developer/messages/send-mediaSends a photo, video or document you uploaded first with Upload Media, with an optional caption. Needs the media feature on your plan. A media file can be sent once: after the send it is removed, so upload again for every send. Works like Send Message: you get 202 with SENT, or QUEUED with a retryAt when the number has to wait. On Meta numbers it only reaches contacts who wrote to you in the last 24 hours (422 TEMPLATE_REQUIRED otherwise). On linked devices the caption counts toward the duplicate wording rule (422 DUPLICATE_BROADCAST).
3 required7 responses
/v1/developer/messages/send-mediaSends a photo, video or document you uploaded first with Upload Media, with an optional caption. Needs the media feature on your plan. A media file can be sent once: after the send it is removed, so upload again for every send. Works like Send Message: you get 202 with SENT, or QUEUED with a retryAt when the number has to wait. On Meta numbers it only reaches contacts who wrote to you in the last 24 hours (422 TEMPLATE_REQUIRED otherwise). On linked devices the caption counts toward the duplicate wording rule (422 DUPLICATE_BROADCAST).
Request Fields
deviceUid
Requiredstring • body
Which of your WhatsApp numbers sends the message. Copy it from List Devices or the Accounts page in the dashboard.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwto
Requiredstring, 7-15 digits with optional leading + • body
The phone number that receives the message, with country code. Group IDs are not accepted here.
Example:+201001234567mediaId
Requiredstring • body
The file to send. Use the id returned by Upload Media. Each upload can be sent once.
Example:3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0bcaption
Optionalstring • body
Text shown under the photo, video or document.
Example:Your invoice for MayclientMessageId
Optionalstring, 1-128 characters • body
Your own unique ID for this message. If your request times out and you retry with the same ID, we won't send it twice. Reusing it for a different message returns 409.
Example:order-1042-confirmation| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | body | Yes | Which of your WhatsApp numbers sends the message. Copy it from List Devices or the Accounts page in the dashboard.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| to | string, 7-15 digits with optional leading + | body | Yes | The phone number that receives the message, with country code. Group IDs are not accepted here.Example: +201001234567 |
| mediaId | string | body | Yes | The file to send. Use the id returned by Upload Media. Each upload can be sent once.Example: 3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b |
| caption | string | body | No | Text shown under the photo, video or document.Example: Your invoice for May |
| clientMessageId | string, 1-128 characters | body | No | Your own unique ID for this message. If your request times out and you retry with the same ID, we won't send it twice. Reusing it for a different message returns 409.Example: order-1042-confirmation |
Request Body
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"to": "+201001234567",
"mediaId": "3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b",
"caption": "Your invoice for May",
"clientMessageId": "invoice-may-1042"
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/messages/send-media" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"to": "+201001234567",
"mediaId": "3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b",
"caption": "Your invoice for May",
"clientMessageId": "invoice-may-1042"
}'Responses
{
"accepted": true,
"queued": false,
"status": "SENT",
"queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"messageId": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
"providerMessageId": "3EB0C767D26A1D8E4F2B",
"retryAt": null,
"reason": null
}{
"accepted": true,
"queued": true,
"status": "QUEUED",
"queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"messageId": null,
"providerMessageId": null,
"retryAt": "2026-05-24T00:00:05.000Z",
"reason": "Daily account safety limit reached (300 messages per day for accounts at this age). Try again after 2026-05-23T23:59:59.999Z."
}{
"accepted": false,
"queued": false,
"status": "FAILED",
"queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"messageId": null,
"retryAt": null,
"reason": "WhatsApp did not accept the message"
}{
"statusCode": 403,
"error": "Forbidden",
"code": "PLAN_FEATURE_UNAVAILABLE",
"message": "Your plan doesn't include media messages.",
"planKey": "starter",
"featureKey": "mediaDocuments",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send-media"
}{
"statusCode": 404,
"error": "Not Found",
"message": "Media not found",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send-media"
}{
"statusCode": 422,
"error": "Template Required",
"code": "TEMPLATE_REQUIRED",
"message": "This contact hasn't messaged you in 24 hours. Send it again with a templateId.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send-media"
}{
"statusCode": 422,
"error": "Duplicate Broadcast",
"code": "DUPLICATE_BROADCAST",
"message": "The same text was already sent to 8 different numbers from this device in the last 24h. Vary the wording (e.g. {Hi|Hello} spintax) or send it as a campaign so variations are generated for you.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send-media"
}Status Codes
Accepted - the request was taken. Check status in the body: it may already be sent, or queued for later.
Bad Request - a field is invalid, or you already have 1,000 messages waiting in the queue (cancel some or wait for them to send)
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include media (PLAN_FEATURE_UNAVAILABLE), the monthly message limit is used up (PLAN_LIMIT_EXCEEDED), or the key lacks the permission or is limited to other devices or chats
Not Found - unknown device, or the media id is unknown or was already sent
Conflict - this clientMessageId was already used for a different message
Unprocessable Entity - the number can't do this (CAPABILITY_UNAVAILABLE), a Meta number needs a template (TEMPLATE_REQUIRED), the template can't be sent as configured (TEMPLATE_SEND_INVALID), or the same wording already reached too many numbers (DUPLICATE_BROADCAST).
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 202 | Accepted - the request was taken. Check status in the body: it may already be sent, or queued for later. |
| 400 | Bad Request - a field is invalid, or you already have 1,000 messages waiting in the queue (cancel some or wait for them to send) |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include media (PLAN_FEATURE_UNAVAILABLE), the monthly message limit is used up (PLAN_LIMIT_EXCEEDED), or the key lacks the permission or is limited to other devices or chats |
| 404 | Not Found - unknown device, or the media id is unknown or was already sent |
| 409 | Conflict - this clientMessageId was already used for a different message |
| 422 | Unprocessable Entity - the number can't do this (CAPABILITY_UNAVAILABLE), a Meta number needs a template (TEMPLATE_REQUIRED), the template can't be sent as configured (TEMPLATE_SEND_INVALID), or the same wording already reached too many numbers (DUPLICATE_BROADCAST). |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
POST/v1/developer/messages/send-voiceSends an audio file you uploaded first as a voice note. The file must be audio (kind audio); anything else returns 400. Like media, each upload can be sent once, then it is removed. Works like Send Message: 202 with SENT, or QUEUED with a retryAt when the number has to wait. On Meta numbers it only reaches contacts who wrote to you in the last 24 hours (422 TEMPLATE_REQUIRED otherwise).
3 required4 responses
/v1/developer/messages/send-voiceSends an audio file you uploaded first as a voice note. The file must be audio (kind audio); anything else returns 400. Like media, each upload can be sent once, then it is removed. Works like Send Message: 202 with SENT, or QUEUED with a retryAt when the number has to wait. On Meta numbers it only reaches contacts who wrote to you in the last 24 hours (422 TEMPLATE_REQUIRED otherwise).
Request Fields
deviceUid
Requiredstring • body
Which of your WhatsApp numbers sends the message. Copy it from List Devices or the Accounts page in the dashboard.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwto
Requiredstring, 7-15 digits with optional leading + • body
The phone number that receives the message, with country code. Group IDs are not accepted here.
Example:+201001234567mediaId
Requiredstring • body
The audio file to send as a voice note. Use the id returned by Upload Media; the file must be audio (for example .ogg or .mp3).
Example:3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0bclientMessageId
Optionalstring, 1-128 characters • body
Your own unique ID for this message. If your request times out and you retry with the same ID, we won't send it twice. Reusing it for a different message returns 409.
Example:order-1042-confirmation| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | body | Yes | Which of your WhatsApp numbers sends the message. Copy it from List Devices or the Accounts page in the dashboard.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| to | string, 7-15 digits with optional leading + | body | Yes | The phone number that receives the message, with country code. Group IDs are not accepted here.Example: +201001234567 |
| mediaId | string | body | Yes | The audio file to send as a voice note. Use the id returned by Upload Media; the file must be audio (for example .ogg or .mp3).Example: 3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b |
| clientMessageId | string, 1-128 characters | body | No | Your own unique ID for this message. If your request times out and you retry with the same ID, we won't send it twice. Reusing it for a different message returns 409.Example: order-1042-confirmation |
Request Body
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"to": "+201001234567",
"mediaId": "3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b",
"clientMessageId": "welcome-voice-1042"
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/messages/send-voice" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"to": "+201001234567",
"mediaId": "3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b",
"clientMessageId": "welcome-voice-1042"
}'Responses
{
"accepted": true,
"queued": false,
"status": "SENT",
"queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"messageId": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
"providerMessageId": "3EB0C767D26A1D8E4F2B",
"retryAt": null,
"reason": null
}{
"accepted": true,
"queued": true,
"status": "QUEUED",
"queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"messageId": null,
"providerMessageId": null,
"retryAt": "2026-05-24T00:00:05.000Z",
"reason": "Daily account safety limit reached (300 messages per day for accounts at this age). Try again after 2026-05-23T23:59:59.999Z."
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Voice message requires an audio media file",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send-voice"
}{
"statusCode": 422,
"error": "Template Required",
"code": "TEMPLATE_REQUIRED",
"message": "This contact hasn't messaged you in 24 hours. Send it again with a templateId.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send-voice"
}Status Codes
Accepted - the request was taken. Check status in the body: it may already be sent, or queued for later.
Bad Request - a field is invalid, the file isn't audio, or 1,000 messages are already waiting in the queue
Unauthorized - missing or invalid API key
Forbidden - your monthly message limit is used up (PLAN_LIMIT_EXCEEDED), the key lacks the messages permission, or the key is limited to other devices or chats
Not Found - unknown device, or the media id is unknown or was already sent
Conflict - this clientMessageId was already used for a different message
Unprocessable Entity - the number can't do this (CAPABILITY_UNAVAILABLE), a Meta number needs a template (TEMPLATE_REQUIRED), the template can't be sent as configured (TEMPLATE_SEND_INVALID), or the same wording already reached too many numbers (DUPLICATE_BROADCAST).
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 202 | Accepted - the request was taken. Check status in the body: it may already be sent, or queued for later. |
| 400 | Bad Request - a field is invalid, the file isn't audio, or 1,000 messages are already waiting in the queue |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your monthly message limit is used up (PLAN_LIMIT_EXCEEDED), the key lacks the messages permission, or the key is limited to other devices or chats |
| 404 | Not Found - unknown device, or the media id is unknown or was already sent |
| 409 | Conflict - this clientMessageId was already used for a different message |
| 422 | Unprocessable Entity - the number can't do this (CAPABILITY_UNAVAILABLE), a Meta number needs a template (TEMPLATE_REQUIRED), the template can't be sent as configured (TEMPLATE_SEND_INVALID), or the same wording already reached too many numbers (DUPLICATE_BROADCAST). |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
POST/v1/developer/messages/check-whatsappTells you which phone numbers have WhatsApp, up to 50 per call. Use it to clean a list before sending. It doesn't send anything and doesn't use your send limits. The check runs on a connected linked device; if you pass a Meta number, another connected linked device on your account is used. If none is available, verified is false and every number is reported as existing, so wrong numbers only show up later as failed sends. Needs the numbers permission on the key. A key limited to certain chats can only check those numbers (others return 403 with deniedRecipients).
2 required2 responses
/v1/developer/messages/check-whatsappTells you which phone numbers have WhatsApp, up to 50 per call. Use it to clean a list before sending. It doesn't send anything and doesn't use your send limits. The check runs on a connected linked device; if you pass a Meta number, another connected linked device on your account is used. If none is available, verified is false and every number is reported as existing, so wrong numbers only show up later as failed sends. Needs the numbers permission on the key. A key limited to certain chats can only check those numbers (others return 403 with deniedRecipients).
Request Fields
deviceUid
Requiredstring • body
Which of your numbers runs the check. It must be connected. Copy it from List Devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwnumbers
Requiredstring[], 1-50 items, each 7-15 digits with optional leading + • body
The phone numbers to check, with country code.
Example:["+201001234567", "+15551234567"]| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | body | Yes | Which of your numbers runs the check. It must be connected. Copy it from List Devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| numbers | string[], 1-50 items, each 7-15 digits with optional leading + | body | Yes | The phone numbers to check, with country code.Example: ["+201001234567", "+15551234567"] |
Request Body
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"numbers": ["+201001234567", "+15551234567"]
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/messages/check-whatsapp" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"numbers": ["+201001234567", "+15551234567"]
}'Responses
{
"results": [
{ "phone": "+201001234567", "exists": true },
{ "phone": "+15551234567", "exists": false }
],
"verified": true
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Device wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw is not connected. Current status: DISCONNECTED",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/check-whatsapp"
}Status Codes
Created
Bad Request - more than 50 numbers, a badly formatted number, or the device isn't connected
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - more than 50 numbers, a badly formatted number, or the device isn't connected |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
GET/v1/developer/messagesLists the messages you sent and received, newest first. Use it to show a conversation or reconcile what was delivered. How far back you can see depends on your plan's message history. A key limited to certain devices must pass deviceUid; a key limited to certain chats only sees those chats.
0 required1 response
/v1/developer/messagesLists the messages you sent and received, newest first. Use it to show a conversation or reconcile what was delivered. How far back you can see depends on your plan's message history. A key limited to certain devices must pass deviceUid; a key limited to certain chats only sees those chats.
Request Fields
deviceUid
Optionalstring • query
Only show messages of this number. Copy it from List Devices. Required for keys limited to certain devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwto
Optionalstring • query
Only show messages with this phone number, sent to it or received from it. Part of a number also matches.
Example:+201001234567page
Optionalinteger, from 1 • query
Which page of results to return. Starts at 1.
Example:1limit
Optionalinteger, 1-100 • query
How many messages per page. Default 20.
Example:20| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | query | No | Only show messages of this number. Copy it from List Devices. Required for keys limited to certain devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| to | string | query | No | Only show messages with this phone number, sent to it or received from it. Part of a number also matches.Example: +201001234567 |
| page | integer, from 1 | query | No | Which page of results to return. Starts at 1.Example: 1 |
| limit | integer, 1-100 | query | No | How many messages per page. Default 20.Example: 20 |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/messages" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
"accountId": "1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6",
"accountName": "Sales line",
"to": "201001234567",
"from": null,
"senderName": null,
"text": "Hi Sara, your order #1042 has shipped.",
"caption": null,
"messageType": "TEXT",
"direction": "OUTBOUND",
"status": "DELIVERED",
"providerMessageId": "3EB0C767D26A1D8E4F2B",
"isGroup": false,
"groupId": null,
"groupName": null,
"media": null,
"sentAt": "2026-05-23T10:15:02.000Z",
"createdAt": "2026-05-23T10:15:02.000Z",
"deliveredAt": "2026-05-23T10:15:04.000Z",
"readAt": null,
"failedAt": null,
"editedAt": null,
"deletedAt": null,
"reactions": []
}
],
"total": 1,
"page": 1,
"limit": 20
}Status Codes
Success
Bad Request - the key is limited to certain devices and deviceUid is missing
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - the key is limited to certain devices and deviceUid is missing |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
GET/v1/developer/messages/:idReturns one message with its current status (SENT, DELIVERED, READ or FAILED) and delivery times. Use it after a send to see whether the message arrived. Messages outside your plan's history window, or outside the key's devices or chats, return 404.
1 required1 response
/v1/developer/messages/:idReturns one message with its current status (SENT, DELIVERED, READ or FAILED) and delivery times. Use it after a send to see whether the message arrived. Messages outside your plan's history window, or outside the key's devices or chats, return 404.
Request Fields
id
Requiredstring (UUID) • path
The message to read. Use messageId from a send response, from the scheduled message, or from List Messages.
Example:b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The message to read. Use messageId from a send response, from the scheduled message, or from List Messages.Example: b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/messages/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "c7d8e9f0-2b3c-4d5e-9f01-a2b3c4d5e6f7",
"accountId": "1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6",
"accountName": "Sales line",
"to": "201009876543",
"from": "201001234567",
"senderName": "Sara",
"text": "",
"caption": "Is this the right size?",
"messageType": "MEDIA",
"direction": "INBOUND",
"status": "SENT",
"providerMessageId": "3EB0A1B2C3D4E5F60718",
"isGroup": false,
"groupId": null,
"groupName": null,
"media": {
"id": "3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b",
"kind": "image",
"mimeType": "image/jpeg",
"sizeBytes": 48213,
"durationSec": null,
"width": 1080,
"height": 1350,
"fileName": null,
"contentUrl": "/v1/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b/content",
"downloadUrl": "/v1/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b/download"
},
"sentAt": "2026-05-23T10:15:02.000Z",
"createdAt": "2026-05-23T10:15:03.000Z",
"deliveredAt": null,
"readAt": null,
"failedAt": null,
"editedAt": null,
"deletedAt": null,
"reactions": []
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
Templates
Templates are pre-approved message formats for Meta numbers. Meta only lets you start a conversation, or write to someone who hasn't messaged you in 24 hours, with an approved template. You create templates and get them approved in the dashboard; here you list them so you can send one by passing its id as templateId to Send Message or a campaign. Linked (QR) numbers don't use templates. Needs the templates permission on the key.
GET/v1/developer/templatesLists the approved templates of one Meta number. variableCount is how many placeholders ({{1}}, {{2}}, …) the template body has: send that many values in variables. Check sendable before using a template: some templates can't be sent through the API yet (named variables like {{name}}, image/video/document headers, header variables, URL buttons with a variable, or other unsupported parts). sendable is true when issues is empty; otherwise each issue has a code (TEMPLATE_NAMED_PARAMS_UNSUPPORTED, TEMPLATE_MEDIA_HEADER_UNSUPPORTED, TEMPLATE_HEADER_VARIABLES_UNSUPPORTED, TEMPLATE_URL_BUTTON_VARIABLE_UNSUPPORTED, TEMPLATE_BUTTON_PARAMETERS_UNSUPPORTED, TEMPLATE_COMPONENT_UNSUPPORTED) and a message saying why. Sending such a template returns 422 TEMPLATE_SEND_INVALID with the same issues. A linked device returns 422 CAPABILITY_UNAVAILABLE.
1 required3 responses
/v1/developer/templatesLists the approved templates of one Meta number. variableCount is how many placeholders ({{1}}, {{2}}, …) the template body has: send that many values in variables. Check sendable before using a template: some templates can't be sent through the API yet (named variables like {{name}}, image/video/document headers, header variables, URL buttons with a variable, or other unsupported parts). sendable is true when issues is empty; otherwise each issue has a code (TEMPLATE_NAMED_PARAMS_UNSUPPORTED, TEMPLATE_MEDIA_HEADER_UNSUPPORTED, TEMPLATE_HEADER_VARIABLES_UNSUPPORTED, TEMPLATE_URL_BUTTON_VARIABLE_UNSUPPORTED, TEMPLATE_BUTTON_PARAMETERS_UNSUPPORTED, TEMPLATE_COMPONENT_UNSUPPORTED) and a message saying why. Sending such a template returns 422 TEMPLATE_SEND_INVALID with the same issues. A linked device returns 422 CAPABILITY_UNAVAILABLE.
Request Fields
deviceUid
Requiredstring • query
The Meta number whose templates you want. Copy it from List Devices (a number with capabilities.templates true).
Example:wa_dev_TWV0YU51bWJlcklkMDAwMDAwMDAwMDAw| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | query | Yes | The Meta number whose templates you want. Copy it from List Devices (a number with capabilities.templates true).Example: wa_dev_TWV0YU51bWJlcklkMDAwMDAwMDAwMDAw |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/templates" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"items": [
{
"id": "8b7d1c2e-3f4a-4b5c-9d6e-7f8a9b0c1d2e",
"name": "order_update",
"language": "en_US",
"category": "UTILITY",
"variableCount": 2,
"components": [
{ "type": "BODY", "text": "Hi {{1}}, your order {{2}} has shipped." }
],
"sendable": true,
"issues": []
},
{
"id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"name": "spring_sale",
"language": "en_US",
"category": "MARKETING",
"variableCount": 0,
"components": [
{ "type": "HEADER", "format": "IMAGE" },
{ "type": "BODY", "text": "Our spring sale starts today." }
],
"sendable": false,
"issues": [
{
"code": "TEMPLATE_MEDIA_HEADER_UNSUPPORTED",
"message": "This template has a image header, which is not supported yet. Use a template with a text header or none."
}
]
}
]
}{
"statusCode": 400,
"error": "Bad Request",
"message": "deviceUid is required",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/templates"
}{
"statusCode": 422,
"error": "Capability Unavailable",
"code": "CAPABILITY_UNAVAILABLE",
"capability": "templates",
"message": "This feature isn't available for this number.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/templates"
}Status Codes
Success
Bad Request - deviceUid is missing
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Unprocessable Entity - the device is a linked number, which has no templates (CAPABILITY_UNAVAILABLE)
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - deviceUid is missing |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 422 | Unprocessable Entity - the device is a linked number, which has no templates (CAPABILITY_UNAVAILABLE) |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
Media
Files you want to send: photos, videos, audio and documents. First upload the file, then pass its id as mediaId to Send Media Message or Send Voice Message. Each upload can be sent once: after the send the file is removed and the id stops working (404), so upload once per send. Needs the media permission on the key and the media feature on your plan. Keys limited to certain devices can upload, but can't list, read or delete media, because those cover files received on every number.
POST/v1/developer/media/uploadUploads one file as multipart/form-data (up to 16 MB) and returns its id. Use the id right away in a send. We detect the kind from the file type; pass kind only to override it. durationSec, width and height may be empty. With cURL: curl -X POST https://api.msgera.io/v1/developer/media/upload -H "X-API-Key: wak_your_api_key_here" -F "[email protected]"
1 required2 responses
/v1/developer/media/uploadUploads one file as multipart/form-data (up to 16 MB) and returns its id. Use the id right away in a send. We detect the kind from the file type; pass kind only to override it. durationSec, width and height may be empty. With cURL: curl -X POST https://api.msgera.io/v1/developer/media/upload -H "X-API-Key: wak_your_api_key_here" -F "[email protected]"
Request Fields
file
Requiredfile (multipart/form-data), up to 16 MB • body
The file to upload, sent as the form field named file.
Example:invoice-may.pdfkind
Optionalstring • query
What the file is. Leave it out and we work it out from the file type. Use audio for voice notes.
Allowed:image · video · audio · documentExample: document| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| file | file (multipart/form-data), up to 16 MB | body | Yes | The file to upload, sent as the form field named file.Example: invoice-may.pdf |
| kind | string | query | No | What the file is. Leave it out and we work it out from the file type. Use audio for voice notes.Allowed: image · video · audio · documentExample: document |
Request Examples
curl -X POST "https://api.msgera.io/v1/developer/media/upload" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b",
"kind": "document",
"originalName": "invoice-may.pdf",
"mimeType": "application/pdf",
"sizeBytes": 48213,
"durationSec": null,
"width": null,
"height": null,
"createdAt": "2026-05-23T10:14:50.000Z",
"contentUrl": "/v1/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b/content",
"downloadUrl": "/v1/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b/download"
}{
"statusCode": 413,
"error": "Payload Too Large",
"code": "FILE_TOO_LARGE",
"message": "The file is larger than the 16 MB upload limit.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/media/upload"
}Status Codes
Created
Bad Request - no file was sent, or the file is empty
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include media (PLAN_FEATURE_UNAVAILABLE), or the key lacks the media permission or is limited to certain chats
Payload Too Large - the file is over 16 MB (FILE_TOO_LARGE)
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - no file was sent, or the file is empty |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include media (PLAN_FEATURE_UNAVAILABLE), or the key lacks the media permission or is limited to certain chats |
| 413 | Payload Too Large - the file is over 16 MB (FILE_TOO_LARGE) |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
GET/v1/developer/mediaLists your files, newest first: the ones you uploaded and haven't sent yet, plus files customers sent you. Sent uploads no longer appear.
0 required1 response
/v1/developer/mediaLists your files, newest first: the ones you uploaded and haven't sent yet, plus files customers sent you. Sent uploads no longer appear.
Request Fields
page
Optionalinteger, from 1 • query
Which page of results to return. Starts at 1.
Example:1limit
Optionalinteger, 1-100 • query
How many files per page. Default 20.
Example:20kind
Optionalstring • query
Only show one kind of file. Any other value returns 400.
Allowed:image · video · audio · documentExample: image| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| page | integer, from 1 | query | No | Which page of results to return. Starts at 1.Example: 1 |
| limit | integer, 1-100 | query | No | How many files per page. Default 20.Example: 20 |
| kind | string | query | No | Only show one kind of file. Any other value returns 400.Allowed: image · video · audio · documentExample: image |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/media" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b",
"kind": "document",
"originalName": "invoice-may.pdf",
"mimeType": "application/pdf",
"sizeBytes": 48213,
"durationSec": null,
"width": null,
"height": null,
"createdAt": "2026-05-23T10:14:50.000Z",
"contentUrl": "/v1/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b/content",
"downloadUrl": "/v1/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b/download"
}
],
"total": 1,
"page": 1,
"limit": 20
}Status Codes
Success
Bad Request - kind is not one of image, video, audio, document
Unauthorized - missing or invalid API key
Forbidden - the key lacks the media permission, or is limited to certain devices or chats (those keys can't list, read or delete media)
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - kind is not one of image, video, audio, document |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the key lacks the media permission, or is limited to certain devices or chats (those keys can't list, read or delete media) |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
GET/v1/developer/media/:idReturns the details of one file. contentUrl shows the file and downloadUrl downloads it; both need the same authentication.
1 required1 response
/v1/developer/media/:idReturns the details of one file. contentUrl shows the file and downloadUrl downloads it; both need the same authentication.
Request Fields
id
Requiredstring (UUID) • path
The media file. Use the id returned by Upload Media or List Media.
Example:3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The media file. Use the id returned by Upload Media or List Media.Example: 3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/media/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b",
"kind": "document",
"originalName": "invoice-may.pdf",
"mimeType": "application/pdf",
"sizeBytes": 48213,
"durationSec": null,
"width": null,
"height": null,
"createdAt": "2026-05-23T10:14:50.000Z",
"contentUrl": "/v1/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b/content",
"downloadUrl": "/v1/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b/download"
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the key lacks the media permission, or is limited to certain devices or chats (those keys can't list, read or delete media)
Not Found - the file doesn't exist, isn't yours, or was already sent (sent files are removed)
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the key lacks the media permission, or is limited to certain devices or chats (those keys can't list, read or delete media) |
| 404 | Not Found - the file doesn't exist, isn't yours, or was already sent (sent files are removed) |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
DELETE/v1/developer/media/:idDeletes a file you uploaded but no longer want to send. A file that a message or a queued send still uses can't be deleted (400). You don't need to delete files after sending: that happens automatically.
1 required2 responses
/v1/developer/media/:idDeletes a file you uploaded but no longer want to send. A file that a message or a queued send still uses can't be deleted (400). You don't need to delete files after sending: that happens automatically.
Request Fields
id
Requiredstring (UUID) • path
The media file. Use the id returned by Upload Media or List Media.
Example:3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The media file. Use the id returned by Upload Media or List Media.Example: 3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b |
Request Examples
curl -X DELETE "https://api.msgera.io/v1/developer/media/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{ "deleted": true }{
"statusCode": 400,
"error": "Bad Request",
"message": "Media is already referenced by messages and cannot be deleted",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/media/3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b"
}Status Codes
Success
Bad Request - a message or a queued send still uses this file
Unauthorized - missing or invalid API key
Forbidden - the key lacks the media permission, or is limited to certain devices or chats (those keys can't list, read or delete media)
Not Found - the file doesn't exist, isn't yours, or was already sent (sent files are removed)
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a message or a queued send still uses this file |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the key lacks the media permission, or is limited to certain devices or chats (those keys can't list, read or delete media) |
| 404 | Not Found - the file doesn't exist, isn't yours, or was already sent (sent files are removed) |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
Campaigns
Send one message to many people from one or more numbers, then follow its progress. The same routes work for linked numbers and Meta numbers. Sending is paced to keep linked numbers safe: 15-50 seconds between messages, and every 5th message waits about 2 minutes instead. Meta number campaigns send about one message per second. A campaign whose sends fail 10 times in a row pauses itself; fix the cause, then resume it. Campaign statuses: DRAFT (created, not started), AWAITING_REVIEW (waiting for you to approve message variations), QUEUED (scheduled for later), RUNNING (sending now), PAUSED (paused by you or automatically, see error), COMPLETED (every recipient was handled), FAILED (stopped with an error), RECURRING (a repeating campaign waiting for its next run), RECURRING_PAUSED (repeats on hold), RECURRING_STOPPED (repeats ended for good).
POST/v1/developer/campaignsSends one message to many people. Pick the sending number, the message and the audience (contact groups, tags, contacts and/or phone numbers, combined and de-duplicated). The campaign starts right away unless you schedule it or make it recurring. Sending is paced to protect the number, so large audiences can take hours or days: estimatedCompletion in the response tells you when it should finish. On linked numbers, a text campaign needs one different wording per 8 recipients; if your text doesn't have enough, the campaign waits in AWAITING_REVIEW while variations are written for you to approve (see Campaign Variations). On Meta numbers, choose how people are reached with sendMode; Meta campaigns use one number and skip variations. Meta numbers and linked numbers can't share a campaign.
2 required9 responses
/v1/developer/campaignsSends one message to many people. Pick the sending number, the message and the audience (contact groups, tags, contacts and/or phone numbers, combined and de-duplicated). The campaign starts right away unless you schedule it or make it recurring. Sending is paced to protect the number, so large audiences can take hours or days: estimatedCompletion in the response tells you when it should finish. On linked numbers, a text campaign needs one different wording per 8 recipients; if your text doesn't have enough, the campaign waits in AWAITING_REVIEW while variations are written for you to approve (see Campaign Variations). On Meta numbers, choose how people are reached with sendMode; Meta campaigns use one number and skip variations. Meta numbers and linked numbers can't share a campaign.
Request Fields
deviceUid
Requiredstring • body
The number that sends the campaign. Copy its deviceUid from List Devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwdeviceUids
Optionalstring[] • body
More linked numbers to share the work, so a big campaign finishes sooner. Recipients are split evenly between deviceUid and these. Not for Meta numbers.
Example:["wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw", "wa_dev_U2Vjb25kRGV2aWNlSWQwMDAwMDAwMDAw"]name
Requiredstring • body
A name to recognise the campaign in lists and reports. Recipients never see it.
Example:Summer Saletext
Optionalstring (max 10,000 characters) • body
The message people receive. Use {Hi|Hello} to vary the wording and {firstName} to insert each contact's name. Required unless you send a Meta template (templateId); linked numbers need it even for MEDIA and VOICE campaigns.
Example:{Hi|Hello} {firstName}! Our summer sale starts today.messageType
Optionalstring • body
What kind of message to send. MEDIA (photo, video or document) and VOICE (voice note) need mediaAssetId, a plan with media sending, and a linked number. Defaults to TEXT.
Allowed:TEXT · MEDIA · VOICEExample: TEXTmediaAssetId
Optionalstring • body
The file to send in a MEDIA or VOICE campaign. Upload it first with Upload Media and use the id it returns. Use a fresh upload per campaign: the file is deleted when its campaign is deleted.
Example:5d1c9e8a-2b3f-4a6d-9e0c-7f8a1b2c3d4ecaption
Optionalstring • body
Text shown under the photo, video or document in a MEDIA campaign. {firstName} and other placeholders work here too.
Example:Our new menu, just for you {firstName}contactGroupIds
Optionalstring[] (UUID) • body
Send to everyone in these contact groups. Get group IDs from List Contact Groups.
Example:["3f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11"]tagIds
Optionalstring[] (UUID) • body
Send to every contact with these tags. Get tag IDs from List Contact Tags.
Example:["a9e8d7c6-b5a4-4321-8f0e-d1c2b3a4f5e6"]contactIds
Optionalstring[] (UUID) • body
Send to these individual saved contacts. Get contact IDs from List Contacts. An ID that isn't yours fails the whole request.
Example:["0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e"]phoneNumbers
Optionalstring[] • body
Numbers to message directly, for people who aren't saved as contacts. Use the international format with country code; the format isn't strictly checked here, so a wrong number only shows up later as a FAILED recipient.
Example:["+201001234567", "+971501234567"]scheduledAt
Optionalstring (ISO 8601) • body
Start later instead of now. The campaign waits as QUEUED until this time. Leave it out to start immediately.
Example:2026-06-01T09:00:00ZisRecurring
Optionalboolean • body
Repeat this campaign on a schedule, for example every Monday. Each repeat is a new run sent to the audience as it is at that moment. Needs a plan with recurring campaigns.
Example:truerecurrenceRule
Optionalstring (cron) • body
How often a recurring campaign repeats, as a cron expression (minute hour day month weekday, UTC). Required when isRecurring is true.
Example:0 9 * * 1recurrenceEndAt
Optionalstring (ISO 8601) • body
When a recurring campaign should stop repeating. Leave it out to repeat until you stop it.
Example:2026-12-31T23:59:59ZtemplateId
Optionalstring (UUID) • body
Meta numbers only: the approved template to send. Get IDs from List Templates. Required for sendMode TEMPLATE and AUTO; a linked number returns 422 CAPABILITY_UNAVAILABLE.
Example:6e5d4c3b-2a19-4807-b6f5-e4d3c2b1a090variables
Optionalstring[] • body
Values for the template's {{1}}, {{2}}, ... in order. You can use {firstName}, {lastName}, {name} or {phone} to fill them per contact.
Example:["{firstName}", "20%"]sendMode
Optionalstring • body
Meta numbers only: who gets what. TEMPLATE sends the template to everyone and can be scheduled or recurring. SESSION sends text, and every recipient must have written to you in the last 24 hours. AUTO sends text to people inside that window and the template to everyone else. SESSION and AUTO start immediately. Defaults to TEMPLATE with a templateId, SESSION without.
Allowed:TEMPLATE · SESSION · AUTOExample: AUTOautoApproveVariations
Optionalboolean • body
Start sending as soon as message variations are written, without waiting for you to approve them. If the variations still fall short, the campaign waits for review anyway. Defaults to false.
Example:falseexpectedRecipients
Optionalinteger (1-1,000,000) • body
How many people you expect to reach later, if more than today (a growing group or a recurring campaign). Variations are sized for the larger number so you don't need to review again.
Example:5000variationSegments
Optionalobject[] (max 200) • body
Variations you already reviewed with Draft Variations, so the campaign starts with no review step. Send the segments exactly as returned (edited if you like). They must keep your text unchanged and cover the audience, otherwise 400.
Example:[{ "type": "text", "value": "Hi " }, { "type": "slot", "id": "a1b2c3d4e5", ... }]variationUsageIds
Optionalstring[] • body
The usageIds returned by Draft Variations, so that AI spend is linked to this campaign in your reports.
Example:["9c8b7a6d-5e4f-4321-a0b9-c8d7e6f5a4b3"]templateLanguage
Optionalstring • body
Language for generated variations. Leave it out to match the language of your text.
Allowed:egyptian-arabic · gulf-arabic · standard-arabic · english · simple-englishExample: englishtemplateTone
Optionalstring • body
Tone for generated variations. Leave it out to match the tone of your text.
Allowed:formal · friendly · casual · urgent · persuasive · gratefulExample: friendly| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | body | Yes | The number that sends the campaign. Copy its deviceUid from List Devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| deviceUids | string[] | body | No | More linked numbers to share the work, so a big campaign finishes sooner. Recipients are split evenly between deviceUid and these. Not for Meta numbers.Example: ["wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw", "wa_dev_U2Vjb25kRGV2aWNlSWQwMDAwMDAwMDAw"] |
| name | string | body | Yes | A name to recognise the campaign in lists and reports. Recipients never see it.Example: Summer Sale |
| text | string (max 10,000 characters) | body | No | The message people receive. Use {Hi|Hello} to vary the wording and {firstName} to insert each contact's name. Required unless you send a Meta template (templateId); linked numbers need it even for MEDIA and VOICE campaigns.Example: {Hi|Hello} {firstName}! Our summer sale starts today. |
| messageType | string | body | No | What kind of message to send. MEDIA (photo, video or document) and VOICE (voice note) need mediaAssetId, a plan with media sending, and a linked number. Defaults to TEXT.Allowed: TEXT · MEDIA · VOICEExample: TEXT |
| mediaAssetId | string | body | No | The file to send in a MEDIA or VOICE campaign. Upload it first with Upload Media and use the id it returns. Use a fresh upload per campaign: the file is deleted when its campaign is deleted.Example: 5d1c9e8a-2b3f-4a6d-9e0c-7f8a1b2c3d4e |
| caption | string | body | No | Text shown under the photo, video or document in a MEDIA campaign. {firstName} and other placeholders work here too.Example: Our new menu, just for you {firstName} |
| contactGroupIds | string[] (UUID) | body | No | Send to everyone in these contact groups. Get group IDs from List Contact Groups.Example: ["3f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11"] |
| tagIds | string[] (UUID) | body | No | Send to every contact with these tags. Get tag IDs from List Contact Tags.Example: ["a9e8d7c6-b5a4-4321-8f0e-d1c2b3a4f5e6"] |
| contactIds | string[] (UUID) | body | No | Send to these individual saved contacts. Get contact IDs from List Contacts. An ID that isn't yours fails the whole request.Example: ["0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e"] |
| phoneNumbers | string[] | body | No | Numbers to message directly, for people who aren't saved as contacts. Use the international format with country code; the format isn't strictly checked here, so a wrong number only shows up later as a FAILED recipient.Example: ["+201001234567", "+971501234567"] |
| scheduledAt | string (ISO 8601) | body | No | Start later instead of now. The campaign waits as QUEUED until this time. Leave it out to start immediately.Example: 2026-06-01T09:00:00Z |
| isRecurring | boolean | body | No | Repeat this campaign on a schedule, for example every Monday. Each repeat is a new run sent to the audience as it is at that moment. Needs a plan with recurring campaigns.Example: true |
| recurrenceRule | string (cron) | body | No | How often a recurring campaign repeats, as a cron expression (minute hour day month weekday, UTC). Required when isRecurring is true.Example: 0 9 * * 1 |
| recurrenceEndAt | string (ISO 8601) | body | No | When a recurring campaign should stop repeating. Leave it out to repeat until you stop it.Example: 2026-12-31T23:59:59Z |
| templateId | string (UUID) | body | No | Meta numbers only: the approved template to send. Get IDs from List Templates. Required for sendMode TEMPLATE and AUTO; a linked number returns 422 CAPABILITY_UNAVAILABLE.Example: 6e5d4c3b-2a19-4807-b6f5-e4d3c2b1a090 |
| variables | string[] | body | No | Values for the template's {{1}}, {{2}}, ... in order. You can use {firstName}, {lastName}, {name} or {phone} to fill them per contact.Example: ["{firstName}", "20%"] |
| sendMode | string | body | No | Meta numbers only: who gets what. TEMPLATE sends the template to everyone and can be scheduled or recurring. SESSION sends text, and every recipient must have written to you in the last 24 hours. AUTO sends text to people inside that window and the template to everyone else. SESSION and AUTO start immediately. Defaults to TEMPLATE with a templateId, SESSION without.Allowed: TEMPLATE · SESSION · AUTOExample: AUTO |
| autoApproveVariations | boolean | body | No | Start sending as soon as message variations are written, without waiting for you to approve them. If the variations still fall short, the campaign waits for review anyway. Defaults to false.Example: false |
| expectedRecipients | integer (1-1,000,000) | body | No | How many people you expect to reach later, if more than today (a growing group or a recurring campaign). Variations are sized for the larger number so you don't need to review again.Example: 5000 |
| variationSegments | object[] (max 200) | body | No | Variations you already reviewed with Draft Variations, so the campaign starts with no review step. Send the segments exactly as returned (edited if you like). They must keep your text unchanged and cover the audience, otherwise 400.Example: [{ "type": "text", "value": "Hi " }, { "type": "slot", "id": "a1b2c3d4e5", ... }] |
| variationUsageIds | string[] | body | No | The usageIds returned by Draft Variations, so that AI spend is linked to this campaign in your reports.Example: ["9c8b7a6d-5e4f-4321-a0b9-c8d7e6f5a4b3"] |
| templateLanguage | string | body | No | Language for generated variations. Leave it out to match the language of your text.Allowed: egyptian-arabic · gulf-arabic · standard-arabic · english · simple-englishExample: english |
| templateTone | string | body | No | Tone for generated variations. Leave it out to match the tone of your text.Allowed: formal · friendly · casual · urgent · persuasive · gratefulExample: friendly |
Request Body
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"deviceUids": ["wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw", "wa_dev_U2Vjb25kRGV2aWNlSWQwMDAwMDAwMDAw"],
"name": "Summer Sale",
"text": "{Hi|Hello} {firstName}! Our summer sale starts today.",
"contactGroupIds": ["3f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11"],
"phoneNumbers": ["+201001234567"],
"templateLanguage": "english",
"templateTone": "friendly"
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"deviceUids": ["wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw", "wa_dev_U2Vjb25kRGV2aWNlSWQwMDAwMDAwMDAw"],
"name": "Summer Sale",
"text": "{Hi|Hello} {firstName}! Our summer sale starts today.",
"contactGroupIds": ["3f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11"],
"phoneNumbers": ["+201001234567"],
"templateLanguage": "english",
"templateTone": "friendly"
}'Responses
{
"id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"accountIds": [
"1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e"
],
"name": "Summer Sale",
"text": "{Hi|Hello} {firstName}! Our summer sale starts today.",
"spintaxTemplate": null,
"templateLanguage": "english",
"templateTone": "friendly",
"variationStatus": "NONE",
"variationSegments": null,
"variationRequired": 0,
"autoApproveVariations": false,
"expectedRecipients": null,
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"metaTemplateId": null,
"metaVariables": [],
"status": "RUNNING",
"consecutiveFailures": 0,
"totalRecipients": 16,
"sentCount": 0,
"failedCount": 0,
"scheduledAt": null,
"timezone": null,
"error": null,
"startedAt": "2026-05-22T10:00:00.000Z",
"completedAt": null,
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z",
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T10:00:00.000Z",
"isRecurring": false,
"recurrenceRule": null,
"recurrenceEndAt": null,
"nextRunAt": null,
"parentCampaignId": null,
"runNumber": null,
"recipientSource": null,
"_count": {
"recipients": 16
},
"message": "Campaign started",
"estimatedCompletion": {
"startsAt": "2026-05-22T10:00:00.000Z",
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z",
"estimatedDurationMs": 570000,
"estimatedDurationLabel": "~10 minutes",
"calendarDaysSpanned": 1,
"accountCount": 2,
"perAccount": [
{
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"recipientCount": 8,
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z"
},
{
"accountId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e",
"recipientCount": 8,
"estimatedCompletionAt": "2026-05-22T10:09:10.000Z"
}
]
}
}{
"id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"status": "QUEUED",
"message": "Campaign scheduled for later",
"scheduledAt": "2026-06-01T09:00:00.000Z"
}{
"id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"status": "AWAITING_REVIEW",
"variationStatus": "GENERATING",
"message": "Message variations are being generated for your review",
"totalRecipients": 150
}{
"id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"status": "RECURRING",
"isRecurring": true,
"recurrenceRule": "0 9 * * 1",
"nextRunAt": "2026-05-25T09:00:00.000Z",
"totalRecipients": 0,
"estimatedCompletion": {
"startsAt": "2026-05-22T10:00:00.000Z",
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z",
"estimatedDurationMs": 570000,
"estimatedDurationLabel": "~10 minutes",
"calendarDaysSpanned": 1,
"accountCount": 2,
"perAccount": [
{
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"recipientCount": 8,
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z"
},
{
"accountId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e",
"recipientCount": 8,
"estimatedCompletionAt": "2026-05-22T10:09:10.000Z"
}
]
}
}{
"id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"messageType": "AUTO",
"metaTemplateId": "6e5d4c3b-2a19-4807-b6f5-e4d3c2b1a090",
"totalRecipients": 120,
"status": "RUNNING",
"message": "Campaign started",
"metaRouting": {
"templateTotal": 85,
"sessionTotal": 35
}
}{
"statusCode": 400,
"error": "Bad Request",
"code": "CAMPAIGN_ACCOUNTS_MIXED",
"message": "Meta numbers and linked numbers can't send in the same campaign. Create one campaign per kind of number.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/campaigns"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Approved options produce 12 unique messages but 19 are required for this audience",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "All Meta campaign recipients must have recorded WhatsApp opt-in",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns"
}{
"statusCode": 422,
"error": "Capability Unavailable",
"code": "CAPABILITY_UNAVAILABLE",
"capability": "templates",
"message": "This feature isn't available for this number.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/campaigns"
}Status Codes
Created
Bad Request - no recipients, no text, an invalid cron rule, a number that isn't connected, mixed Meta and linked numbers (CAMPAIGN_ACCOUNTS_MIXED), Meta recipients missing opt-in or outside the 24-hour window, or variationSegments that change your text or fall short
Unauthorized - missing or invalid API key
Forbidden - a plan limit is used up (PLAN_LIMIT_EXCEEDED: campaigns this month, active campaigns, recipients per campaign, recurring schedules), the plan lacks a feature (PLAN_FEATURE_UNAVAILABLE: recurring campaigns, media, spintax), or the API key can't use this device or the campaigns service
Not Found - a deviceUid, contact or media file isn't yours
Unprocessable Entity - a template on a linked number or media on a Meta number (CAPABILITY_UNAVAILABLE), or a template whose variables don't fit (TEMPLATE_SEND_INVALID)
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - no recipients, no text, an invalid cron rule, a number that isn't connected, mixed Meta and linked numbers (CAMPAIGN_ACCOUNTS_MIXED), Meta recipients missing opt-in or outside the 24-hour window, or variationSegments that change your text or fall short |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - a plan limit is used up (PLAN_LIMIT_EXCEEDED: campaigns this month, active campaigns, recipients per campaign, recurring schedules), the plan lacks a feature (PLAN_FEATURE_UNAVAILABLE: recurring campaigns, media, spintax), or the API key can't use this device or the campaigns service |
| 404 | Not Found - a deviceUid, contact or media file isn't yours |
| 422 | Unprocessable Entity - a template on a linked number or media on a Meta number (CAPABILITY_UNAVAILABLE), or a template whose variables don't fit (TEMPLATE_SEND_INVALID) |
| 500 | Internal Server Error |
GET/v1/developer/campaignsLists your campaigns, newest first. By default each recurring campaign appears once; set parentOnly=false to also list each of its runs. Keys limited to certain devices only see campaigns from those devices.
0 required1 response
/v1/developer/campaignsLists your campaigns, newest first. By default each recurring campaign appears once; set parentOnly=false to also list each of its runs. Keys limited to certain devices only see campaigns from those devices.
Request Fields
page
Optionalinteger • query
Which page of results to return, starting at 1. Defaults to 1.
Example:1limit
Optionalinteger • query
How many results per page. Defaults to 20.
Example:20parentOnly
Optionalboolean • query
Hide the individual runs of recurring campaigns and show only the campaigns you created. Defaults to true.
Example:true| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| page | integer | query | No | Which page of results to return, starting at 1. Defaults to 1.Example: 1 |
| limit | integer | query | No | How many results per page. Defaults to 20.Example: 20 |
| parentOnly | boolean | query | No | Hide the individual runs of recurring campaigns and show only the campaigns you created. Defaults to true.Example: true |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/campaigns" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"accountIds": [
"1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e"
],
"name": "Summer Sale",
"text": "{Hi|Hello} {firstName}! Our summer sale starts today.",
"spintaxTemplate": null,
"templateLanguage": "english",
"templateTone": "friendly",
"variationStatus": "NONE",
"variationSegments": null,
"variationRequired": 0,
"autoApproveVariations": false,
"expectedRecipients": null,
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"metaTemplateId": null,
"metaVariables": [],
"status": "RUNNING",
"consecutiveFailures": 0,
"totalRecipients": 16,
"sentCount": 0,
"failedCount": 0,
"scheduledAt": null,
"timezone": null,
"error": null,
"startedAt": "2026-05-22T10:00:00.000Z",
"completedAt": null,
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z",
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T10:00:00.000Z",
"isRecurring": false,
"recurrenceRule": null,
"recurrenceEndAt": null,
"nextRunAt": null,
"parentCampaignId": null,
"runNumber": null,
"recipientSource": null,
"accountName": "Sales Line 1",
"_count": {
"childRuns": 0
}
}
],
"total": 1,
"page": 1,
"limit": 20
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 500 | Internal Server Error |
GET/v1/developer/campaigns/:idReturns everything about one campaign: its message, status, counters, schedule and the numbers sending it (accounts). Use it to show a campaign's details; for live counters use Get Campaign Progress.
1 required1 response
/v1/developer/campaigns/:idReturns everything about one campaign: its message, status, counters, schedule and the numbers sending it (accounts). Use it to show a campaign's details; for live counters use Get Campaign Progress.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/campaigns/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"accountIds": [
"1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e"
],
"name": "Summer Sale",
"text": "{Hi|Hello} {firstName}! Our summer sale starts today.",
"spintaxTemplate": null,
"templateLanguage": "english",
"templateTone": "friendly",
"variationStatus": "NONE",
"variationSegments": null,
"variationRequired": 0,
"autoApproveVariations": false,
"expectedRecipients": null,
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"metaTemplateId": null,
"metaVariables": [],
"status": "RUNNING",
"consecutiveFailures": 0,
"totalRecipients": 16,
"sentCount": 6,
"failedCount": 0,
"scheduledAt": null,
"timezone": null,
"error": null,
"startedAt": "2026-05-22T10:00:00.000Z",
"completedAt": null,
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z",
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T10:00:00.000Z",
"isRecurring": false,
"recurrenceRule": null,
"recurrenceEndAt": null,
"nextRunAt": null,
"parentCampaignId": null,
"runNumber": null,
"recipientSource": null,
"_count": {
"recipients": 16,
"childRuns": 0
},
"accountName": "Sales Line 1",
"accounts": [
{
"id": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"name": "Sales Line 1",
"phone": "+201001234567"
},
{
"id": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e",
"name": "Sales Line 2",
"phone": "+201009876543"
}
],
"parentCampaignName": null
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
GET/v1/developer/campaigns/:id/progressQuick counters for a campaign that is sending: how many were sent, failed or are still waiting, and when it should finish. Cheap enough to poll every few seconds. pendingCount is waiting in the queue; dispatchingCount is being handed to WhatsApp right now; dispatchedCount was handed over and awaits confirmation; unknownCount was never confirmed (check before resending).
1 required1 response
/v1/developer/campaigns/:id/progressQuick counters for a campaign that is sending: how many were sent, failed or are still waiting, and when it should finish. Cheap enough to poll every few seconds. pendingCount is waiting in the queue; dispatchingCount is being handed to WhatsApp right now; dispatchedCount was handed over and awaits confirmation; unknownCount was never confirmed (check before resending).
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/campaigns/{id}/progress" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"unknownCount": 0,
"dispatchingCount": 1,
"dispatchedCount": 0,
"status": "RUNNING",
"totalRecipients": 16,
"sentCount": 6,
"failedCount": 1,
"pendingCount": 8,
"startedAt": "2026-05-22T10:00:00.000Z",
"completedAt": null,
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z",
"accountIds": [
"1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e"
]
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
GET/v1/developer/campaigns/:id/analyticsThe delivery funnel: how many messages were sent, delivered, read and failed, with rates in percent (deliveryRate = delivered / sent, readRate = read / delivered, failureRate = failed / all recipients). durationSec is how long the campaign took once it completed. For Meta AUTO campaigns, metaRouting splits the numbers between people who got the template and people who got the free-form text.
1 required2 responses
/v1/developer/campaigns/:id/analyticsThe delivery funnel: how many messages were sent, delivered, read and failed, with rates in percent (deliveryRate = delivered / sent, readRate = read / delivered, failureRate = failed / all recipients). durationSec is how long the campaign took once it completed. For Meta AUTO campaigns, metaRouting splits the numbers between people who got the template and people who got the free-form text.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/campaigns/{id}/analytics" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"status": "RUNNING",
"totalRecipients": 16,
"sent": 6,
"delivered": 5,
"read": 2,
"failed": 1,
"pending": 8,
"unknown": 0,
"dispatching": 1,
"dispatched": 0,
"deliveryRate": 83.3,
"readRate": 40,
"failureRate": 6.3,
"startedAt": "2026-05-22T10:00:00.000Z",
"completedAt": null,
"estimatedCompletionAt": "2026-05-22T10:09:30.000Z",
"durationSec": null
}{
"status": "COMPLETED",
"totalRecipients": 120,
"sent": 117,
"delivered": 110,
"read": 64,
"failed": 3,
"pending": 0,
"unknown": 0,
"dispatching": 0,
"dispatched": 0,
"deliveryRate": 94,
"readRate": 58.2,
"failureRate": 2.5,
"startedAt": "2026-05-22T10:00:00.000Z",
"completedAt": "2026-05-22T10:02:10.000Z",
"estimatedCompletionAt": null,
"durationSec": 130,
"metaRouting": {
"templateTotal": 85,
"sessionTotal": 35,
"templateSent": 83,
"sessionSent": 34,
"templateFailed": 2,
"sessionFailed": 1,
"templatePending": 0,
"sessionPending": 0
}
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
GET/v1/developer/campaigns/:id/recipientsOne row per person in the campaign with where their message is. Rows waiting to send come first (in queue order), then sent ones by send time, then failures. messageStatus is the value to show: PENDING (waiting in the queue), DISPATCHING (being handed to WhatsApp), DISPATCHED (handed over, waiting for confirmation), UNKNOWN (never confirmed; check before resending), SENT, DELIVERED, READ, FAILED (see error and errorCode). errorCode groups failures: INVALID_NUMBER, TEMPLATE_REJECTED, OPTED_OUT, VARIABLE_MISSING, PROVIDER_ERROR, UNCONFIRMED, OTHER. accountId/accountName/accountPhone is the number that sent (or will send) to this person. metaDeliveryMode says whether a Meta AUTO campaign used the TEMPLATE or the SESSION text for them.
1 required1 response
/v1/developer/campaigns/:id/recipientsOne row per person in the campaign with where their message is. Rows waiting to send come first (in queue order), then sent ones by send time, then failures. messageStatus is the value to show: PENDING (waiting in the queue), DISPATCHING (being handed to WhatsApp), DISPATCHED (handed over, waiting for confirmation), UNKNOWN (never confirmed; check before resending), SENT, DELIVERED, READ, FAILED (see error and errorCode). errorCode groups failures: INVALID_NUMBER, TEMPLATE_REJECTED, OPTED_OUT, VARIABLE_MISSING, PROVIDER_ERROR, UNCONFIRMED, OTHER. accountId/accountName/accountPhone is the number that sent (or will send) to this person. metaDeliveryMode says whether a Meta AUTO campaign used the TEMPLATE or the SESSION text for them.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0dpage
Optionalinteger • query
Which page of results to return, starting at 1. Defaults to 1.
Example:1limit
Optionalinteger • query
How many results per page. Defaults to 20.
Example:20status
Optionalstring • query
Show only recipients in this state, for example FAILED to see who didn't get the message.
Allowed:PENDING · DISPATCHING · DISPATCHED · UNKNOWN · SENT · DELIVERED · READ · FAILEDExample: FAILED| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
| page | integer | query | No | Which page of results to return, starting at 1. Defaults to 1.Example: 1 |
| limit | integer | query | No | How many results per page. Defaults to 20.Example: 20 |
| status | string | query | No | Show only recipients in this state, for example FAILED to see who didn't get the message.Allowed: PENDING · DISPATCHING · DISPATCHED · UNKNOWN · SENT · DELIVERED · READ · FAILEDExample: FAILED |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/campaigns/{id}/recipients" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "e3f4a5b6-c7d8-4e9f-a0b1-c2d3e4f5a6b7",
"campaignId": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"accountId": "7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e",
"accountName": "Sales Line 2",
"accountPhone": "+201009876543",
"phone": "+971501234567",
"contactName": "Jane",
"status": "FAILED",
"providerMessageId": null,
"messageId": null,
"error": "This phone number is not registered on WhatsApp.",
"errorCode": "INVALID_NUMBER",
"sentAt": "2026-05-22T10:03:12.000Z",
"createdAt": "2026-05-22T10:00:00.000Z",
"messageStatus": "FAILED",
"deliveredAt": null,
"readAt": null,
"failedAt": null,
"metaDeliveryMode": null
}
],
"total": 1,
"page": 1,
"limit": 20
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/campaigns/:id/pauseStops a sending campaign. Messages not yet sent stay waiting; nothing is lost. Only works while the campaign is RUNNING. Use Resume Campaign to continue.
1 required2 responses
/v1/developer/campaigns/:id/pauseStops a sending campaign. Messages not yet sent stay waiting; nothing is lost. Only works while the campaign is RUNNING. Use Resume Campaign to continue.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/{id}/pause" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"status": "PAUSED"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Only RUNNING campaigns can be paused",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/pause"
}Status Codes
Paused
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Paused |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/campaigns/:id/resumeContinues a PAUSED campaign from where it stopped, including one that paused itself. Needs a free active-campaign slot on your plan.
1 required2 responses
/v1/developer/campaigns/:id/resumeContinues a PAUSED campaign from where it stopped, including one that paused itself. Needs a free active-campaign slot on your plan.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/{id}/resume" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"status": "RUNNING",
"message": "Campaign resumed"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Only PAUSED campaigns can be resumed",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/resume"
}Status Codes
Resumed
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - too many campaigns are already active on your plan (PLAN_LIMIT_EXCEEDED), or the API key can't use this campaign
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Resumed |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - too many campaigns are already active on your plan (PLAN_LIMIT_EXCEEDED), or the API key can't use this campaign |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/campaigns/:id/retry-failedTries again for everyone whose message FAILED: they go back to the queue and the campaign starts sending again. Works on COMPLETED, PAUSED and FAILED campaigns. Fix the cause first (for example a disconnected number), or they may fail again.
1 required2 responses
/v1/developer/campaigns/:id/retry-failedTries again for everyone whose message FAILED: they go back to the queue and the campaign starts sending again. Works on COMPLETED, PAUSED and FAILED campaigns. Fix the cause first (for example a disconnected number), or they may fail again.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/{id}/retry-failed" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"status": "RUNNING",
"message": "Retrying failed recipients"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Can only retry failed recipients on COMPLETED, PAUSED, or FAILED campaigns",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/retry-failed"
}Status Codes
Retry started
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - too many campaigns are already active on your plan (PLAN_LIMIT_EXCEEDED), or the API key can't use this campaign
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Retry started |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - too many campaigns are already active on your plan (PLAN_LIMIT_EXCEEDED), or the API key can't use this campaign |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/campaigns/:id/pause-recurringPuts a repeating campaign on hold: no new runs start until you resume it. A run that is already sending keeps going (pause that run with Pause Campaign). Only works while the campaign is RECURRING.
1 required2 responses
/v1/developer/campaigns/:id/pause-recurringPuts a repeating campaign on hold: no new runs start until you resume it. A run that is already sending keeps going (pause that run with Pause Campaign). Only works while the campaign is RECURRING.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/{id}/pause-recurring" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"status": "RECURRING_PAUSED"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Only RECURRING campaigns can be paused",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/pause-recurring"
}Status Codes
Repeats on hold
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Repeats on hold |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/campaigns/:id/resume-recurringRestarts the repeats of a RECURRING_PAUSED campaign. The next run is the next time its schedule matches from now; missed runs are not sent. Needs a plan with recurring campaigns.
1 required2 responses
/v1/developer/campaigns/:id/resume-recurringRestarts the repeats of a RECURRING_PAUSED campaign. The next run is the next time its schedule matches from now; missed runs are not sent. Needs a plan with recurring campaigns.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/{id}/resume-recurring" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"status": "RECURRING"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Only RECURRING_PAUSED campaigns can be resumed",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/resume-recurring"
}Status Codes
Repeats resumed
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include recurring campaigns (PLAN_FEATURE_UNAVAILABLE), or the API key can't use this campaign
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Repeats resumed |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include recurring campaigns (PLAN_FEATURE_UNAVAILABLE), or the API key can't use this campaign |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/campaigns/:id/stop-recurringEnds a repeating campaign for good: no more runs. Works on RECURRING and RECURRING_PAUSED campaigns, and can't be undone. Past runs stay in your list. After stopping you can delete it.
1 required2 responses
/v1/developer/campaigns/:id/stop-recurringEnds a repeating campaign for good: no more runs. Works on RECURRING and RECURRING_PAUSED campaigns, and can't be undone. Past runs stay in your list. After stopping you can delete it.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/{id}/stop-recurring" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"status": "RECURRING_STOPPED"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Only RECURRING or RECURRING_PAUSED campaigns can be stopped",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/stop-recurring"
}Status Codes
Repeats stopped
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Repeats stopped |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
GET/v1/developer/campaigns/:id/runsLists every run a recurring campaign has sent so far, newest first. Each run is a normal campaign (named "<name> - Run #N") that you can open with Get Campaign, Get Campaign Progress or Get Campaign Recipients. Returns 400 for a campaign that doesn't repeat.
1 required2 responses
/v1/developer/campaigns/:id/runsLists every run a recurring campaign has sent so far, newest first. Each run is a normal campaign (named "<name> - Run #N") that you can open with Get Campaign, Get Campaign Progress or Get Campaign Recipients. Returns 400 for a campaign that doesn't repeat.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0dpage
Optionalinteger • query
Which page of results to return, starting at 1. Defaults to 1.
Example:1limit
Optionalinteger • query
How many runs per page. Defaults to 20, at most 100.
Example:20| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
| page | integer | query | No | Which page of results to return, starting at 1. Defaults to 1.Example: 1 |
| limit | integer | query | No | How many runs per page. Defaults to 20, at most 100.Example: 20 |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/campaigns/{id}/runs" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "4c5d6e7f-8a9b-4c0d-9e1f-2a3b4c5d6e7f",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"accountIds": [
"1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"7a8b9c0d-1e2f-4a3b-8c4d-5e6f7a8b9c0e"
],
"name": "Weekly Offers - Run #3",
"text": "{Hi|Hello} {firstName}! Our summer sale starts today.",
"spintaxTemplate": null,
"templateLanguage": "english",
"templateTone": "friendly",
"variationStatus": "NONE",
"variationSegments": null,
"variationRequired": 0,
"autoApproveVariations": false,
"expectedRecipients": null,
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"metaTemplateId": null,
"metaVariables": [],
"status": "COMPLETED",
"consecutiveFailures": 0,
"totalRecipients": 16,
"sentCount": 16,
"failedCount": 0,
"scheduledAt": null,
"timezone": null,
"error": null,
"startedAt": "2026-05-25T09:00:00.000Z",
"completedAt": "2026-05-25T09:10:02.000Z",
"estimatedCompletionAt": "2026-05-25T09:09:30.000Z",
"createdAt": "2026-05-25T09:00:00.000Z",
"updatedAt": "2026-05-25T09:10:02.000Z",
"isRecurring": false,
"recurrenceRule": null,
"recurrenceEndAt": null,
"nextRunAt": null,
"parentCampaignId": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"runNumber": 3,
"recipientSource": null
}
],
"total": 3,
"page": 1,
"limit": 20
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Campaign is not recurring",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/runs"
}Status Codes
Success
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
DELETE/v1/developer/campaigns/:idDeletes a campaign and its recipient list. Allowed only when nothing is sending or waiting to repeat: DRAFT, AWAITING_REVIEW (use it to discard variations you don't want), COMPLETED, FAILED or RECURRING_STOPPED. RUNNING, QUEUED and PAUSED campaigns can't be deleted, and a repeating campaign must be stopped first. Its media file is deleted too. Runs of a deleted recurring campaign stay in your list.
1 required2 responses
/v1/developer/campaigns/:idDeletes a campaign and its recipient list. Allowed only when nothing is sending or waiting to repeat: DRAFT, AWAITING_REVIEW (use it to discard variations you don't want), COMPLETED, FAILED or RECURRING_STOPPED. RUNNING, QUEUED and PAUSED campaigns can't be deleted, and a repeating campaign must be stopped first. Its media file is deleted too. Runs of a deleted recurring campaign stay in your list.
Request Fields
id
Requiredstring (UUID) • path
The campaign to work with. It's the id returned by Create Campaign or List Campaigns.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign to work with. It's the id returned by Create Campaign or List Campaigns.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X DELETE "https://api.msgera.io/v1/developer/campaigns/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"deleted": true
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Only DRAFT, AWAITING_REVIEW, COMPLETED, FAILED, or RECURRING_STOPPED campaigns can be deleted",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d"
}Status Codes
Success
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
Campaign Variations
WhatsApp is more likely to ban a linked number that sends identical text to many people, so a linked-number text campaign needs one different wording per 8 recipients. When your text (including your own {Hi|Hello} spintax) doesn't have enough, the campaign is created as AWAITING_REVIEW and AI writes variations; nothing is sent until you approve them. Your text is split into fixed text and swappable phrases (slots). Each slot keeps your own phrase as the ORIGINAL option, next to AI options and options you add or edit (USER). capacity is how many different messages the approved options make; it must reach required before you can approve. Writing variations spends AI credits from your monthly pool; variations you approved before for the exact same text are reused for free (you still approve them unless autoApproveVariations is on). Instead of polling, listen for the campaign.variations_ready and campaign.variations_failed webhooks. To skip this step, use Draft Variations before creating the campaign. To give up on a campaign in review, delete it.
POST/v1/developer/campaigns/variations/draftWrites different wordings of your message before you create the campaign, so you can review them in your own app and launch a campaign that starts straight away. A linked-number text campaign needs one wording per 8 recipients; for 8 recipients or fewer nothing is generated and you get your text back as APPROVED. The response splits your text into fixed text and swappable phrases (slots); each slot keeps your own phrase as the ORIGINAL option next to the AI options. Review it the same way as Edit Variations (approve or reject options, edit or add your own, never change the ORIGINAL, keep the {placeholders}), then call Create Campaign with the segments as variationSegments and usageIds as variationUsageIds. If you approved variations for this exact message before, they come back with reused: true at no cost. Otherwise each call spends AI credits from your monthly pool (403 PLAN_LIMIT_EXCEEDED when it's empty); your plan must include spintax (403 PLAN_FEATURE_UNAVAILABLE).
2 required3 responses
/v1/developer/campaigns/variations/draftWrites different wordings of your message before you create the campaign, so you can review them in your own app and launch a campaign that starts straight away. A linked-number text campaign needs one wording per 8 recipients; for 8 recipients or fewer nothing is generated and you get your text back as APPROVED. The response splits your text into fixed text and swappable phrases (slots); each slot keeps your own phrase as the ORIGINAL option next to the AI options. Review it the same way as Edit Variations (approve or reject options, edit or add your own, never change the ORIGINAL, keep the {placeholders}), then call Create Campaign with the segments as variationSegments and usageIds as variationUsageIds. If you approved variations for this exact message before, they come back with reused: true at no cost. Otherwise each call spends AI credits from your monthly pool (403 PLAN_LIMIT_EXCEEDED when it's empty); your plan must include spintax (403 PLAN_FEATURE_UNAVAILABLE).
Request Fields
message
Requiredstring (1-4,096 characters) • body
The campaign message you want variations for. Send exactly the same text later as text on Create Campaign.
Example:Hi {firstName}, our summer sale starts today!recipientCount
Requiredinteger (1-1,000,000) • body
How many people the campaign will reach. It decides how many different wordings are needed (one per 8 people).
Example:32language
Optionalstring • body
Language to write the variations in. Leave it out to match your message.
Allowed:egyptian-arabic · gulf-arabic · standard-arabic · english · simple-englishExample: englishtone
Optionalstring • body
Tone of the variations. Leave it out to match your message.
Allowed:formal · friendly · casual · urgent · persuasive · gratefulExample: friendlyfresh
Optionalboolean • body
Write new variations even if you approved some for this message before. Costs AI credits. Defaults to false.
Example:falsesegments
Optionalobject[] (max 200) • body
The segments from a previous draft, as you have them now. Required whenever you send slotIds: without it the call silently writes (and bills) a completely new draft.
Example:[{ "type": "slot", "id": "a1b2c3d4e5", "label": "Hi {firstName}", "options": [...] }, ...]slotIds
Optionalstring[] (max 20) • body
Rewrite only these phrases of the given segments, for example ones you didn't like. Use the slot ids from segments. Your ORIGINAL and your own (USER) options in those slots are kept.
Example:["c1d2e3f4a5"]| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| message | string (1-4,096 characters) | body | Yes | The campaign message you want variations for. Send exactly the same text later as text on Create Campaign.Example: Hi {firstName}, our summer sale starts today! |
| recipientCount | integer (1-1,000,000) | body | Yes | How many people the campaign will reach. It decides how many different wordings are needed (one per 8 people).Example: 32 |
| language | string | body | No | Language to write the variations in. Leave it out to match your message.Allowed: egyptian-arabic · gulf-arabic · standard-arabic · english · simple-englishExample: english |
| tone | string | body | No | Tone of the variations. Leave it out to match your message.Allowed: formal · friendly · casual · urgent · persuasive · gratefulExample: friendly |
| fresh | boolean | body | No | Write new variations even if you approved some for this message before. Costs AI credits. Defaults to false.Example: false |
| segments | object[] (max 200) | body | No | The segments from a previous draft, as you have them now. Required whenever you send slotIds: without it the call silently writes (and bills) a completely new draft.Example: [{ "type": "slot", "id": "a1b2c3d4e5", "label": "Hi {firstName}", "options": [...] }, ...] |
| slotIds | string[] (max 20) | body | No | Rewrite only these phrases of the given segments, for example ones you didn't like. Use the slot ids from segments. Your ORIGINAL and your own (USER) options in those slots are kept.Example: ["c1d2e3f4a5"] |
Request Body
{
"message": "Hi {firstName}, our summer sale starts today!",
"recipientCount": 32,
"language": "english",
"tone": "friendly"
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/variations/draft" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"message": "Hi {firstName}, our summer sale starts today!",
"recipientCount": 32,
"language": "english",
"tone": "friendly"
}'Responses
{
"status": "PENDING_REVIEW",
"required": 4,
"capacity": 6,
"originalText": "Hi {firstName}, our summer sale starts today!",
"segments": [
{
"type": "slot",
"id": "a1b2c3d4e5",
"label": "Hi {firstName}",
"options": [
{ "id": "f6a7b8c9d0", "text": "Hi {firstName}", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "e1f2a3b4c5", "text": "Hello {firstName}", "status": "APPROVED", "source": "AI" },
{ "id": "d6e7f8a9b0", "text": "Hey {firstName}", "status": "APPROVED", "source": "AI" }
]
},
{ "type": "text", "value": ", " },
{
"type": "slot",
"id": "c1d2e3f4a5",
"label": "our summer sale starts today!",
"options": [
{ "id": "b6c7d8e9f0", "text": "our summer sale starts today!", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "a0b1c2d3e4", "text": "the summer sale is live now!", "status": "APPROVED", "source": "AI" }
]
}
],
"samples": [
"Hey {firstName}, the summer sale is live now!",
"Hi {firstName}, our summer sale starts today!"
],
"usageIds": ["9c8b7a6d-5e4f-4321-a0b9-c8d7e6f5a4b3"]
}{
"status": "PENDING_REVIEW",
"required": 4,
"capacity": 6,
"originalText": "Hi {firstName}, our summer sale starts today!",
"segments": [
{
"type": "slot",
"id": "a1b2c3d4e5",
"label": "Hi {firstName}",
"options": [
{ "id": "f6a7b8c9d0", "text": "Hi {firstName}", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "e1f2a3b4c5", "text": "Hello {firstName}", "status": "APPROVED", "source": "AI" },
{ "id": "d6e7f8a9b0", "text": "Hey {firstName}", "status": "APPROVED", "source": "AI" }
]
},
{ "type": "text", "value": ", " },
{
"type": "slot",
"id": "c1d2e3f4a5",
"label": "our summer sale starts today!",
"options": [
{ "id": "b6c7d8e9f0", "text": "our summer sale starts today!", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "a0b1c2d3e4", "text": "the summer sale is live now!", "status": "APPROVED", "source": "AI" }
]
}
],
"samples": ["Hello {firstName}, our summer sale starts today!"],
"reused": true,
"usageIds": []
}{
"status": "APPROVED",
"required": 1,
"capacity": 1,
"originalText": "Hi {firstName}, our summer sale starts today!",
"segments": [
{ "type": "text", "value": "Hi {firstName}, our summer sale starts today!" }
],
"samples": ["Hi {firstName}, our summer sale starts today!"]
}Status Codes
Created - the draft is in the body
Bad Request - a field is invalid, the segments don't match the message, or none of the slotIds exist
Unauthorized - missing or invalid API key
Forbidden - AI credits are used up (PLAN_LIMIT_EXCEEDED), the plan lacks spintax (PLAN_FEATURE_UNAVAILABLE), or the API key lacks the campaigns permission
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created - the draft is in the body |
| 400 | Bad Request - a field is invalid, the segments don't match the message, or none of the slotIds exist |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - AI credits are used up (PLAN_LIMIT_EXCEEDED), the plan lacks spintax (PLAN_FEATURE_UNAVAILABLE), or the API key lacks the campaigns permission |
| 500 | Internal Server Error |
GET/v1/developer/campaigns/:id/variationsShows the variations of a campaign so you can review them. status is GENERATING (still being written), PENDING_REVIEW (ready for you), APPROVED, FAILED (see error; try Regenerate Variations) or NONE (this campaign didn't need any). samples are a few example messages built from the approved options.
1 required1 response
/v1/developer/campaigns/:id/variationsShows the variations of a campaign so you can review them. status is GENERATING (still being written), PENDING_REVIEW (ready for you), APPROVED, FAILED (see error; try Regenerate Variations) or NONE (this campaign didn't need any). samples are a few example messages built from the approved options.
Request Fields
id
Requiredstring (UUID) • path
The campaign waiting for review. It's the id returned by Create Campaign.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign waiting for review. It's the id returned by Create Campaign.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/campaigns/{id}/variations" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"campaignId": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"status": "PENDING_REVIEW",
"required": 4,
"capacity": 4,
"originalText": "Hi {firstName}, our summer sale starts today!",
"segments": [
{
"type": "slot",
"id": "a1b2c3d4e5",
"label": "Hi {firstName}",
"options": [
{ "id": "f6a7b8c9d0", "text": "Hi {firstName}", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "e1f2a3b4c5", "text": "Hello {firstName}", "status": "APPROVED", "source": "AI" },
{ "id": "d6e7f8a9b0", "text": "Hey {firstName}", "status": "REJECTED", "source": "AI" }
]
},
{ "type": "text", "value": ", " },
{
"type": "slot",
"id": "c1d2e3f4a5",
"label": "our summer sale starts today!",
"options": [
{ "id": "b6c7d8e9f0", "text": "our summer sale starts today!", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "a0b1c2d3e4", "text": "the summer sale is live now!", "status": "APPROVED", "source": "USER" }
]
}
],
"samples": [
"Hello {firstName}, the summer sale is live now!",
"Hi {firstName}, our summer sale starts today!"
],
"error": null
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
PATCH/v1/developer/campaigns/:id/variationsChanges the options while the campaign is PENDING_REVIEW and returns the updated view. You can approve or reject any option, edit or remove AI and USER options, and add your own. The ORIGINAL option can only be rejected, never edited or removed. Every slot needs at least one approved option, options must keep the same {placeholders} as your phrase and can't contain | or other braces, and a slot holds at most 12 options. Operations run in order; if one is invalid, none are applied.
2 required2 responses
/v1/developer/campaigns/:id/variationsChanges the options while the campaign is PENDING_REVIEW and returns the updated view. You can approve or reject any option, edit or remove AI and USER options, and add your own. The ORIGINAL option can only be rejected, never edited or removed. Every slot needs at least one approved option, options must keep the same {placeholders} as your phrase and can't contain | or other braces, and a slot holds at most 12 options. Operations run in order; if one is invalid, none are applied.
Request Fields
id
Requiredstring (UUID) • path
The campaign waiting for review. It's the id returned by Create Campaign.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0dops
Requiredobject[] (1-100) • body
The changes to make, in order. Each has op, slotId (from the segments), and: optionId + status (APPROVED or REJECTED) for setStatus; optionId + text for setText; text for addOption; optionId for removeOption. Text is at most 500 characters.
Allowed:setStatus · setText · addOption · removeOptionExample: [{ "op": "setStatus", "slotId": "a1b2c3d4e5", "optionId": "d6e7f8a9b0", "status": "APPROVED" }]| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign waiting for review. It's the id returned by Create Campaign.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
| ops | object[] (1-100) | body | Yes | The changes to make, in order. Each has op, slotId (from the segments), and: optionId + status (APPROVED or REJECTED) for setStatus; optionId + text for setText; text for addOption; optionId for removeOption. Text is at most 500 characters.Allowed: setStatus · setText · addOption · removeOptionExample: [{ "op": "setStatus", "slotId": "a1b2c3d4e5", "optionId": "d6e7f8a9b0", "status": "APPROVED" }] |
Request Body
{
"ops": [
{ "op": "setStatus", "slotId": "a1b2c3d4e5", "optionId": "d6e7f8a9b0", "status": "APPROVED" },
{ "op": "setText", "slotId": "a1b2c3d4e5", "optionId": "e1f2a3b4c5", "text": "Hello there {firstName}" },
{ "op": "addOption", "slotId": "c1d2e3f4a5", "text": "the summer sale begins today!" },
{ "op": "removeOption", "slotId": "c1d2e3f4a5", "optionId": "a0b1c2d3e4" }
]
}Request Examples
curl -X PATCH "https://api.msgera.io/v1/developer/campaigns/{id}/variations" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"ops": [
{ "op": "setStatus", "slotId": "a1b2c3d4e5", "optionId": "d6e7f8a9b0", "status": "APPROVED" },
{ "op": "setText", "slotId": "a1b2c3d4e5", "optionId": "e1f2a3b4c5", "text": "Hello there {firstName}" },
{ "op": "addOption", "slotId": "c1d2e3f4a5", "text": "the summer sale begins today!" },
{ "op": "removeOption", "slotId": "c1d2e3f4a5", "optionId": "a0b1c2d3e4" }
]
}'Responses
{
"campaignId": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"status": "PENDING_REVIEW",
"required": 4,
"capacity": 4,
"originalText": "Hi {firstName}, our summer sale starts today!",
"segments": [
{
"type": "slot",
"id": "a1b2c3d4e5",
"label": "Hi {firstName}",
"options": [
{ "id": "f6a7b8c9d0", "text": "Hi {firstName}", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "e1f2a3b4c5", "text": "Hello {firstName}", "status": "APPROVED", "source": "AI" },
{ "id": "d6e7f8a9b0", "text": "Hey {firstName}", "status": "REJECTED", "source": "AI" }
]
},
{ "type": "text", "value": ", " },
{
"type": "slot",
"id": "c1d2e3f4a5",
"label": "our summer sale starts today!",
"options": [
{ "id": "b6c7d8e9f0", "text": "our summer sale starts today!", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "a0b1c2d3e4", "text": "the summer sale is live now!", "status": "APPROVED", "source": "USER" }
]
}
],
"samples": [
"Hello {firstName}, the summer sale is live now!",
"Hi {firstName}, our summer sale starts today!"
],
"error": null
}{
"statusCode": 400,
"error": "Bad Request",
"message": "The original phrase cannot be removed; reject it instead",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/variations"
}Status Codes
Success
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/campaigns/:id/variations/regenerateAsks AI for new options when you don't like the current ones. Pass slotIds to rewrite only those phrases: their ORIGINAL and USER options are kept. Without slotIds everything is rewritten from scratch and options you added or edited are lost. Works while the campaign is AWAITING_REVIEW and its variations are PENDING_REVIEW or FAILED. Spends AI credits.
1 required2 responses
/v1/developer/campaigns/:id/variations/regenerateAsks AI for new options when you don't like the current ones. Pass slotIds to rewrite only those phrases: their ORIGINAL and USER options are kept. Without slotIds everything is rewritten from scratch and options you added or edited are lost. Works while the campaign is AWAITING_REVIEW and its variations are PENDING_REVIEW or FAILED. Spends AI credits.
Request Fields
id
Requiredstring (UUID) • path
The campaign waiting for review. It's the id returned by Create Campaign.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0dslotIds
Optionalstring[] (max 20) • body
The phrases to rewrite, using slot ids from Get Variations. Leave it out to rewrite all of them.
Example:["c1d2e3f4a5"]| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign waiting for review. It's the id returned by Create Campaign.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
| slotIds | string[] (max 20) | body | No | The phrases to rewrite, using slot ids from Get Variations. Leave it out to rewrite all of them.Example: ["c1d2e3f4a5"] |
Request Body
{
"slotIds": ["c1d2e3f4a5"]
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/{id}/variations/regenerate" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"slotIds": ["c1d2e3f4a5"]
}'Responses
{
"campaignId": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"status": "PENDING_REVIEW",
"required": 4,
"capacity": 4,
"originalText": "Hi {firstName}, our summer sale starts today!",
"segments": [
{
"type": "slot",
"id": "a1b2c3d4e5",
"label": "Hi {firstName}",
"options": [
{ "id": "f6a7b8c9d0", "text": "Hi {firstName}", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "e1f2a3b4c5", "text": "Hello {firstName}", "status": "APPROVED", "source": "AI" },
{ "id": "d6e7f8a9b0", "text": "Hey {firstName}", "status": "REJECTED", "source": "AI" }
]
},
{ "type": "text", "value": ", " },
{
"type": "slot",
"id": "c1d2e3f4a5",
"label": "our summer sale starts today!",
"options": [
{ "id": "b6c7d8e9f0", "text": "our summer sale starts today!", "status": "APPROVED", "source": "ORIGINAL" },
{ "id": "a0b1c2d3e4", "text": "the summer sale is live now!", "status": "APPROVED", "source": "USER" }
]
}
],
"samples": [
"Hello {firstName}, the summer sale is live now!",
"Hi {firstName}, our summer sale starts today!"
],
"error": null
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Variations can only be regenerated while the campaign awaits review",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/variations/regenerate"
}Status Codes
Created - the new variations are in the body
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - AI credits are used up (PLAN_LIMIT_EXCEEDED), or the API key can't use this campaign
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created - the new variations are in the body |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - AI credits are used up (PLAN_LIMIT_EXCEEDED), or the API key can't use this campaign |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/campaigns/:id/variations/approveAccepts the approved options and starts the campaign. The response has the new campaign status (RUNNING, QUEUED when it's scheduled for later, or RECURRING for a repeating campaign) and the final variations. Only works while variations are PENDING_REVIEW, and fails with 400 when capacity is below required.
1 required2 responses
/v1/developer/campaigns/:id/variations/approveAccepts the approved options and starts the campaign. The response has the new campaign status (RUNNING, QUEUED when it's scheduled for later, or RECURRING for a repeating campaign) and the final variations. Only works while variations are PENDING_REVIEW, and fails with 400 when capacity is below required.
Request Fields
id
Requiredstring (UUID) • path
The campaign waiting for review. It's the id returned by Create Campaign.
Example:8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The campaign waiting for review. It's the id returned by Create Campaign.Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d |
Request Examples
curl -X POST "https://api.msgera.io/v1/developer/campaigns/{id}/variations/approve" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"status": "RUNNING",
"message": "Campaign started",
"variations": {
"campaignId": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
"status": "APPROVED",
"required": 4,
"capacity": 4,
"originalText": "Hi {firstName}, our summer sale starts today!",
"segments": [
{
"type": "slot",
"id": "a1b2c3d4e5",
"label": "Hi {firstName}",
"options": [
{
"id": "f6a7b8c9d0",
"text": "Hi {firstName}",
"status": "APPROVED",
"source": "ORIGINAL"
},
{
"id": "e1f2a3b4c5",
"text": "Hello {firstName}",
"status": "APPROVED",
"source": "AI"
},
{
"id": "d6e7f8a9b0",
"text": "Hey {firstName}",
"status": "REJECTED",
"source": "AI"
}
]
},
{
"type": "text",
"value": ", "
},
{
"type": "slot",
"id": "c1d2e3f4a5",
"label": "our summer sale starts today!",
"options": [
{
"id": "b6c7d8e9f0",
"text": "our summer sale starts today!",
"status": "APPROVED",
"source": "ORIGINAL"
},
{
"id": "a0b1c2d3e4",
"text": "the summer sale is live now!",
"status": "APPROVED",
"source": "USER"
}
]
}
],
"samples": [
"Hello {firstName}, the summer sale is live now!",
"Hi {firstName}, our summer sale starts today!"
],
"error": null
}
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Approved options produce 3 unique messages but 4 are required. Approve or add more options.",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/campaigns/8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d/variations/approve"
}Status Codes
Created - approved and started
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - a plan limit stops the campaign from starting (PLAN_LIMIT_EXCEEDED), or the API key can't use this campaign
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created - approved and started |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - a plan limit stops the campaign from starting (PLAN_LIMIT_EXCEEDED), or the API key can't use this campaign |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
Contacts
Your address book: the people you message. Use an API key with the contacts permission. Keys limited to certain devices may use these routes; keys limited to certain chats can't. Put contacts into groups and tags with Contact Groups & Tags, then filter this list by groupId or tagId, or target them in a campaign.
GET/v1/developer/contactsLists your contacts, newest first, a page at a time. Each contact includes the groups and tags it belongs to.
0 required1 response
/v1/developer/contactsLists your contacts, newest first, a page at a time. Each contact includes the groups and tags it belongs to.
Request Fields
search
Optionalstring • query
Only show contacts whose name, phone or email contains this text. Matching is case-sensitive.
Example:JanegroupId
Optionalstring (UUID) • query
Only show members of this group. Get the id from List Contact Groups.
Example:6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11tagId
Optionalstring (UUID) • query
Only show contacts with this tag. Get the id from List Contact Tags.
Example:a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2bpage
Optionalinteger, 1 or more • query
Which page to show. Starts at 1 (the default); an invalid value shows page 1.
Example:1limit
Optionalinteger, 1-100 • query
How many contacts per page. Default 20. Values above 100 are treated as 100, and an invalid value uses the default.
Example:50| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| search | string | query | No | Only show contacts whose name, phone or email contains this text. Matching is case-sensitive.Example: Jane |
| groupId | string (UUID) | query | No | Only show members of this group. Get the id from List Contact Groups.Example: 6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11 |
| tagId | string (UUID) | query | No | Only show contacts with this tag. Get the id from List Contact Tags.Example: a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b |
| page | integer, 1 or more | query | No | Which page to show. Starts at 1 (the default); an invalid value shows page 1.Example: 1 |
| limit | integer, 1-100 | query | No | How many contacts per page. Default 20. Values above 100 are treated as 100, and an invalid value uses the default.Example: 50 |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/contacts" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"userId": "0c4e7f2a-5b8d-4e1f-a3c6-9d2b7e4f1a08",
"accountId": null,
"name": "Jane Doe",
"phone": "+201001234567",
"email": "[email protected]",
"whatsappOptIn": true,
"whatsappOptInAt": "2026-05-23T10:15:00.000Z",
"emailOptIn": false,
"emailOptInAt": null,
"emailOptOutAt": null,
"createdAt": "2026-05-23T10:15:00.000Z",
"updatedAt": "2026-05-23T10:15:00.000Z",
"groups": [{ "id": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11", "name": "VIP Clients" }],
"tags": [{ "id": "a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b", "name": "Leads", "color": "#22c55e" }]
}
],
"total": 90,
"page": 1,
"limit": 20
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 500 | Internal Server Error |
POST/v1/developer/contactsAdds one person to your contacts. Each phone number and each email can only be used once. To add many people at once, import a file into a group or tag.
1 required2 responses
/v1/developer/contactsAdds one person to your contacts. Each phone number and each email can only be used once. To add many people at once, import a file into a group or tag.
Request Fields
phone
Requiredstring, +? then 7-15 digits • body
The person's WhatsApp number. Digits only with an optional leading +, 7-15 digits. Include the country code; with a + it is saved in standard international form.
Example:+201001234567name
Optionalstring, at least 1 char • body
The person's name, shown in the dashboard and usable in messages. Send null to remove it.
Example:Jane Doestring (email) • body
The person's email address, for email campaigns. Stored in lowercase and must be unique among your contacts. Send null to remove it.
Example:[email protected]whatsappOptIn
Optionalboolean • body
Set to true only if this person agreed to receive WhatsApp messages from your business. The time of consent is recorded. Default false.
Example:trueemailOptIn
Optionalboolean • body
Set to true only if this person agreed to receive marketing email from you. The time of consent is recorded. Default false.
Example:false| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| phone | string, +? then 7-15 digits | body | Yes | The person's WhatsApp number. Digits only with an optional leading +, 7-15 digits. Include the country code; with a + it is saved in standard international form.Example: +201001234567 |
| name | string, at least 1 char | body | No | The person's name, shown in the dashboard and usable in messages. Send null to remove it.Example: Jane Doe |
| string (email) | body | No | The person's email address, for email campaigns. Stored in lowercase and must be unique among your contacts. Send null to remove it.Example: [email protected] | |
| whatsappOptIn | boolean | body | No | Set to true only if this person agreed to receive WhatsApp messages from your business. The time of consent is recorded. Default false.Example: true |
| emailOptIn | boolean | body | No | Set to true only if this person agreed to receive marketing email from you. The time of consent is recorded. Default false.Example: false |
Request Body
{
"phone": "+201001234567",
"name": "Jane Doe",
"email": "[email protected]",
"whatsappOptIn": true
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/contacts" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"phone": "+201001234567",
"name": "Jane Doe",
"email": "[email protected]",
"whatsappOptIn": true
}'Responses
{
"id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"userId": "0c4e7f2a-5b8d-4e1f-a3c6-9d2b7e4f1a08",
"accountId": null,
"name": "Jane Doe",
"phone": "+201001234567",
"email": "[email protected]",
"whatsappOptIn": true,
"whatsappOptInAt": "2026-05-23T10:15:00.000Z",
"emailOptIn": false,
"emailOptInAt": null,
"emailOptOutAt": null,
"createdAt": "2026-05-23T10:15:00.000Z",
"updatedAt": "2026-05-23T10:15:00.000Z",
"groups": [],
"tags": []
}{
"statusCode": 409,
"error": "Conflict",
"message": "Contact with phone +201001234567 already exists",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/contacts"
}Status Codes
Created
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan's contact limit is reached (PLAN_LIMIT_EXCEEDED, limitKey maxContacts), or the API key lacks the contacts permission.
Conflict - another of your contacts already has this phone number or email.
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan's contact limit is reached (PLAN_LIMIT_EXCEEDED, limitKey maxContacts), or the API key lacks the contacts permission. |
| 409 | Conflict - another of your contacts already has this phone number or email. |
| 500 | Internal Server Error |
GET/v1/developer/contacts/:idReturns one contact with its groups and tags.
1 required1 response
/v1/developer/contacts/:idReturns one contact with its groups and tags.
Request Fields
id
Requiredstring (UUID) • path
The id of the contact. You get it when you create a contact or from List Contacts.
Example:1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The id of the contact. You get it when you create a contact or from List Contacts.Example: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/contacts/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"userId": "0c4e7f2a-5b8d-4e1f-a3c6-9d2b7e4f1a08",
"accountId": null,
"name": "Jane Doe",
"phone": "+201001234567",
"email": "[email protected]",
"whatsappOptIn": true,
"whatsappOptInAt": "2026-05-23T10:15:00.000Z",
"emailOptIn": false,
"emailOptInAt": null,
"emailOptOutAt": null,
"createdAt": "2026-05-23T10:15:00.000Z",
"updatedAt": "2026-05-23T10:15:00.000Z",
"groups": [{ "id": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11", "name": "VIP Clients" }],
"tags": [{ "id": "a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b", "name": "Leads", "color": "#22c55e" }]
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
PATCH/v1/developer/contacts/:idChanges one contact. Send only the fields you want to change; the rest stay as they are. Returns the updated contact.
1 required1 response
/v1/developer/contacts/:idChanges one contact. Send only the fields you want to change; the rest stay as they are. Returns the updated contact.
Request Fields
id
Requiredstring (UUID) • path
The id of the contact. You get it when you create a contact or from List Contacts.
Example:1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bedphone
Optionalstring, +? then 7-15 digits • body
A new WhatsApp number for this person. Digits only with an optional leading +, 7-15 digits. Include the country code; with a + it is saved in standard international form.
Example:+201001234568name
Optionalstring, at least 1 char • body
The person's name, shown in the dashboard and usable in messages. Send null to remove it.
Example:Jane Doestring (email) • body
The person's email address, for email campaigns. Stored in lowercase and must be unique among your contacts. Send null to remove it.
Example:[email protected]whatsappOptIn
Optionalboolean • body
Set to true only if this person agreed to receive WhatsApp messages from your business. The time of consent is recorded. Default false.
Example:trueemailOptIn
Optionalboolean • body
Set to true only if this person agreed to receive marketing email from you. The time of consent is recorded. Default false.
Example:false| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The id of the contact. You get it when you create a contact or from List Contacts.Example: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed |
| phone | string, +? then 7-15 digits | body | No | A new WhatsApp number for this person. Digits only with an optional leading +, 7-15 digits. Include the country code; with a + it is saved in standard international form.Example: +201001234568 |
| name | string, at least 1 char | body | No | The person's name, shown in the dashboard and usable in messages. Send null to remove it.Example: Jane Doe |
| string (email) | body | No | The person's email address, for email campaigns. Stored in lowercase and must be unique among your contacts. Send null to remove it.Example: [email protected] | |
| whatsappOptIn | boolean | body | No | Set to true only if this person agreed to receive WhatsApp messages from your business. The time of consent is recorded. Default false.Example: true |
| emailOptIn | boolean | body | No | Set to true only if this person agreed to receive marketing email from you. The time of consent is recorded. Default false.Example: false |
Request Body
{
"name": "Jane Smith",
"emailOptIn": true
}Request Examples
curl -X PATCH "https://api.msgera.io/v1/developer/contacts/{id}" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"emailOptIn": true
}'Responses
{
"id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed",
"userId": "0c4e7f2a-5b8d-4e1f-a3c6-9d2b7e4f1a08",
"accountId": null,
"name": "Jane Doe",
"phone": "+201001234567",
"email": "[email protected]",
"whatsappOptIn": true,
"whatsappOptInAt": "2026-05-23T10:15:00.000Z",
"emailOptIn": false,
"emailOptInAt": null,
"emailOptOutAt": null,
"createdAt": "2026-05-23T10:15:00.000Z",
"updatedAt": "2026-05-23T10:15:00.000Z",
"groups": [{ "id": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11", "name": "VIP Clients" }],
"tags": [{ "id": "a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b", "name": "Leads", "color": "#22c55e" }]
}Status Codes
Success
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Conflict - another of your contacts already has this phone number or email.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 409 | Conflict - another of your contacts already has this phone number or email. |
| 500 | Internal Server Error |
DELETE/v1/developer/contacts/:idDeletes the contact for good and removes it from its groups and tags. Messages already sent to the number are kept.
1 required1 response
/v1/developer/contacts/:idDeletes the contact for good and removes it from its groups and tags. Messages already sent to the number are kept.
Request Fields
id
Requiredstring (UUID) • path
The id of the contact. You get it when you create a contact or from List Contacts.
Example:1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The id of the contact. You get it when you create a contact or from List Contacts.Example: 1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed |
Request Examples
curl -X DELETE "https://api.msgera.io/v1/developer/contacts/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{ "deleted": true }Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
Scheduled Messages
Send a message to one person at a time you choose, for example an appointment reminder. Scheduled messages follow the same account safety limits as other sends: if the number is at its limit when the time comes, the message waits and goes out as soon as it's allowed. A message still waiting 30 days after its time is dropped as FAILED. This list also holds sends from Send Message that were queued for a safety window (source AUTO): you can see and cancel them here, but not edit them. Statuses: PENDING (waiting for its time or for a safety window), PROCESSING (sending now), DISPATCHED (handed to WhatsApp, waiting for confirmation), SENT, DELIVERED, READ, FAILED (see error), CANCELLED, UNKNOWN (no confirmation came back; check before sending again).
POST/v1/developer/scheduled-messagesSchedules one message for later. It's sent automatically at scheduledAt (or soon after if the number is at a safety limit), and you can change or cancel it until then. On a Meta number, free-form messages only reach people who wrote to you in the 24 hours before it sends; otherwise it ends FAILED.
4 required3 responses
/v1/developer/scheduled-messagesSchedules one message for later. It's sent automatically at scheduledAt (or soon after if the number is at a safety limit), and you can change or cancel it until then. On a Meta number, free-form messages only reach people who wrote to you in the 24 hours before it sends; otherwise it ends FAILED.
Request Fields
deviceUid
Requiredstring • body
The number to send from. Copy its deviceUid from List Devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwto
Requiredstring (7-15 digits, optional +) • body
Who receives the message: a phone number with country code.
Example:+201001234567text
Requiredstring • body
The message for a TEXT message. It's required for MEDIA and VOICE too, but there the recipient sees caption instead.
Example:Reminder: your appointment is tomorrow at 10:00.scheduledAt
Requiredstring (ISO 8601) • body
When to send it. Must be in the future. Include a timezone offset or Z for UTC.
Example:2026-06-01T08:00:00ZmessageType
Optionalstring • body
What kind of message to send: TEXT, MEDIA (photo, video or document) or VOICE (voice note). Defaults to TEXT.
Allowed:TEXT · MEDIA · VOICEExample: TEXTmediaAssetId
Optionalstring • body
The file to send. Required for MEDIA and VOICE (VOICE needs an audio file). Upload it with Upload Media first; the file is deleted once the message is sent or cancelled, so upload a new one for each message.
Example:5d1c9e8a-2b3f-4a6d-9e0c-7f8a1b2c3d4ecaption
Optionalstring • body
Text shown under the photo, video or document of a MEDIA message.
Example:Your invoice for May| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | body | Yes | The number to send from. Copy its deviceUid from List Devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| to | string (7-15 digits, optional +) | body | Yes | Who receives the message: a phone number with country code.Example: +201001234567 |
| text | string | body | Yes | The message for a TEXT message. It's required for MEDIA and VOICE too, but there the recipient sees caption instead.Example: Reminder: your appointment is tomorrow at 10:00. |
| scheduledAt | string (ISO 8601) | body | Yes | When to send it. Must be in the future. Include a timezone offset or Z for UTC.Example: 2026-06-01T08:00:00Z |
| messageType | string | body | No | What kind of message to send: TEXT, MEDIA (photo, video or document) or VOICE (voice note). Defaults to TEXT.Allowed: TEXT · MEDIA · VOICEExample: TEXT |
| mediaAssetId | string | body | No | The file to send. Required for MEDIA and VOICE (VOICE needs an audio file). Upload it with Upload Media first; the file is deleted once the message is sent or cancelled, so upload a new one for each message.Example: 5d1c9e8a-2b3f-4a6d-9e0c-7f8a1b2c3d4e |
| caption | string | body | No | Text shown under the photo, video or document of a MEDIA message.Example: Your invoice for May |
Request Body
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"to": "+201001234567",
"text": "Reminder: your appointment is tomorrow at 10:00.",
"scheduledAt": "2026-06-01T08:00:00Z"
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/scheduled-messages" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"to": "+201001234567",
"text": "Reminder: your appointment is tomorrow at 10:00.",
"scheduledAt": "2026-06-01T08:00:00Z"
}'Responses
{
"id": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"to": "+201001234567",
"text": "Reminder: your appointment is tomorrow at 10:00.",
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"scheduledAt": "2026-06-01T08:00:00.000Z",
"timezone": null,
"status": "PENDING",
"messageId": null,
"error": null,
"source": "SCHEDULED",
"requestHash": null,
"attempts": 0,
"expiresAt": "2026-07-01T08:00:00.000Z",
"processingStartedAt": null,
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T10:00:00.000Z"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "scheduledAt must be in the future",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/scheduled-messages"
}{
"statusCode": 403,
"error": "Forbidden",
"message": "pendingScheduledMessages limit exceeded for the Starter plan",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/scheduled-messages",
"code": "PLAN_LIMIT_EXCEEDED",
"planKey": "starter",
"limitKey": "pendingScheduledMessages",
"limit": 10,
"current": 10,
"requested": 1
}Status Codes
Created
Bad Request - a time in the past, a badly formatted number, or MEDIA/VOICE without mediaAssetId
Unauthorized - missing or invalid API key
Forbidden - you already have as many waiting scheduled messages as your plan allows (PLAN_LIMIT_EXCEEDED), or the API key can't use this device, chat or service
Not Found - the deviceUid or the media file isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a time in the past, a badly formatted number, or MEDIA/VOICE without mediaAssetId |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - you already have as many waiting scheduled messages as your plan allows (PLAN_LIMIT_EXCEEDED), or the API key can't use this device, chat or service |
| 404 | Not Found - the deviceUid or the media file isn't yours |
| 500 | Internal Server Error |
GET/v1/developer/scheduled-messagesLists your scheduled messages and queued sends, soonest first. Filter by status, for example PENDING to see what's still waiting. Each row adds accountName, the name of the sending number. source is SCHEDULED for messages you scheduled and AUTO for sends that were queued for a safety window; for those, error explains what they're waiting for. Keys limited to certain devices or chats only see their own messages.
0 required1 response
/v1/developer/scheduled-messagesLists your scheduled messages and queued sends, soonest first. Filter by status, for example PENDING to see what's still waiting. Each row adds accountName, the name of the sending number. source is SCHEDULED for messages you scheduled and AUTO for sends that were queued for a safety window; for those, error explains what they're waiting for. Keys limited to certain devices or chats only see their own messages.
Request Fields
page
Optionalinteger • query
Which page of results to return, starting at 1. Defaults to 1.
Example:1limit
Optionalinteger • query
How many results per page. Defaults to 20, at most 100.
Example:20status
Optionalstring • query
Show only messages in this state. Leave it out to see all of them.
Allowed:PENDING · PROCESSING · DISPATCHED · SENT · DELIVERED · READ · FAILED · CANCELLED · UNKNOWNExample: PENDING| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| page | integer | query | No | Which page of results to return, starting at 1. Defaults to 1.Example: 1 |
| limit | integer | query | No | How many results per page. Defaults to 20, at most 100.Example: 20 |
| status | string | query | No | Show only messages in this state. Leave it out to see all of them.Allowed: PENDING · PROCESSING · DISPATCHED · SENT · DELIVERED · READ · FAILED · CANCELLED · UNKNOWNExample: PENDING |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/scheduled-messages" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"to": "+201001234567",
"text": "Your order #1042 has shipped.",
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"scheduledAt": "2026-05-23T00:00:05.000Z",
"timezone": null,
"status": "PENDING",
"messageId": null,
"error": "Daily account safety limit reached (300 messages per day for accounts at this age). Try again after 2026-05-22T23:59:59.999Z.",
"source": "AUTO",
"requestHash": "b1946ac92492d2347c6235b4d2611184",
"attempts": 1,
"expiresAt": "2026-06-22T10:00:00.000Z",
"processingStartedAt": null,
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T10:00:00.000Z",
"accountName": "Sales Line 1"
},
{
"id": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"to": "+201001234567",
"text": "Reminder: your appointment is tomorrow at 10:00.",
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"scheduledAt": "2026-06-01T08:00:00.000Z",
"timezone": null,
"status": "PENDING",
"messageId": null,
"error": null,
"source": "SCHEDULED",
"requestHash": null,
"attempts": 0,
"expiresAt": "2026-07-01T08:00:00.000Z",
"processingStartedAt": null,
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T10:00:00.000Z",
"accountName": "Sales Line 1"
}
],
"total": 2,
"page": 1,
"limit": 20
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 500 | Internal Server Error |
GET/v1/developer/scheduled-messages/:idReturns one scheduled message or queued send with its current status. Once it's sent, messageId links it to the message in List Messages.
1 required1 response
/v1/developer/scheduled-messages/:idReturns one scheduled message or queued send with its current status. Once it's sent, messageId links it to the message in List Messages.
Request Fields
id
Requiredstring (UUID) • path
The scheduled message to work with. It's the id returned by Create Scheduled Message or List Scheduled Messages (for a queued send, the queueId from the send response).
Example:2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The scheduled message to work with. It's the id returned by Create Scheduled Message or List Scheduled Messages (for a queued send, the queueId from the send response).Example: 2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/scheduled-messages/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"to": "+201001234567",
"text": "Reminder: your appointment is tomorrow at 10:00.",
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"scheduledAt": "2026-06-01T08:00:00.000Z",
"timezone": null,
"status": "PENDING",
"messageId": null,
"error": null,
"source": "SCHEDULED",
"requestHash": null,
"attempts": 0,
"expiresAt": "2026-07-01T08:00:00.000Z",
"processingStartedAt": null,
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T10:00:00.000Z",
"accountName": "Sales Line 1"
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
PATCH/v1/developer/scheduled-messages/:idChanges a message that hasn't been sent yet: its text, file, caption or time. Send only the fields you want to change. Only PENDING messages you scheduled can be changed; a queued send (source AUTO) can't be edited, so cancel it and send a new one.
1 required3 responses
/v1/developer/scheduled-messages/:idChanges a message that hasn't been sent yet: its text, file, caption or time. Send only the fields you want to change. Only PENDING messages you scheduled can be changed; a queued send (source AUTO) can't be edited, so cancel it and send a new one.
Request Fields
id
Requiredstring (UUID) • path
The scheduled message to work with. It's the id returned by Create Scheduled Message or List Scheduled Messages (for a queued send, the queueId from the send response).
Example:2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6atext
Optionalstring • body
New message text.
Example:Reminder: your appointment moved to 11:00.caption
Optionalstring • body
New caption for a MEDIA message.
Example:Updated invoice for MaymediaAssetId
Optionalstring • body
A different file to send. Upload it with Upload Media first and use the new id.
Example:9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6dscheduledAt
Optionalstring (ISO 8601) • body
A new send time. Must be in the future.
Example:2026-06-01T09:00:00Ztimezone
Optionalstring (IANA, max 64) • body
The timezone you picked the time in, kept so your app can show it in local time. It doesn't change scheduledAt.
Example:Africa/Cairo| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The scheduled message to work with. It's the id returned by Create Scheduled Message or List Scheduled Messages (for a queued send, the queueId from the send response).Example: 2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a |
| text | string | body | No | New message text.Example: Reminder: your appointment moved to 11:00. |
| caption | string | body | No | New caption for a MEDIA message.Example: Updated invoice for May |
| mediaAssetId | string | body | No | A different file to send. Upload it with Upload Media first and use the new id.Example: 9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d |
| scheduledAt | string (ISO 8601) | body | No | A new send time. Must be in the future.Example: 2026-06-01T09:00:00Z |
| timezone | string (IANA, max 64) | body | No | The timezone you picked the time in, kept so your app can show it in local time. It doesn't change scheduledAt.Example: Africa/Cairo |
Request Body
{
"text": "Reminder: your appointment moved to 11:00.",
"scheduledAt": "2026-06-01T09:00:00Z",
"timezone": "Africa/Cairo"
}Request Examples
curl -X PATCH "https://api.msgera.io/v1/developer/scheduled-messages/{id}" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"text": "Reminder: your appointment moved to 11:00.",
"scheduledAt": "2026-06-01T09:00:00Z",
"timezone": "Africa/Cairo"
}'Responses
{
"id": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"to": "+201001234567",
"text": "Reminder: your appointment moved to 11:00.",
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"scheduledAt": "2026-06-01T09:00:00.000Z",
"timezone": "Africa/Cairo",
"status": "PENDING",
"messageId": null,
"error": null,
"source": "SCHEDULED",
"requestHash": null,
"attempts": 0,
"expiresAt": "2026-07-01T09:00:00.000Z",
"processingStartedAt": null,
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T11:00:00.000Z"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Cancel this queued message and submit a new message to change its content",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/scheduled-messages/6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11"
}{
"statusCode": 409,
"error": "Conflict",
"message": "Message is already being processed",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/scheduled-messages/2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a"
}Status Codes
Success
Bad Request - the message isn't PENDING, it's a queued send (source AUTO), or the new time is in the past
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the message or the media file isn't yours
Conflict - the message started sending while your request was being handled
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - the message isn't PENDING, it's a queued send (source AUTO), or the new time is in the past |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the message or the media file isn't yours |
| 409 | Conflict - the message started sending while your request was being handled |
| 500 | Internal Server Error |
DELETE/v1/developer/scheduled-messages/:idCancels a message that hasn't been sent yet, including a queued send. The row stays with status CANCELLED and its media file is deleted. Only PENDING messages can be cancelled.
1 required2 responses
/v1/developer/scheduled-messages/:idCancels a message that hasn't been sent yet, including a queued send. The row stays with status CANCELLED and its media file is deleted. Only PENDING messages can be cancelled.
Request Fields
id
Requiredstring (UUID) • path
The scheduled message to work with. It's the id returned by Create Scheduled Message or List Scheduled Messages (for a queued send, the queueId from the send response).
Example:2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The scheduled message to work with. It's the id returned by Create Scheduled Message or List Scheduled Messages (for a queued send, the queueId from the send response).Example: 2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a |
Request Examples
curl -X DELETE "https://api.msgera.io/v1/developer/scheduled-messages/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"to": "+201001234567",
"text": "Reminder: your appointment is tomorrow at 10:00.",
"caption": null,
"messageType": "TEXT",
"mediaAssetId": null,
"scheduledAt": "2026-06-01T08:00:00.000Z",
"timezone": null,
"status": "CANCELLED",
"messageId": null,
"error": null,
"source": "SCHEDULED",
"requestHash": null,
"attempts": 0,
"expiresAt": "2026-07-01T08:00:00.000Z",
"processingStartedAt": null,
"createdAt": "2026-05-22T10:00:00.000Z",
"updatedAt": "2026-05-22T11:00:00.000Z"
}{
"statusCode": 400,
"error": "Bad Request",
"message": "Only PENDING messages can be cancelled",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/scheduled-messages/2d3e4f5a-6b7c-4d8e-9f0a-1b2c3d4e5f6a"
}Status Codes
Success
Bad Request - the message isn't PENDING any more (already sending, sent, failed or cancelled), or a field is invalid
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Conflict - the message started sending while your request was being handled
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - the message isn't PENDING any more (already sending, sent, failed or cancelled), or a field is invalid |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 409 | Conflict - the message started sending while your request was being handled |
| 500 | Internal Server Error |
Auto-Reply Rules (retired)
Keyword auto-reply rules are retired and no longer send anything. AI agents now answer incoming messages: connect one to the number in the dashboard. You can still list your old rules and delete them; creating or changing a rule returns 410 AUTO_REPLY_RETIRED.
GET/v1/developer/auto-replyLists the old rules saved on one number, so you can review them before deleting them. The rules have no effect any more.
1 required1 response
/v1/developer/auto-replyLists the old rules saved on one number, so you can review them before deleting them. The rules have no effect any more.
Request Fields
deviceUid
Requiredstring • query
The number whose rules you want to see. Copy its deviceUid from List Devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwpage
Optionalinteger • query
Which page of results to return, starting at 1. Defaults to 1.
Example:1limit
Optionalinteger • query
How many rules per page. Defaults to 50.
Example:50| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | query | Yes | The number whose rules you want to see. Copy its deviceUid from List Devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| page | integer | query | No | Which page of results to return, starting at 1. Defaults to 1.Example: 1 |
| limit | integer | query | No | How many rules per page. Defaults to 50.Example: 50 |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/auto-reply" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e",
"userId": "c2a71f0e-6b4d-4e8a-a1f3-0d9e8c7b6a51",
"accountId": "1f4e2d3c-5b6a-4789-8a0b-c1d2e3f4a5b6",
"triggerType": "CONTAINS",
"triggerValue": "price",
"replyText": "Our prices start at $15.99/mo.",
"caseSensitive": false,
"enabled": true,
"order": 0,
"createdAt": "2025-11-02T09:00:00.000Z",
"updatedAt": "2025-11-02T09:00:00.000Z"
}
],
"total": 1,
"page": 1,
"limit": 50
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the deviceUid isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the deviceUid isn't yours |
| 500 | Internal Server Error |
POST/v1/developer/auto-replyRetired: always answers 410 and creates nothing. Connect an AI agent to the number instead. The body is still checked first, so a malformed body returns 400 and an unknown deviceUid returns 404.
4 required1 response
/v1/developer/auto-replyRetired: always answers 410 and creates nothing. Connect an AI agent to the number instead. The body is still checked first, so a malformed body returns 400 and an unknown deviceUid returns 404.
Request Fields
deviceUid
Requiredstring • body
The number the rule was meant for, from List Devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwtriggerType
Requiredstring • body
How the keyword was matched. Kept only for old integrations.
Allowed:CONTAINS · EXACT · STARTS_WITH · ENDS_WITHExample: CONTAINStriggerValue
Requiredstring • body
The keyword. Kept only for old integrations.
Example:pricereplyText
Requiredstring • body
The reply. Kept only for old integrations.
Example:Our prices start at $15.99/mo.| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | body | Yes | The number the rule was meant for, from List Devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| triggerType | string | body | Yes | How the keyword was matched. Kept only for old integrations.Allowed: CONTAINS · EXACT · STARTS_WITH · ENDS_WITHExample: CONTAINS |
| triggerValue | string | body | Yes | The keyword. Kept only for old integrations.Example: price |
| replyText | string | body | Yes | The reply. Kept only for old integrations.Example: Our prices start at $15.99/mo. |
Request Body
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"triggerType": "CONTAINS",
"triggerValue": "price",
"replyText": "Our prices start at $15.99/mo."
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/auto-reply" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"triggerType": "CONTAINS",
"triggerValue": "price",
"replyText": "Our prices start at $15.99/mo."
}'Responses
{
"statusCode": 410,
"error": "Error",
"code": "AUTO_REPLY_RETIRED",
"message": "Auto-reply rules are retired. Connect an AI agent to this number instead.",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/auto-reply"
}Status Codes
Gone - auto-reply rules are retired (AUTO_REPLY_RETIRED). Use an AI agent instead.
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 410 | Gone - auto-reply rules are retired (AUTO_REPLY_RETIRED). Use an AI agent instead. |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 500 | Internal Server Error |
PATCH/v1/developer/auto-reply/:idRetired: always answers 410 and changes nothing. Connect an AI agent to the number instead.
1 required1 response
/v1/developer/auto-reply/:idRetired: always answers 410 and changes nothing. Connect an AI agent to the number instead.
Request Fields
id
Requiredstring (UUID) • path
The rule's id, from List Auto-Reply Rules.
Example:b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The rule's id, from List Auto-Reply Rules.Example: b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e |
Request Examples
curl -X PATCH "https://api.msgera.io/v1/developer/auto-reply/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"statusCode": 410,
"error": "Error",
"code": "AUTO_REPLY_RETIRED",
"message": "Auto-reply rules are retired. Connect an AI agent to this number instead.",
"timestamp": "2026-05-22T10:00:00.000Z",
"path": "/v1/developer/auto-reply/b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e"
}Status Codes
Gone - auto-reply rules are retired (AUTO_REPLY_RETIRED). Use an AI agent instead.
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Internal Server Error
| Code | Description |
|---|---|
| 410 | Gone - auto-reply rules are retired (AUTO_REPLY_RETIRED). Use an AI agent instead. |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 500 | Internal Server Error |
DELETE/v1/developer/auto-reply/:idPermanently removes an old rule. Use it to clean up rules that no longer do anything.
1 required1 response
/v1/developer/auto-reply/:idPermanently removes an old rule. Use it to clean up rules that no longer do anything.
Request Fields
id
Requiredstring (UUID) • path
The rule's id, from List Auto-Reply Rules.
Example:b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The rule's id, from List Auto-Reply Rules.Example: b7c8d9e0-f1a2-4b3c-8d4e-5f6a7b8c9d0e |
Request Examples
curl -X DELETE "https://api.msgera.io/v1/developer/auto-reply/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"ok": true
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the rule doesn't exist or isn't yours
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the rule doesn't exist or isn't yours |
| 500 | Internal Server Error |
Webhooks
A webhook is a URL on your server that we call when something happens, such as a customer writing to you or a message being delivered, so you don't have to keep asking. Each WhatsApp number has one webhook with two URLs: one for 1:1 chats and one for group chats. A new webhook only receives message.inbound (new messages from customers). To also get message.status, message.edited, message.deleted, message.reaction or account.status, tick those events for the webhook in the dashboard under Developer > Webhooks, where you can also add filters and see the signing secret. Event payloads, signatures, retries and failed deliveries are covered in the Webhooks and Webhook events guides. Needs the webhooks permission on the key.
GET/v1/developer/webhooksLists your webhooks, newest first, with the number each belongs to (deviceUid), its URLs, whether it is on, the events it receives and its filters. The signing secret is never returned here.
0 required1 response
/v1/developer/webhooksLists your webhooks, newest first, with the number each belongs to (deviceUid), its URLs, whether it is on, the events it receives and its filters. The signing secret is never returned here.
Request Fields
deviceUid
Optionalstring • query
Only show results for this number. Copy it from List Devices. Required for keys limited to certain devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | query | No | Only show results for this number. Copy it from List Devices. Required for keys limited to certain devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/webhooks" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"userId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"accountId": "1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6",
"personalWebhookUrl": "https://example.com/hooks/whatsapp",
"groupWebhookUrl": null,
"isActive": true,
"events": ["message.inbound", "message.status", "account.status"],
"filters": [{ "field": "isGroup", "op": "is", "values": ["false"] }],
"createdAt": "2026-05-20T09:00:00.000Z",
"updatedAt": "2026-05-23T10:00:00.000Z",
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw"
}
],
"total": 1
}Status Codes
Success
Bad Request - a value is invalid, or the key is limited to certain devices and deviceUid is missing
Unauthorized - missing or invalid API key
Forbidden - the key lacks the webhooks permission, is limited to other devices, or is limited to certain chats (those keys can't manage webhooks)
Not Found - the resource doesn't exist or isn't yours
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a value is invalid, or the key is limited to certain devices and deviceUid is missing |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the key lacks the webhooks permission, is limited to other devices, or is limited to certain chats (those keys can't manage webhooks) |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
PUT/v1/developer/webhooksSets the webhook URLs for one number. If the number has no webhook yet, one is created (on, receiving message.inbound); creating one counts toward your plan's webhook limit. If it already has one, only the fields you send change and its events and filters stay as they are. Send an empty string to remove a URL.
1 required1 response
/v1/developer/webhooksSets the webhook URLs for one number. If the number has no webhook yet, one is created (on, receiving message.inbound); creating one counts toward your plan's webhook limit. If it already has one, only the fields you send change and its events and filters stay as they are. Send an empty string to remove a URL.
Request Fields
deviceUid
Requiredstring • body
Which of your numbers this webhook is for. Copy it from List Devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwpersonalWebhookUrl
Optionalstring (URL) • body
Where we send events from 1:1 chats. It also receives account.status events (if subscribed) and the campaign.variations_ready and campaign.variations_failed notices for campaigns sent from this number.
Example:https://example.com/hooks/whatsappgroupWebhookUrl
Optionalstring (URL) • body
Where we send events from group chats. Leave it out if you don't need group messages.
Example:https://example.com/hooks/whatsapp-groupsisActive
Optionalboolean • body
Turn deliveries off (false) or back on (true) without losing the URLs. New webhooks start on.
Example:true| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | body | Yes | Which of your numbers this webhook is for. Copy it from List Devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| personalWebhookUrl | string (URL) | body | No | Where we send events from 1:1 chats. It also receives account.status events (if subscribed) and the campaign.variations_ready and campaign.variations_failed notices for campaigns sent from this number.Example: https://example.com/hooks/whatsapp |
| groupWebhookUrl | string (URL) | body | No | Where we send events from group chats. Leave it out if you don't need group messages.Example: https://example.com/hooks/whatsapp-groups |
| isActive | boolean | body | No | Turn deliveries off (false) or back on (true) without losing the URLs. New webhooks start on.Example: true |
Request Body
{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"personalWebhookUrl": "https://example.com/hooks/whatsapp",
"groupWebhookUrl": "https://example.com/hooks/whatsapp-groups",
"isActive": true
}Request Examples
curl -X PUT "https://api.msgera.io/v1/developer/webhooks" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"deviceUid": "wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw",
"personalWebhookUrl": "https://example.com/hooks/whatsapp",
"groupWebhookUrl": "https://example.com/hooks/whatsapp-groups",
"isActive": true
}'Responses
{
"id": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"userId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"accountId": "1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6",
"personalWebhookUrl": "https://example.com/hooks/whatsapp",
"groupWebhookUrl": "https://example.com/hooks/whatsapp-groups",
"isActive": true,
"events": ["message.inbound", "message.status", "account.status"],
"filters": [{ "field": "isGroup", "op": "is", "values": ["false"] }],
"createdAt": "2026-05-20T09:00:00.000Z",
"updatedAt": "2026-05-23T10:00:00.000Z"
}Status Codes
Success
Bad Request - a URL is not valid or deviceUid is missing
Unauthorized - missing or invalid API key
Forbidden - you already have as many webhooks as your plan allows (PLAN_LIMIT_EXCEEDED), or the key lacks the webhooks permission or is limited to other devices or to certain chats
Not Found - the device doesn't exist or isn't yours
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a URL is not valid or deviceUid is missing |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - you already have as many webhooks as your plan allows (PLAN_LIMIT_EXCEEDED), or the key lacks the webhooks permission or is limited to other devices or to certain chats |
| 404 | Not Found - the device doesn't exist or isn't yours |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
DELETE/v1/developer/webhooks/:idRemoves a webhook completely, so we stop calling your URLs for that number. To pause it instead, send isActive false with Create or Update Webhook.
1 required1 response
/v1/developer/webhooks/:idRemoves a webhook completely, so we stop calling your URLs for that number. To pause it instead, send isActive false with Create or Update Webhook.
Request Fields
id
Requiredstring (UUID) • path
The webhook to remove. Use the id from List Webhooks.
Example:5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The webhook to remove. Use the id from List Webhooks.Example: 5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b |
Request Examples
curl -X DELETE "https://api.msgera.io/v1/developer/webhooks/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{ "deleted": true }Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the key lacks the webhooks permission, is limited to other devices, or is limited to certain chats (those keys can't manage webhooks)
Not Found - the webhook doesn't exist or isn't yours
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the key lacks the webhooks permission, is limited to other devices, or is limited to certain chats (those keys can't manage webhooks) |
| 404 | Not Found - the webhook doesn't exist or isn't yours |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
GET/v1/developer/webhooks/historyLists every attempt we made to call your webhooks, newest first: which event, whether your server answered with success, the HTTP status it returned and any error. Use it to find out why your server missed an event. How far back you can see depends on your plan.
0 required1 response
/v1/developer/webhooks/historyLists every attempt we made to call your webhooks, newest first: which event, whether your server answered with success, the HTTP status it returned and any error. Use it to find out why your server missed an event. How far back you can see depends on your plan.
Request Fields
deviceUid
Optionalstring • query
Only show results for this number. Copy it from List Devices. Required for keys limited to certain devices.
Example:wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAwstatus
Optionalstring • query
Only show successful or failed attempts. Use FAILED to find problems.
Allowed:SUCCESS · FAILEDExample: FAILEDpage
Optionalinteger, from 1 • query
Which page of results to return. Starts at 1.
Example:1limit
Optionalinteger, 1-100 • query
How many attempts per page. Default 20.
Example:20| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| deviceUid | string | query | No | Only show results for this number. Copy it from List Devices. Required for keys limited to certain devices.Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw |
| status | string | query | No | Only show successful or failed attempts. Use FAILED to find problems.Allowed: SUCCESS · FAILEDExample: FAILED |
| page | integer, from 1 | query | No | Which page of results to return. Starts at 1.Example: 1 |
| limit | integer, 1-100 | query | No | How many attempts per page. Default 20.Example: 20 |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/webhooks/history" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"data": [
{
"id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
"userId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
"accountId": "1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6",
"webhookConfigId": "5e6f7a8b-9c0d-4e1f-8a2b-3c4d5e6f7a8b",
"webhookUrl": "https://example.com/hooks/whatsapp",
"targetType": "PERSONAL",
"attemptNumber": 2,
"deliveryStatus": "FAILED",
"responseStatusCode": 503,
"errorMessage": "Webhook returned HTTP 503",
"payloadPreview": "{\"event\":\"message.inbound\",...}",
"event": "message.inbound",
"deliveryId": "4c3b2a19-0f8e-4d7c-9b6a-5f4e3d2c1b0a",
"idempotencyKey": "msg_1d2e3f4a-5b6c-4d7e-8f90-a1b2c3d4e5f6_3EB0A1B2C3D4E5F60718",
"createdAt": "2026-05-23T10:15:10.000Z",
"completedAt": "2026-05-23T10:15:10.412Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}Status Codes
Success
Bad Request - a value is invalid, or the key is limited to certain devices and deviceUid is missing
Unauthorized - missing or invalid API key
Forbidden - the key lacks the webhooks permission, is limited to other devices, or is limited to certain chats (those keys can't manage webhooks)
Not Found - the resource doesn't exist or isn't yours
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a value is invalid, or the key is limited to certain devices and deviceUid is missing |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the key lacks the webhooks permission, is limited to other devices, or is limited to certain chats (those keys can't manage webhooks) |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
Spintax
Spintax lets one text produce many wordings: {Hi|Hello|Hey} picks one option per message. Varied wording keeps linked numbers safe and is what the duplicate-wording rule asks for. Use these endpoints to try a text before you send it; sends and campaigns accept spintax directly. Needs the spintax feature on your plan and the numbers permission on the key. Nothing is sent and no AI credits are used.
POST/v1/developer/spintax/previewReturns several different wordings from your text, so you can check how it reads. count in the response is how many different wordings were found, which can be fewer than you asked for when the text has few options. Text with unbalanced braces is returned unchanged.
1 required1 response
/v1/developer/spintax/previewReturns several different wordings from your text, so you can check how it reads. count in the response is how many different wordings were found, which can be fewer than you asked for when the text has few options. Text with unbalanced braces is returned unchanged.
Request Fields
template
Requiredstring, 1-10,000 characters • body
Your message with choices in curly braces, separated by |. Each {Hi|Hello|Hey} becomes one of its options, so one text gives many wordings.
Example:{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today.count
Optionalinteger, 1-50 • body
How many different wordings you want back. Default 5.
Example:5| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| template | string, 1-10,000 characters | body | Yes | Your message with choices in curly braces, separated by |. Each {Hi|Hello|Hey} becomes one of its options, so one text gives many wordings.Example: {Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today. |
| count | integer, 1-50 | body | No | How many different wordings you want back. Default 5.Example: 5 |
Request Body
{
"template": "{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today.",
"count": 3
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/spintax/preview" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"template": "{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today.",
"count": 3
}'Responses
{
"template": "{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today.",
"count": 3,
"samples": [
"Hello friend! Our sale ends today.",
"Hey there! Our offer ends today.",
"Hi friend! Our offer ends today."
]
}Status Codes
Created
Bad Request - template is missing, empty or longer than 10,000 characters, or count is outside 1-50
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include spintax (PLAN_FEATURE_UNAVAILABLE), or the key lacks the numbers permission
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - template is missing, empty or longer than 10,000 characters, or count is outside 1-50 |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include spintax (PLAN_FEATURE_UNAVAILABLE), or the key lacks the numbers permission |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
POST/v1/developer/spintax/spinReturns one random wording from your text. Use it when your own system sends the final text and you just want one variation.
1 required1 response
/v1/developer/spintax/spinReturns one random wording from your text. Use it when your own system sends the final text and you just want one variation.
Request Fields
template
Requiredstring, 1-10,000 characters • body
Your message with choices in curly braces, separated by |. Each {Hi|Hello|Hey} becomes one of its options, so one text gives many wordings.
Example:{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today.| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| template | string, 1-10,000 characters | body | Yes | Your message with choices in curly braces, separated by |. Each {Hi|Hello|Hey} becomes one of its options, so one text gives many wordings.Example: {Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today. |
Request Body
{
"template": "{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today."
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/spintax/spin" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"template": "{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today."
}'Responses
{
"template": "{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today.",
"result": "Hey friend! Our sale ends today."
}Status Codes
Created
Bad Request - template is missing, empty or longer than 10,000 characters
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include spintax (PLAN_FEATURE_UNAVAILABLE), or the key lacks the numbers permission
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - template is missing, empty or longer than 10,000 characters |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include spintax (PLAN_FEATURE_UNAVAILABLE), or the key lacks the numbers permission |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
Reseller - Domains
Price, register, renew and manage the DNS of domains you resell. Purchases are paid from your prepaid USD wallet at reseller prices; when the wallet is short, pay the difference by card with Pay for a Domain by Card. Needs an API key with the domains permission, which only an active reseller can grant; other keys get 403 API_KEY_PERMISSION_DENIED. Keys limited to certain devices or chats can't use these routes. Guide: /docs/domains-api.
GET/v1/developer/domains/tldsShows every domain ending you can sell (.com, .io, ...) with your reseller price to register and to renew for one year. Prices are in US cents and are the full amount taken from your wallet.
0 required2 responses
/v1/developer/domains/tldsShows every domain ending you can sell (.com, .io, ...) with your reseller price to register and to renew for one year. Prices are in US cents and are the full amount taken from your wallet.
Request Fields
q
Optionalstring, up to 63 chars • query
Only show endings that contain this text. Leave it out to see everything.
Example:copage
Optionalinteger, 1 or more • query
Which page of results to show. Starts at 1 (the default).
Example:1pageSize
Optionalinteger, 1-200 • query
How many endings to show per page. Default 50.
Example:50| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| q | string, up to 63 chars | query | No | Only show endings that contain this text. Leave it out to see everything.Example: co |
| page | integer, 1 or more | query | No | Which page of results to show. Starts at 1 (the default).Example: 1 |
| pageSize | integer, 1-200 | query | No | How many endings to show per page. Default 50.Example: 50 |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/domains/tlds" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"total": 412,
"page": 1,
"pageSize": 50,
"currency": "USD",
"channel": "reseller",
"items": [
{ "tld": "com", "registerPriceCents": 1049, "renewPriceCents": 1149 },
{ "tld": "io", "registerPriceCents": 3899, "renewPriceCents": 4299 }
]
}{
"statusCode": 403,
"error": "Forbidden",
"code": "API_KEY_PERMISSION_DENIED",
"service": "domains",
"message": "This API key does not have the \"domains\" permission",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/tlds"
}Status Codes
Success
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 500 | Internal Server Error |
POST/v1/developer/domains/checkTells you whether a domain can be bought and what it costs you. purchasable is false when the name is already taken or its ending can't be sold right now; the prices are then null. A missing domain returns 400; a badly written one returns 400 DOMAIN_INVALID.
1 required3 responses
/v1/developer/domains/checkTells you whether a domain can be bought and what it costs you. purchasable is false when the name is already taken or its ending can't be sold right now; the prices are then null. A missing domain returns 400; a badly written one returns 400 DOMAIN_INVALID.
Request Fields
domain
Requiredstring, 1-253 chars • body
The full domain you want to check, including its ending.
Example:example.com| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| domain | string, 1-253 chars | body | Yes | The full domain you want to check, including its ending.Example: example.com |
Request Body
{ "domain": "example.com" }Request Examples
curl -X POST "https://api.msgera.io/v1/developer/domains/check" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "domain": "example.com" }'Responses
{
"domain": "example.com",
"available": true,
"purchasable": true,
"priceCents": 1049,
"renewPriceCents": 1149,
"currency": "USD"
}{
"domain": "google.com",
"available": false,
"purchasable": false,
"priceCents": null,
"renewPriceCents": null,
"currency": "USD"
}{
"statusCode": 400,
"error": "Error",
"code": "DOMAIN_INVALID",
"message": "Enter a valid domain name.",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/check"
}Status Codes
Created
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly.
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 503 | Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly. |
POST/v1/developer/domains/registerBuys a domain. The price Ă— years is taken from your wallet first, then the registrar is asked to register it. The request waits up to about 30 seconds: you usually get status ACTIVE. If the registrar is slower, or its answer is unclear, you get status PENDING and the order finishes in the background - check Get Domain later. If the registrar refuses, the money is refunded and you get 503 PROVIDER_UNAVAILABLE. Not enough money in the wallet? Use Pay for a Domain by Card instead.
11 required4 responses
/v1/developer/domains/registerBuys a domain. The price Ă— years is taken from your wallet first, then the registrar is asked to register it. The request waits up to about 30 seconds: you usually get status ACTIVE. If the registrar is slower, or its answer is unclear, you get status PENDING and the order finishes in the background - check Get Domain later. If the registrar refuses, the money is refunded and you get 503 PROVIDER_UNAVAILABLE. Not enough money in the wallet? Use Pay for a Domain by Card instead.
Request Fields
domain
Requiredstring • body
The domain to buy. Check it first with Check Domain Availability.
Example:example.comyears
Requiredinteger, 1-10 • body
How many years to register it for. You pay for all of them now.
Example:1contact
Requiredobject • body
Who owns the domain. Registries require these details; the fields are listed below.
contact.firstName
Requiredstring, 1-80 chars • body
First name of the domain owner (the registrant).
Example:Janecontact.lastName
Requiredstring, 1-80 chars • body
Last name of the domain owner.
Example:Doecontact.organization
Optionalstring, 1-120 chars • body
Company name, if the domain belongs to a business. Leave it out for a person.
Example:Acme Inc.contact.address1
Requiredstring, 1-120 chars • body
Street address of the owner.
Example:1 Market Stcontact.address2
Optionalstring, 1-120 chars • body
Second address line, such as a suite or floor.
Example:Suite 400contact.city
Requiredstring, 1-80 chars • body
City.
Example:San Franciscocontact.state
Optionalstring, 1-80 chars • body
State, province or region, where the country uses one.
Example:CAcontact.postalCode
Requiredstring, 1-20 chars • body
Postal or ZIP code.
Example:94105contact.country
Requiredstring, 2 uppercase letters • body
Country of the owner as a two-letter ISO code.
Example:UScontact.email
Requiredstring (email) • body
Email of the owner. Domain registries may send ownership notices here, so use a real inbox.
Example:[email protected]contact.phone
Requiredstring, + then 7-15 digits • body
Phone of the owner in international format, starting with + and the country code.
Example:+14155550123privacy
Optionalboolean • body
Hide the owner's details from public domain lookups (WHOIS). On by default.
Example:trueidempotencyKey
Optionalstring (UUID) • body
A unique ID you create for this purchase. If the request fails and you retry with the same key, you are never charged twice and get the first result back.
Example:7f9c2b1e-4d3a-4c8b-9e2f-1a5b6c7d8e9f| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| domain | string | body | Yes | The domain to buy. Check it first with Check Domain Availability.Example: example.com |
| years | integer, 1-10 | body | Yes | How many years to register it for. You pay for all of them now.Example: 1 |
| contact | object | body | Yes | Who owns the domain. Registries require these details; the fields are listed below. |
| contact.firstName | string, 1-80 chars | body | Yes | First name of the domain owner (the registrant).Example: Jane |
| contact.lastName | string, 1-80 chars | body | Yes | Last name of the domain owner.Example: Doe |
| contact.organization | string, 1-120 chars | body | No | Company name, if the domain belongs to a business. Leave it out for a person.Example: Acme Inc. |
| contact.address1 | string, 1-120 chars | body | Yes | Street address of the owner.Example: 1 Market St |
| contact.address2 | string, 1-120 chars | body | No | Second address line, such as a suite or floor.Example: Suite 400 |
| contact.city | string, 1-80 chars | body | Yes | City.Example: San Francisco |
| contact.state | string, 1-80 chars | body | No | State, province or region, where the country uses one.Example: CA |
| contact.postalCode | string, 1-20 chars | body | Yes | Postal or ZIP code.Example: 94105 |
| contact.country | string, 2 uppercase letters | body | Yes | Country of the owner as a two-letter ISO code.Example: US |
| contact.email | string (email) | body | Yes | Email of the owner. Domain registries may send ownership notices here, so use a real inbox.Example: [email protected] |
| contact.phone | string, + then 7-15 digits | body | Yes | Phone of the owner in international format, starting with + and the country code.Example: +14155550123 |
| privacy | boolean | body | No | Hide the owner's details from public domain lookups (WHOIS). On by default.Example: true |
| idempotencyKey | string (UUID) | body | No | A unique ID you create for this purchase. If the request fails and you retry with the same key, you are never charged twice and get the first result back.Example: 7f9c2b1e-4d3a-4c8b-9e2f-1a5b6c7d8e9f |
Request Body
{
"domain": "example.com",
"years": 1,
"privacy": true,
"idempotencyKey": "7f9c2b1e-4d3a-4c8b-9e2f-1a5b6c7d8e9f",
"contact": {
"firstName": "Jane",
"lastName": "Doe",
"organization": "Acme Inc.",
"address1": "1 Market St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US",
"email": "[email protected]",
"phone": "+14155550123"
}
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/domains/register" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"domain": "example.com",
"years": 1,
"privacy": true,
"idempotencyKey": "7f9c2b1e-4d3a-4c8b-9e2f-1a5b6c7d8e9f",
"contact": {
"firstName": "Jane",
"lastName": "Doe",
"organization": "Acme Inc.",
"address1": "1 Market St",
"city": "San Francisco",
"state": "CA",
"postalCode": "94105",
"country": "US",
"email": "[email protected]",
"phone": "+14155550123"
}
}'Responses
{
"id": "4b0f6c2e-8a1d-4f3b-9c7e-2d5a6b8c9e01",
"name": "example.com",
"status": "ACTIVE",
"autoRenew": true,
"nameserverMode": "MSGERA",
"expiresAt": "2027-10-02T09:15:00.000Z",
"createdAt": "2026-10-02T09:14:41.000Z"
}{
"id": "4b0f6c2e-8a1d-4f3b-9c7e-2d5a6b8c9e01",
"name": "example.com",
"status": "PENDING",
"autoRenew": true,
"nameserverMode": "MSGERA",
"expiresAt": null,
"createdAt": "2026-10-02T09:14:41.000Z"
}{
"statusCode": 400,
"error": "Error",
"code": "DOMAIN_UNAVAILABLE",
"message": "This domain is not available.",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/register"
}{
"statusCode": 402,
"error": "Error",
"code": "INSUFFICIENT_FUNDS",
"message": "Wallet balance is too low. Add funds to continue.",
"balanceCents": 1200,
"requiredCents": 2399,
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/register"
}Status Codes
Created
Bad Request - a field is missing or wrong, the domain is taken or already in your account (DOMAIN_UNAVAILABLE), its ending can't be sold right now (DOMAIN_PRICE_UNAVAILABLE), or the name is invalid (DOMAIN_INVALID).
Unauthorized - missing or invalid API key
Payment Required - the wallet balance is too low (INSUFFICIENT_FUNDS, with balanceCents and requiredCents). Nothing is charged. Top up, or use the purchase checkout to pay the difference by card.
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Conflict - the same request is still running (DUPLICATE_REQUEST), the idempotency key was already used for a different purchase (IDEMPOTENCY_KEY_REUSED), or the purchase failed earlier and was refunded (PURCHASE_FAILED).
Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly.
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a field is missing or wrong, the domain is taken or already in your account (DOMAIN_UNAVAILABLE), its ending can't be sold right now (DOMAIN_PRICE_UNAVAILABLE), or the name is invalid (DOMAIN_INVALID). |
| 401 | Unauthorized - missing or invalid API key |
| 402 | Payment Required - the wallet balance is too low (INSUFFICIENT_FUNDS, with balanceCents and requiredCents). Nothing is charged. Top up, or use the purchase checkout to pay the difference by card. |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 409 | Conflict - the same request is still running (DUPLICATE_REQUEST), the idempotency key was already used for a different purchase (IDEMPOTENCY_KEY_REUSED), or the purchase failed earlier and was refunded (PURCHASE_FAILED). |
| 503 | Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly. |
POST/v1/developer/domains/:name/renewAdds years to a domain you own so it doesn't expire. Works for ACTIVE and EXPIRED domains. The renew price Ă— years is taken from your wallet; if the registrar refuses, it is refunded. If the registrar takes longer than about 30 seconds, the domain comes back with its old expiresAt and the renewal finishes in the background - check Get Domain later.
1 required3 responses
/v1/developer/domains/:name/renewAdds years to a domain you own so it doesn't expire. Works for ACTIVE and EXPIRED domains. The renew price Ă— years is taken from your wallet; if the registrar refuses, it is refunded. If the registrar takes longer than about 30 seconds, the domain comes back with its old expiresAt and the renewal finishes in the background - check Get Domain later.
Request Fields
name
Requiredstring • path
The domain you want to work with, exactly as it appears in List Domains.
Example:example.comyears
Optionalinteger, 1-10 • body
How many years to add. Default 1.
Example:1idempotencyKey
Optionalstring, 1-64 chars: letters, digits, - or _ • body
A unique ID you create for this renewal. If the request fails and you retry with the same key, you are never charged twice.
Example:renew-example-com-2026| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| name | string | path | Yes | The domain you want to work with, exactly as it appears in List Domains.Example: example.com |
| years | integer, 1-10 | body | No | How many years to add. Default 1.Example: 1 |
| idempotencyKey | string, 1-64 chars: letters, digits, - or _ | body | No | A unique ID you create for this renewal. If the request fails and you retry with the same key, you are never charged twice.Example: renew-example-com-2026 |
Request Body
{ "years": 1, "idempotencyKey": "renew-example-com-2026" }Request Examples
curl -X POST "https://api.msgera.io/v1/developer/domains/{name}/renew" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "years": 1, "idempotencyKey": "renew-example-com-2026" }'Responses
{
"id": "4b0f6c2e-8a1d-4f3b-9c7e-2d5a6b8c9e01",
"name": "example.com",
"status": "ACTIVE",
"autoRenew": true,
"nameserverMode": "MSGERA",
"expiresAt": "2027-10-02T09:15:00.000Z",
"createdAt": "2026-10-02T09:14:41.000Z"
}{
"statusCode": 402,
"error": "Error",
"code": "INSUFFICIENT_FUNDS",
"message": "Wallet balance is too low. Add funds to continue.",
"balanceCents": 1200,
"requiredCents": 2399,
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/example.com/renew"
}{
"statusCode": 409,
"error": "Error",
"code": "DUPLICATE_REQUEST",
"message": "A renewal for this domain is already in progress.",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/example.com/renew"
}Status Codes
Created
Bad Request - a field is wrong, the name is now held by someone else (DOMAIN_UNAVAILABLE), its ending can't be priced (DOMAIN_PRICE_UNAVAILABLE), or the current expiry date is unknown (DOMAIN_INVALID).
Unauthorized - missing or invalid API key
Payment Required - the wallet balance is too low (INSUFFICIENT_FUNDS, with balanceCents and requiredCents). Nothing is charged. Top up, or use the purchase checkout to pay the difference by card.
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - the domain isn't on your account, or isn't ACTIVE or EXPIRED (DOMAIN_NOT_FOUND).
Conflict - the same request is still running (DUPLICATE_REQUEST), the idempotency key was already used for a different purchase (IDEMPOTENCY_KEY_REUSED), or the purchase failed earlier and was refunded (PURCHASE_FAILED).
Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly.
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a field is wrong, the name is now held by someone else (DOMAIN_UNAVAILABLE), its ending can't be priced (DOMAIN_PRICE_UNAVAILABLE), or the current expiry date is unknown (DOMAIN_INVALID). |
| 401 | Unauthorized - missing or invalid API key |
| 402 | Payment Required - the wallet balance is too low (INSUFFICIENT_FUNDS, with balanceCents and requiredCents). Nothing is charged. Top up, or use the purchase checkout to pay the difference by card. |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - the domain isn't on your account, or isn't ACTIVE or EXPIRED (DOMAIN_NOT_FOUND). |
| 409 | Conflict - the same request is still running (DUPLICATE_REQUEST), the idempotency key was already used for a different purchase (IDEMPOTENCY_KEY_REUSED), or the purchase failed earlier and was refunded (PURCHASE_FAILED). |
| 503 | Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly. |
GET/v1/developer/domainsLists every domain on your account, newest first, as a plain array. status is PENDING (registering), ACTIVE, FAILED (registration refused, money refunded) or EXPIRED. nameserverMode is MSGERA (we host DNS) or CUSTOM (your own nameservers).
0 required1 response
/v1/developer/domainsLists every domain on your account, newest first, as a plain array. status is PENDING (registering), ACTIVE, FAILED (registration refused, money refunded) or EXPIRED. nameserverMode is MSGERA (we host DNS) or CUSTOM (your own nameservers).
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/domains" \
-H "X-API-Key: wak_your_api_key_here"Responses
[
{
"id": "4b0f6c2e-8a1d-4f3b-9c7e-2d5a6b8c9e01",
"name": "example.com",
"status": "ACTIVE",
"autoRenew": true,
"nameserverMode": "MSGERA",
"expiresAt": "2027-10-02T09:15:00.000Z",
"createdAt": "2026-10-02T09:14:41.000Z"
}
]Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 500 | Internal Server Error |
GET/v1/developer/domains/:nameReturns one domain on your account. Use it to follow a registration that came back PENDING, or a renewal that finished in the background.
1 required1 response
/v1/developer/domains/:nameReturns one domain on your account. Use it to follow a registration that came back PENDING, or a renewal that finished in the background.
Request Fields
name
Requiredstring • path
The domain you want to work with, exactly as it appears in List Domains.
Example:example.com| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| name | string | path | Yes | The domain you want to work with, exactly as it appears in List Domains.Example: example.com |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/domains/{name}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "4b0f6c2e-8a1d-4f3b-9c7e-2d5a6b8c9e01",
"name": "example.com",
"status": "ACTIVE",
"autoRenew": true,
"nameserverMode": "MSGERA",
"expiresAt": "2027-10-02T09:15:00.000Z",
"createdAt": "2026-10-02T09:14:41.000Z"
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - the domain isn't on your account (DOMAIN_NOT_FOUND).
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - the domain isn't on your account (DOMAIN_NOT_FOUND). |
| 500 | Internal Server Error |
GET/v1/developer/domains/:name/dnsLists the DNS records of an ACTIVE domain. Records in the product group are kept up to date by Msgera for connected services such as mailboxes; custom ones are yours. When the domain uses your own nameservers (CUSTOM), our records aren't used, so items is empty.
1 required2 responses
/v1/developer/domains/:name/dnsLists the DNS records of an ACTIVE domain. Records in the product group are kept up to date by Msgera for connected services such as mailboxes; custom ones are yours. When the domain uses your own nameservers (CUSTOM), our records aren't used, so items is empty.
Request Fields
name
Requiredstring • path
The domain you want to work with, exactly as it appears in List Domains.
Example:example.com| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| name | string | path | Yes | The domain you want to work with, exactly as it appears in List Domains.Example: example.com |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/domains/{name}/dns" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"domain": "example.com",
"nameserverMode": "MSGERA",
"items": [
{
"type": "A",
"name": "@",
"address": "203.0.113.10",
"ttl": 3600,
"group": "custom"
},
{
"type": "MX",
"name": "@",
"address": "mx1.mail.example.net",
"value": "mx1.mail.example.net",
"exchange": "mx1.mail.example.net",
"preference": 10,
"ttl": 3600,
"group": "product"
}
]
}{
"domain": "example.com",
"nameserverMode": "CUSTOM",
"items": []
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - the domain isn't on your account or isn't ACTIVE (DOMAIN_NOT_FOUND).
Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly.
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - the domain isn't on your account or isn't ACTIVE (DOMAIN_NOT_FOUND). |
| 503 | Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly. |
PUT/v1/developer/domains/:name/dnsAdds or overwrites 1-500 DNS records and returns the full list. Existing records that conflict with the ones you send are overwritten; records you don't send are kept. Every record needs value or address, even when you also send exchange, cname, nameserver or target. Only works while the domain uses Msgera nameservers.
4 required2 responses
/v1/developer/domains/:name/dnsAdds or overwrites 1-500 DNS records and returns the full list. Existing records that conflict with the ones you send are overwritten; records you don't send are kept. Every record needs value or address, even when you also send exchange, cname, nameserver or target. Only works while the domain uses Msgera nameservers.
Request Fields
name
Requiredstring • path
The domain you want to work with, exactly as it appears in List Domains.
Example:example.comitems
Requiredarray of objects, 1-500 • body
The records to save. Each one uses the fields below.
items[].type
Requiredstring • body
Kind of record.
Allowed:A · AAAA · CNAME · MX · TXT · SRV · CAA · NSExample: Aitems[].name
Requiredstring, 1-253 chars • body
The host the record is for: @ for the domain itself, or a subdomain part such as www.
Example:@items[].value
Optionalstring, 1-2048 chars • body
What the record points to or contains (target host, text, ...). Send value or address.
Example:v=spf1 include:_spf.example.net ~allitems[].address
Optionalstring, 1-2048 chars • body
The IP address, for A and AAAA records. Send value or address.
Example:203.0.113.10items[].ttl
Optionalinteger, 60-86400 • body
How many seconds others may cache the record. Default 3600 (one hour).
Example:3600items[].preference
Optionalinteger • body
MX only: priority of this mail server; lower numbers are tried first. Default 0.
Example:10items[].exchange
Optionalstring • body
MX only: the mail server host. Used instead of value when sent.
Example:mx1.mail.example.netitems[].cname
Optionalstring • body
CNAME only: the host this name is an alias of. Used instead of value when sent.
Example:example.comitems[].nameserver
Optionalstring • body
NS only: the nameserver host. Used instead of value when sent.
Example:ns1.example.netitems[].service
Optionalstring • body
SRV only: the service name.
Example:_sipitems[].protocol
Optionalstring • body
SRV only: the protocol.
Example:_tcpitems[].port
Optionalinteger • body
SRV only: the port. Default 0.
Example:5060items[].weight
Optionalinteger • body
SRV only: share of traffic among servers with the same priority. Default 0.
Example:5items[].target
Optionalstring • body
SRV only: the host that runs the service. Used instead of value when sent.
Example:sip.example.comitems[].priority
Optionalinteger, 0-65535 • body
SRV priority. Accepted, but SRV records are currently saved with priority 0.
Example:10items[].group
Optionalstring • body
Leave it out or send custom. Records in the product group are managed by Msgera and can't be written (400 DNS_NOT_MANAGED).
Allowed:custom · productExample: custom| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| name | string | path | Yes | The domain you want to work with, exactly as it appears in List Domains.Example: example.com |
| items | array of objects, 1-500 | body | Yes | The records to save. Each one uses the fields below. |
| items[].type | string | body | Yes | Kind of record.Allowed: A · AAAA · CNAME · MX · TXT · SRV · CAA · NSExample: A |
| items[].name | string, 1-253 chars | body | Yes | The host the record is for: @ for the domain itself, or a subdomain part such as www.Example: @ |
| items[].value | string, 1-2048 chars | body | No | What the record points to or contains (target host, text, ...). Send value or address.Example: v=spf1 include:_spf.example.net ~all |
| items[].address | string, 1-2048 chars | body | No | The IP address, for A and AAAA records. Send value or address.Example: 203.0.113.10 |
| items[].ttl | integer, 60-86400 | body | No | How many seconds others may cache the record. Default 3600 (one hour).Example: 3600 |
| items[].preference | integer | body | No | MX only: priority of this mail server; lower numbers are tried first. Default 0.Example: 10 |
| items[].exchange | string | body | No | MX only: the mail server host. Used instead of value when sent.Example: mx1.mail.example.net |
| items[].cname | string | body | No | CNAME only: the host this name is an alias of. Used instead of value when sent.Example: example.com |
| items[].nameserver | string | body | No | NS only: the nameserver host. Used instead of value when sent.Example: ns1.example.net |
| items[].service | string | body | No | SRV only: the service name.Example: _sip |
| items[].protocol | string | body | No | SRV only: the protocol.Example: _tcp |
| items[].port | integer | body | No | SRV only: the port. Default 0.Example: 5060 |
| items[].weight | integer | body | No | SRV only: share of traffic among servers with the same priority. Default 0.Example: 5 |
| items[].target | string | body | No | SRV only: the host that runs the service. Used instead of value when sent.Example: sip.example.com |
| items[].priority | integer, 0-65535 | body | No | SRV priority. Accepted, but SRV records are currently saved with priority 0.Example: 10 |
| items[].group | string | body | No | Leave it out or send custom. Records in the product group are managed by Msgera and can't be written (400 DNS_NOT_MANAGED).Allowed: custom · productExample: custom |
Request Body
{
"items": [
{ "type": "A", "name": "@", "address": "203.0.113.10", "ttl": 3600 },
{ "type": "CNAME", "name": "www", "value": "example.com" },
{ "type": "TXT", "name": "@", "value": "v=spf1 include:_spf.example.net ~all" }
]
}Request Examples
curl -X PUT "https://api.msgera.io/v1/developer/domains/{name}/dns" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"items": [
{ "type": "A", "name": "@", "address": "203.0.113.10", "ttl": 3600 },
{ "type": "CNAME", "name": "www", "value": "example.com" },
{ "type": "TXT", "name": "@", "value": "v=spf1 include:_spf.example.net ~all" }
]
}'Responses
{
"domain": "example.com",
"nameserverMode": "MSGERA",
"items": [
{
"type": "A",
"name": "@",
"address": "203.0.113.10",
"ttl": 3600,
"group": "custom"
},
{
"type": "MX",
"name": "@",
"address": "mx1.mail.example.net",
"value": "mx1.mail.example.net",
"exchange": "mx1.mail.example.net",
"preference": 10,
"ttl": 3600,
"group": "product"
}
]
}{
"statusCode": 400,
"error": "Error",
"code": "DNS_NOT_MANAGED",
"message": "DNS can only be edited while using Msgera nameservers.",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/example.com/dns"
}Status Codes
Success
Bad Request - a field is wrong, a record has no value or address or was refused by the registrar (DNS_INVALID_RECORD), or the domain uses its own nameservers or a record is in the product group (DNS_NOT_MANAGED).
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - the domain isn't on your account or isn't ACTIVE (DOMAIN_NOT_FOUND).
Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly.
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - a field is wrong, a record has no value or address or was refused by the registrar (DNS_INVALID_RECORD), or the domain uses its own nameservers or a record is in the product group (DNS_NOT_MANAGED). |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - the domain isn't on your account or isn't ACTIVE (DOMAIN_NOT_FOUND). |
| 503 | Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly. |
GET/v1/developer/domains/:name/nameserversShows who answers DNS for the domain. MSGERA means we do and you edit records with Save DNS Records; hosts is then empty. CUSTOM means the nameservers in hosts do, so DNS is edited wherever those are hosted.
1 required2 responses
/v1/developer/domains/:name/nameserversShows who answers DNS for the domain. MSGERA means we do and you edit records with Save DNS Records; hosts is then empty. CUSTOM means the nameservers in hosts do, so DNS is edited wherever those are hosted.
Request Fields
name
Requiredstring • path
The domain you want to work with, exactly as it appears in List Domains.
Example:example.com| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| name | string | path | Yes | The domain you want to work with, exactly as it appears in List Domains.Example: example.com |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/domains/{name}/nameservers" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"domain": "example.com",
"mode": "CUSTOM",
"hosts": ["ns1.example.net", "ns2.example.net"]
}{ "domain": "example.com", "mode": "MSGERA", "hosts": [] }Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - the domain isn't on your account or isn't ACTIVE (DOMAIN_NOT_FOUND).
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - the domain isn't on your account or isn't ACTIVE (DOMAIN_NOT_FOUND). |
| 500 | Internal Server Error |
PUT/v1/developer/domains/:name/nameserversSwitches the domain between Msgera nameservers and your own. Switching back to MSGERA restores the mail records of any mailboxes on the domain. Changes can take a few hours to reach everyone on the internet.
2 required2 responses
/v1/developer/domains/:name/nameserversSwitches the domain between Msgera nameservers and your own. Switching back to MSGERA restores the mail records of any mailboxes on the domain. Changes can take a few hours to reach everyone on the internet.
Request Fields
name
Requiredstring • path
The domain you want to work with, exactly as it appears in List Domains.
Example:example.commode
Requiredstring • body
MSGERA to let Msgera host DNS, CUSTOM to use nameservers from another DNS provider.
Allowed:MSGERA · CUSTOMExample: CUSTOMhosts
Optionalarray of strings, 2-13 items, each 1-253 chars • body
Required when mode is CUSTOM: the nameservers your DNS provider gave you. They must be different, must exist, and can't be inside the domain itself. Ignored for MSGERA.
Example:["ns1.example.net", "ns2.example.net"]| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| name | string | path | Yes | The domain you want to work with, exactly as it appears in List Domains.Example: example.com |
| mode | string | body | Yes | MSGERA to let Msgera host DNS, CUSTOM to use nameservers from another DNS provider.Allowed: MSGERA · CUSTOMExample: CUSTOM |
| hosts | array of strings, 2-13 items, each 1-253 chars | body | No | Required when mode is CUSTOM: the nameservers your DNS provider gave you. They must be different, must exist, and can't be inside the domain itself. Ignored for MSGERA.Example: ["ns1.example.net", "ns2.example.net"] |
Request Body
{
"mode": "CUSTOM",
"hosts": ["ns1.example.net", "ns2.example.net"]
}Request Examples
curl -X PUT "https://api.msgera.io/v1/developer/domains/{name}/nameservers" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"mode": "CUSTOM",
"hosts": ["ns1.example.net", "ns2.example.net"]
}'Responses
{
"domain": "example.com",
"mode": "CUSTOM",
"hosts": ["ns1.example.net", "ns2.example.net"]
}{
"statusCode": 400,
"error": "Error",
"code": "NAMESERVERS_INVALID",
"message": "Nameserver \"ns3.example.net\" could not be found.",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/example.com/nameservers"
}Status Codes
Success
Bad Request - mode is missing or hosts doesn't have 2-13 entries, or a nameserver is invalid, repeated, inside the domain itself or doesn't exist (NAMESERVERS_INVALID).
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - the domain isn't on your account or isn't ACTIVE (DOMAIN_NOT_FOUND).
Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly.
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - mode is missing or hosts doesn't have 2-13 entries, or a nameserver is invalid, repeated, inside the domain itself or doesn't exist (NAMESERVERS_INVALID). |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - the domain isn't on your account or isn't ACTIVE (DOMAIN_NOT_FOUND). |
| 503 | Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly. |
POST/v1/developer/domains/purchases/checkoutUse this when your wallet balance can't cover a registration or renewal. You get a checkoutUrl: open it (or send it to whoever pays) to pay the missing amount by card. The payment goes into your wallet and the domain is then registered or renewed automatically - follow it with Get Domain Purchase. The card amount is the shortfall, at least $10 (1000 cents) and at most $5,000. If the wallet already has enough, you get 409 WALLET_COVERS_PURCHASE: call Register or Renew directly. The checkout link expires after 24 hours.
3 required2 responses
/v1/developer/domains/purchases/checkoutUse this when your wallet balance can't cover a registration or renewal. You get a checkoutUrl: open it (or send it to whoever pays) to pay the missing amount by card. The payment goes into your wallet and the domain is then registered or renewed automatically - follow it with Get Domain Purchase. The card amount is the shortfall, at least $10 (1000 cents) and at most $5,000. If the wallet already has enough, you get 409 WALLET_COVERS_PURCHASE: call Register or Renew directly. The checkout link expires after 24 hours.
Request Fields
kind
Requiredstring • body
What you are buying: a new domain or more years for one you own.
Allowed:DOMAIN_REGISTER · DOMAIN_RENEWExample: DOMAIN_REGISTERpayload
Requiredobject • body
The same body you would send to Register Domain (for DOMAIN_REGISTER), or to Renew Domain plus a domain field (for DOMAIN_RENEW). It is checked by the same rules. Any idempotencyKey inside it is ignored.
Example:{ "domain": "example.com", "years": 1 }idempotencyKey
Requiredstring, 1-64 chars: letters, digits, - or _ • body
A unique ID you create for this purchase. Retrying with the same key returns the same checkout instead of opening a second one.
Example:order-1042| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| kind | string | body | Yes | What you are buying: a new domain or more years for one you own.Allowed: DOMAIN_REGISTER · DOMAIN_RENEWExample: DOMAIN_REGISTER |
| payload | object | body | Yes | The same body you would send to Register Domain (for DOMAIN_REGISTER), or to Renew Domain plus a domain field (for DOMAIN_RENEW). It is checked by the same rules. Any idempotencyKey inside it is ignored.Example: { "domain": "example.com", "years": 1 } |
| idempotencyKey | string, 1-64 chars: letters, digits, - or _ | body | Yes | A unique ID you create for this purchase. Retrying with the same key returns the same checkout instead of opening a second one.Example: order-1042 |
Request Body
{
"kind": "DOMAIN_RENEW",
"idempotencyKey": "order-1042",
"payload": { "domain": "example.com", "years": 1 }
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/domains/purchases/checkout" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"kind": "DOMAIN_RENEW",
"idempotencyKey": "order-1042",
"payload": { "domain": "example.com", "years": 1 }
}'Responses
{
"intentId": "3f6d2a9b-7c1e-4b5a-9d8f-0e2c4a6b8d10",
"status": "AWAITING_PAYMENT",
"checkoutUrl": "https://pay.example.com/checkout/cs_7Hq2Lm",
"topupCents": 1000,
"quotedTotalCents": 1049,
"expiresAt": "2026-10-03T09:15:00.000Z"
}{
"statusCode": 409,
"error": "Error",
"code": "WALLET_COVERS_PURCHASE",
"message": "Your wallet balance already covers this purchase.",
"balanceCents": 5000,
"requiredCents": 1049,
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/domains/purchases/checkout"
}Status Codes
Created
Bad Request - a field or the payload is wrong, the domain can't be bought (DOMAIN_UNAVAILABLE, DOMAIN_PRICE_UNAVAILABLE, DOMAIN_INVALID), or the card amount would be over $5,000.
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - for DOMAIN_RENEW, the domain isn't on your account (DOMAIN_NOT_FOUND).
Conflict - the wallet already covers the purchase (WALLET_COVERS_PURCHASE, with balanceCents and requiredCents), the key was used for a different purchase (IDEMPOTENCY_KEY_REUSED), or the same checkout is still being set up (DUPLICATE_REQUEST).
Service Unavailable - card payment couldn't be started (PAYMENT_SETUP_FAILED). Try again.
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a field or the payload is wrong, the domain can't be bought (DOMAIN_UNAVAILABLE, DOMAIN_PRICE_UNAVAILABLE, DOMAIN_INVALID), or the card amount would be over $5,000. |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - for DOMAIN_RENEW, the domain isn't on your account (DOMAIN_NOT_FOUND). |
| 409 | Conflict - the wallet already covers the purchase (WALLET_COVERS_PURCHASE, with balanceCents and requiredCents), the key was used for a different purchase (IDEMPOTENCY_KEY_REUSED), or the same checkout is still being set up (DUPLICATE_REQUEST). |
| 503 | Service Unavailable - card payment couldn't be started (PAYMENT_SETUP_FAILED). Try again. |
GET/v1/developer/domains/purchases/:idShows how a card-paid domain purchase is going. AWAITING_PAYMENT: the card payment hasn't been made yet. PAID: the money reached your wallet and the purchase is running. COMPLETED: done, resultRef points to the result. FAILED: the purchase couldn't be made (see failureCode and failureMessage); the money stays in your wallet. EXPIRED: nobody paid within 24 hours. For domains, resultRef is the domain name.
1 required1 response
/v1/developer/domains/purchases/:idShows how a card-paid domain purchase is going. AWAITING_PAYMENT: the card payment hasn't been made yet. PAID: the money reached your wallet and the purchase is running. COMPLETED: done, resultRef points to the result. FAILED: the purchase couldn't be made (see failureCode and failureMessage); the money stays in your wallet. EXPIRED: nobody paid within 24 hours. For domains, resultRef is the domain name.
Request Fields
id
Requiredstring (UUID) • path
The intentId you got from Pay for a Domain by Card.
Example:3f6d2a9b-7c1e-4b5a-9d8f-0e2c4a6b8d10| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The intentId you got from Pay for a Domain by Card.Example: 3f6d2a9b-7c1e-4b5a-9d8f-0e2c4a6b8d10 |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/domains/purchases/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "3f6d2a9b-7c1e-4b5a-9d8f-0e2c4a6b8d10",
"kind": "DOMAIN_RENEW",
"status": "COMPLETED",
"failureCode": null,
"failureMessage": null,
"resultRef": "example.com",
"topupCents": 1000,
"quotedTotalCents": 1149,
"expiresAt": "2026-10-03T09:15:00.000Z",
"summary": { "domain": "example.com", "years": 1 }
}Status Codes
Success
Bad Request - the id isn't a UUID.
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - no domain purchase with this id on your account (PURCHASE_NOT_FOUND).
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - the id isn't a UUID. |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "domains" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - no domain purchase with this id on your account (PURCHASE_NOT_FOUND). |
| 500 | Internal Server Error |
Reseller - Mailboxes
Create and manage email mailboxes on domains you registered with Msgera. Mailboxes are paid from your prepaid USD wallet; when it is short, pay the difference by card with Pay for a Mailbox by Card. Needs an API key with the email permission, which only an active reseller can grant; other keys get 403 API_KEY_PERMISSION_DENIED. Keys limited to certain devices or chats can't use these routes. To send email from a mailbox (send and send-bulk), see /docs/email-api.
GET/v1/catalog/email/plans/meLists the mailbox plans you can sell, with your price in US cents for the first month and for each renewal. Copy the id of a plan into planId when you create a mailbox.
0 required1 response
/v1/catalog/email/plans/meLists the mailbox plans you can sell, with your price in US cents for the first month and for each renewal. Copy the id of a plan into planId when you create a mailbox.
Request Examples
curl -X GET "https://api.msgera.io/v1/catalog/email/plans/me" \
-H "X-API-Key: wak_your_api_key_here"Responses
[
{
"id": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34",
"name": "Business 5 GB",
"quotaGb": 5,
"priceCents": 199,
"renewPriceCents": 199,
"currency": "USD"
}
]Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 500 | Internal Server Error |
GET/v1/email/mailboxes/quoteShows each mailbox plan with what you would pay today. Every paid subscription includes one free mailbox whose storage grows with the plan (included.quotaGb); a mailbox up to that size is free and a bigger one costs only the difference, so todayCents can be lower than priceCents (or 0). Use it to show a price before buying.
0 required1 response
/v1/email/mailboxes/quoteShows each mailbox plan with what you would pay today. Every paid subscription includes one free mailbox whose storage grows with the plan (included.quotaGb); a mailbox up to that size is free and a bigger one costs only the difference, so todayCents can be lower than priceCents (or 0). Use it to show a price before buying.
Request Examples
curl -X GET "https://api.msgera.io/v1/email/mailboxes/quote" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"included": { "total": 1, "used": 0, "remaining": 1, "quotaGb": 10 },
"plans": [
{
"id": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34",
"name": "Business 5 GB",
"quotaGb": 5,
"priceCents": 199,
"renewPriceCents": 199,
"todayCents": 0,
"renewTodayCents": 0,
"includedInPlan": true,
"currency": "USD"
}
]
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 500 | Internal Server Error |
GET/v1/email/mailboxes/checkTells you whether an address can be created before you pay: the username must follow the rules, the domain must be yours and active, and nobody may already use the address. status is available, invalid, taken or domain_not_ready, with a message for anything but available.
2 required2 responses
/v1/email/mailboxes/checkTells you whether an address can be created before you pay: the username must follow the rules, the domain must be yours and active, and nobody may already use the address. status is available, invalid, taken or domain_not_ready, with a message for anything but available.
Request Fields
domain
Requiredstring • query
The domain the mailbox goes on. It must be a domain you registered with Msgera and that is active.
Example:mybrand.comlocalPart
Requiredstring (1-64 chars) • query
The part before the @. Letters, numbers, dots, hyphens and underscores, starting and ending with a letter or number.
Example:hello| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| domain | string | query | Yes | The domain the mailbox goes on. It must be a domain you registered with Msgera and that is active.Example: mybrand.com |
| localPart | string (1-64 chars) | query | Yes | The part before the @. Letters, numbers, dots, hyphens and underscores, starting and ending with a letter or number.Example: hello |
Request Examples
curl -X GET "https://api.msgera.io/v1/email/mailboxes/check" \
-H "X-API-Key: wak_your_api_key_here"Responses
{ "status": "available", "address": "[email protected]" }{
"status": "taken",
"address": "[email protected]",
"message": "This address is already in use."
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 500 | Internal Server Error |
GET/v1/developer/email/mailboxesLists your mailboxes, newest first, as a plain array. Deleted ones aren't shown. status is ACTIVE, or SUSPENDED when a renewal wasn't paid in time (mail is kept, but nobody can log in, send or receive until it's paid). periodEnd is when the current paid month ends.
0 required2 responses
/v1/developer/email/mailboxesLists your mailboxes, newest first, as a plain array. Deleted ones aren't shown. status is ACTIVE, or SUSPENDED when a renewal wasn't paid in time (mail is kept, but nobody can log in, send or receive until it's paid). periodEnd is when the current paid month ends.
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/email/mailboxes" \
-H "X-API-Key: wak_your_api_key_here"Responses
[
{
"id": "9d1e7a3c-2b4f-4e6a-8c0d-5f7b9a1c3e2d",
"address": "[email protected]",
"status": "ACTIVE",
"quotaGb": 5,
"periodEnd": "2026-11-02T09:20:00.000Z",
"autoRenew": true,
"hasStoredCredentials": true
}
]{
"statusCode": 403,
"error": "Forbidden",
"code": "API_KEY_PERMISSION_DENIED",
"service": "email",
"message": "This API key does not have the \"email\" permission",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/email/mailboxes"
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 500 | Internal Server Error |
POST/v1/developer/email/mailboxesCreates an email address on a domain you registered with Msgera (it must be ACTIVE). The first month is taken from your wallet before the mailbox is made, and refunded if it can't be made. The charge is 0 when your plan still includes a free mailbox. While the domain uses Msgera nameservers, the mail DNS records are set up for you. Save the password from the response: it is how the owner logs in. Not enough money in the wallet? Use Pay for a Mailbox by Card instead.
3 required3 responses
/v1/developer/email/mailboxesCreates an email address on a domain you registered with Msgera (it must be ACTIVE). The first month is taken from your wallet before the mailbox is made, and refunded if it can't be made. The charge is 0 when your plan still includes a free mailbox. While the domain uses Msgera nameservers, the mail DNS records are set up for you. Save the password from the response: it is how the owner logs in. Not enough money in the wallet? Use Pay for a Mailbox by Card instead.
Request Fields
domain
Requiredstring • body
An ACTIVE domain on your account (see List Domains).
Example:example.comlocalPart
Requiredstring, 1-64 chars • body
The part before the @. Letters, digits, dots, dashes and underscores; it must start and end with a letter or digit. Stored in lowercase.
Example:helloplanId
Requiredstring (UUID) • body
Which mailbox plan (storage size and price) to use. Get it from List Mailbox Plans.
Example:c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34password
Optionalstring, at least 12 chars • body
The password the owner will log in with. Leave it out and a strong one is generated and returned to you.
Example:Spring-Garden-2026!idempotencyKey
Optionalstring (UUID) • body
A unique ID you create for this purchase. If the request fails and you retry with the same key, you are never charged twice and get the same mailbox back.
Example:5a7c9e1b-3d4f-4a6b-8c0d-2e4f6a8b0c1d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| domain | string | body | Yes | An ACTIVE domain on your account (see List Domains).Example: example.com |
| localPart | string, 1-64 chars | body | Yes | The part before the @. Letters, digits, dots, dashes and underscores; it must start and end with a letter or digit. Stored in lowercase.Example: hello |
| planId | string (UUID) | body | Yes | Which mailbox plan (storage size and price) to use. Get it from List Mailbox Plans.Example: c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34 |
| password | string, at least 12 chars | body | No | The password the owner will log in with. Leave it out and a strong one is generated and returned to you.Example: Spring-Garden-2026! |
| idempotencyKey | string (UUID) | body | No | A unique ID you create for this purchase. If the request fails and you retry with the same key, you are never charged twice and get the same mailbox back.Example: 5a7c9e1b-3d4f-4a6b-8c0d-2e4f6a8b0c1d |
Request Body
{
"domain": "example.com",
"localPart": "hello",
"planId": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34",
"idempotencyKey": "5a7c9e1b-3d4f-4a6b-8c0d-2e4f6a8b0c1d"
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/email/mailboxes" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"domain": "example.com",
"localPart": "hello",
"planId": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34",
"idempotencyKey": "5a7c9e1b-3d4f-4a6b-8c0d-2e4f6a8b0c1d"
}'Responses
{
"id": "9d1e7a3c-2b4f-4e6a-8c0d-5f7b9a1c3e2d",
"address": "[email protected]",
"password": "q3V9xk2LmT8pR4wZ",
"quotaGb": 5,
"mailServer": "mail.your-mail-host.example",
"webmailUrl": "https://webmail.your-mail-host.example",
"imapPort": 993,
"smtpPort": 587,
"smtpTls": true,
"periodEnd": "2026-11-02T09:20:00.000Z"
}{
"statusCode": 402,
"error": "Error",
"code": "INSUFFICIENT_FUNDS",
"message": "Wallet balance is too low. Add funds to continue.",
"balanceCents": 1200,
"requiredCents": 2399,
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/email/mailboxes"
}{
"statusCode": 409,
"error": "Error",
"code": "MAILBOX_INVALID",
"message": "This mailbox address is already in use.",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/email/mailboxes"
}Status Codes
Created
Bad Request - a field is wrong, or MAILBOX_INVALID: the address is invalid, the plan doesn't exist or isn't priced, or the domain isn't an ACTIVE domain on your account.
Unauthorized - missing or invalid API key
Payment Required - the wallet balance is too low (INSUFFICIENT_FUNDS, with balanceCents and requiredCents). Nothing is charged. Top up, or use the purchase checkout to pay the difference by card.
Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Conflict - the address is already in use (MAILBOX_INVALID), the same request is still running (DUPLICATE_REQUEST), or the key was used for a different mailbox (IDEMPOTENCY_KEY_REUSED).
Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly.
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a field is wrong, or MAILBOX_INVALID: the address is invalid, the plan doesn't exist or isn't priced, or the domain isn't an ACTIVE domain on your account. |
| 401 | Unauthorized - missing or invalid API key |
| 402 | Payment Required - the wallet balance is too low (INSUFFICIENT_FUNDS, with balanceCents and requiredCents). Nothing is charged. Top up, or use the purchase checkout to pay the difference by card. |
| 403 | Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 409 | Conflict - the address is already in use (MAILBOX_INVALID), the same request is still running (DUPLICATE_REQUEST), or the key was used for a different mailbox (IDEMPOTENCY_KEY_REUSED). |
| 503 | Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly. |
DELETE/v1/developer/email/mailboxes/:idDeletes the mailbox and all its mail right away. This can't be undone and the current month isn't refunded. You can create the same address again later.
1 required1 response
/v1/developer/email/mailboxes/:idDeletes the mailbox and all its mail right away. This can't be undone and the current month isn't refunded. You can create the same address again later.
Request Fields
id
Requiredstring (UUID) • path
The id of the mailbox, from List Mailboxes or Create Mailbox.
Example:9d1e7a3c-2b4f-4e6a-8c0d-5f7b9a1c3e2d| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The id of the mailbox, from List Mailboxes or Create Mailbox.Example: 9d1e7a3c-2b4f-4e6a-8c0d-5f7b9a1c3e2d |
Request Examples
curl -X DELETE "https://api.msgera.io/v1/developer/email/mailboxes/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{ "ok": true }Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - no mailbox with this id on your account, or it was already deleted (MAILBOX_INVALID).
Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly.
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - no mailbox with this id on your account, or it was already deleted (MAILBOX_INVALID). |
| 503 | Service Unavailable - the registrar or mail service couldn't complete the action (PROVIDER_UNAVAILABLE). Any wallet charge is refunded automatically. Try again shortly. |
POST/v1/developer/email/purchases/checkoutUse this when your wallet balance can't cover a new mailbox. You get a checkoutUrl: open it (or send it to whoever pays) to pay the missing amount by card. The payment goes into your wallet and the mailbox is then created automatically - follow it with Get Mailbox Purchase. The card amount is the shortfall, at least $10 (1000 cents). If the wallet already has enough, you get 409 WALLET_COVERS_PURCHASE: call Create Mailbox directly. The checkout link expires after 24 hours.
3 required2 responses
/v1/developer/email/purchases/checkoutUse this when your wallet balance can't cover a new mailbox. You get a checkoutUrl: open it (or send it to whoever pays) to pay the missing amount by card. The payment goes into your wallet and the mailbox is then created automatically - follow it with Get Mailbox Purchase. The card amount is the shortfall, at least $10 (1000 cents). If the wallet already has enough, you get 409 WALLET_COVERS_PURCHASE: call Create Mailbox directly. The checkout link expires after 24 hours.
Request Fields
kind
Requiredstring • body
What you are buying. Always MAILBOX_CREATE here.
Allowed:MAILBOX_CREATEExample: MAILBOX_CREATEpayload
Requiredobject • body
The same body you would send to Create Mailbox (domain, localPart, planId and optional password). It is checked by the same rules. Any idempotencyKey inside it is ignored.
Example:{ "domain": "example.com", "localPart": "hello", "planId": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34" }idempotencyKey
Requiredstring, 1-64 chars: letters, digits, - or _ • body
A unique ID you create for this purchase. Retrying with the same key returns the same checkout instead of opening a second one.
Example:mailbox-hello-example-com| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| kind | string | body | Yes | What you are buying. Always MAILBOX_CREATE here.Allowed: MAILBOX_CREATEExample: MAILBOX_CREATE |
| payload | object | body | Yes | The same body you would send to Create Mailbox (domain, localPart, planId and optional password). It is checked by the same rules. Any idempotencyKey inside it is ignored.Example: { "domain": "example.com", "localPart": "hello", "planId": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34" } |
| idempotencyKey | string, 1-64 chars: letters, digits, - or _ | body | Yes | A unique ID you create for this purchase. Retrying with the same key returns the same checkout instead of opening a second one.Example: mailbox-hello-example-com |
Request Body
{
"kind": "MAILBOX_CREATE",
"idempotencyKey": "mailbox-hello-example-com",
"payload": {
"domain": "example.com",
"localPart": "hello",
"planId": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34"
}
}Request Examples
curl -X POST "https://api.msgera.io/v1/developer/email/purchases/checkout" \
-H "X-API-Key: wak_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"kind": "MAILBOX_CREATE",
"idempotencyKey": "mailbox-hello-example-com",
"payload": {
"domain": "example.com",
"localPart": "hello",
"planId": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34"
}
}'Responses
{
"intentId": "3f6d2a9b-7c1e-4b5a-9d8f-0e2c4a6b8d10",
"status": "AWAITING_PAYMENT",
"checkoutUrl": "https://pay.example.com/checkout/cs_7Hq2Lm",
"topupCents": 1000,
"quotedTotalCents": 1049,
"expiresAt": "2026-10-03T09:15:00.000Z"
}{
"statusCode": 409,
"error": "Error",
"code": "WALLET_COVERS_PURCHASE",
"message": "Your wallet balance already covers this purchase.",
"balanceCents": 5000,
"requiredCents": 1049,
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/email/purchases/checkout"
}Status Codes
Created
Bad Request - a field or the payload is wrong, or MAILBOX_INVALID: the plan doesn't exist or isn't priced, or the domain isn't an ACTIVE domain on your account.
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Conflict - the wallet already covers the purchase (WALLET_COVERS_PURCHASE, with balanceCents and requiredCents), the address is in use (MAILBOX_INVALID), the key was used for a different purchase (IDEMPOTENCY_KEY_REUSED), or the same checkout is still being set up (DUPLICATE_REQUEST).
Service Unavailable - card payment couldn't be started (PAYMENT_SETUP_FAILED). Try again.
| Code | Description |
|---|---|
| 201 | Created |
| 400 | Bad Request - a field or the payload is wrong, or MAILBOX_INVALID: the plan doesn't exist or isn't priced, or the domain isn't an ACTIVE domain on your account. |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 409 | Conflict - the wallet already covers the purchase (WALLET_COVERS_PURCHASE, with balanceCents and requiredCents), the address is in use (MAILBOX_INVALID), the key was used for a different purchase (IDEMPOTENCY_KEY_REUSED), or the same checkout is still being set up (DUPLICATE_REQUEST). |
| 503 | Service Unavailable - card payment couldn't be started (PAYMENT_SETUP_FAILED). Try again. |
GET/v1/developer/email/purchases/:idShows how a card-paid mailbox purchase is going. AWAITING_PAYMENT: the card payment hasn't been made yet. PAID: the money reached your wallet and the purchase is running. COMPLETED: done, resultRef points to the result. FAILED: the purchase couldn't be made (see failureCode and failureMessage); the money stays in your wallet. EXPIRED: nobody paid within 24 hours. For mailboxes, resultRef is the new mailbox id; its password is stored and shown in the dashboard.
1 required1 response
/v1/developer/email/purchases/:idShows how a card-paid mailbox purchase is going. AWAITING_PAYMENT: the card payment hasn't been made yet. PAID: the money reached your wallet and the purchase is running. COMPLETED: done, resultRef points to the result. FAILED: the purchase couldn't be made (see failureCode and failureMessage); the money stays in your wallet. EXPIRED: nobody paid within 24 hours. For mailboxes, resultRef is the new mailbox id; its password is stored and shown in the dashboard.
Request Fields
id
Requiredstring (UUID) • path
The intentId you got from Pay for a Mailbox by Card.
Example:3f6d2a9b-7c1e-4b5a-9d8f-0e2c4a6b8d10| Field | Type | Location | Required | Description |
|---|---|---|---|---|
| id | string (UUID) | path | Yes | The intentId you got from Pay for a Mailbox by Card.Example: 3f6d2a9b-7c1e-4b5a-9d8f-0e2c4a6b8d10 |
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/email/purchases/{id}" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"id": "3f6d2a9b-7c1e-4b5a-9d8f-0e2c4a6b8d10",
"kind": "MAILBOX_CREATE",
"status": "COMPLETED",
"failureCode": null,
"failureMessage": null,
"resultRef": "9d1e7a3c-2b4f-4e6a-8c0d-5f7b9a1c3e2d",
"topupCents": 1000,
"quotedTotalCents": 199,
"expiresAt": "2026-10-03T09:15:00.000Z",
"summary": { "address": "[email protected]", "planId": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34" }
}Status Codes
Success
Bad Request - the id isn't a UUID.
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Not Found - no mailbox purchase with this id on your account (PURCHASE_NOT_FOUND).
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad Request - the id isn't a UUID. |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "email" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 404 | Not Found - no mailbox purchase with this id on your account (PURCHASE_NOT_FOUND). |
| 500 | Internal Server Error |
Reseller - Wallet
Your prepaid USD wallet pays for domains and mailboxes. Use this to read the balance. Top-ups are made in the dashboard (minimum $10). Needs an API key with the wallet permission, which only an active reseller can grant. Keys limited to certain devices or chats can't use it. See the Wallet API guide at /docs/wallet-api.
GET/v1/developer/walletShows how much money is in your wallet, in US cents (7450 = $74.50). Check it before buying to avoid 402 INSUFFICIENT_FUNDS.
0 required2 responses
/v1/developer/walletShows how much money is in your wallet, in US cents (7450 = $74.50). Check it before buying to avoid 402 INSUFFICIENT_FUNDS.
Request Examples
curl -X GET "https://api.msgera.io/v1/developer/wallet" \
-H "X-API-Key: wak_your_api_key_here"Responses
{
"balanceCents": 7450,
"currency": "USD"
}{
"statusCode": 403,
"error": "Forbidden",
"code": "API_KEY_PERMISSION_DENIED",
"service": "wallet",
"message": "This API key does not have the \"wallet\" permission",
"timestamp": "2026-10-02T09:15:00.000Z",
"path": "/v1/developer/wallet"
}Status Codes
Success
Unauthorized - missing or invalid API key
Forbidden - the API key doesn't have the "wallet" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here.
Internal Server Error
| Code | Description |
|---|---|
| 200 | Success |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - the API key doesn't have the "wallet" permission (API_KEY_PERMISSION_DENIED). Only an active reseller can grant it, and it stops working while the account isn't an active reseller. Keys limited to certain devices or chats are also refused here. |
| 500 | Internal Server Error |
Account Safety
Sending limits that protect linked (QR) numbers from being banned by WhatsApp. They apply to every send: API, dashboard, scheduled messages and campaigns.
Each linked number has its own sending budget. Direct sends, scheduled messages and campaigns all spend the same budget, so sending one by one instead of using a campaign doesn't get around it. Using several numbers gives you several budgets.
Meta numbers don't use these limits: their safety field is null and Meta applies its own messaging limits. Their rule is the 24-hour window: free-form messages only reach people who wrote to you in the last 24 hours, and everyone else needs an approved template.
Check the budget before you send
Call Get Device and read its safety object. canSend tells you if the number can send right now; when it's false, blockedReason says which limit was hit and resetsAt when it frees up. dailyRemaining is how many more messages fit today.
- Daily limit, by days since the number was first paired: up to 1 day 300, up to 7 days 300, up to 30 days 600, up to 90 days 1,000, after that 2,000 messages per UTC day.
- Hourly limit: 60 messages per UTC hour at every age.
- Per-minute limit: 2, 3, 4, 5 or 6 messages per minute, growing with the same age steps.
- All three must have room at the same time. Example: a number paired 2 days ago can send 3 messages this minute, 60 this hour and 300 today, whichever runs out first.
- Direct sends from one number are spaced 8-15 seconds apart.
{
"dailyLimit": 300,
"dailySent": 12,
"dailyRemaining": 288,
"hourlyLimit": 60,
"hourlySent": 4,
"hourlyRemaining": 56,
"minuteLimit": 3,
"minuteSent": 0,
"minuteRemaining": 3,
"accountAgeDays": 2,
"canSend": true,
"blockedReason": null,
"resetsAt": "2026-05-23T23:59:59.999Z",
"campaignDelayMinSec": 15,
"campaignDelayMaxSec": 50,
"variantRecipientsPerVariant": 8
}- dailyLimit, dailySent, dailyRemaining: today's budget (UTC day), what was used and what is left.
- hourlyLimit, hourlySent, hourlyRemaining: the same for the current UTC hour.
- minuteLimit, minuteSent, minuteRemaining: the same for the current minute.
- accountAgeDays: days since the number was first paired. Limits grow with it.
- canSend, blockedReason, resetsAt: whether a send would go out now, why not, and when the limit resets.
- campaignDelayMinSec, campaignDelayMaxSec: the random wait between campaign messages (15-50 seconds).
- variantRecipientsPerVariant: how many people one wording may reach in a text campaign (8).
Hitting a limit never makes a send fail. The request is accepted with HTTP 202 and status QUEUED; retryAt says when it will be tried and reason says why it waits. It goes out automatically, and you can find it (and cancel it) in List Scheduled Messages using queueId.
{
"accepted": true,
"queued": true,
"status": "QUEUED",
"queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
"messageId": null,
"providerMessageId": null,
"retryAt": "2026-05-24T00:00:05.000Z",
"reason": "Daily account safety limit reached (300 messages per day for accounts at this age). Try again after 2026-05-23T23:59:59.999Z."
}Campaigns never fail for being bigger than today's budget: they keep sending over the following days. estimatedCompletion on Create Campaign tells you when they should finish. Campaign messages are 15-50 seconds apart, and every 5th message waits about 2 minutes (give or take 25%) instead. Passing several deviceUids splits the audience so they send in parallel. Meta number campaigns send about one message per second.
New contacts per day
Messages to numbers that have never written to this number are limited to 30% of its daily limit (90 a day for a new number). Extra messages are not failed: they are queued and spread over the following days. Replies to people who already wrote to you are not limited. Campaigns with several numbers prefer a number the recipient already talks to.
Same wording to many people: on a linked number, one exact text (placeholders like {firstName} don't count as different) may reach at most 8 different numbers in 24 hours. Beyond that a direct send returns 422 DUPLICATE_BROADCAST. Vary the wording with spintax like {Hi|Hello}, or send a campaign, which writes the variations for you (one wording per 8 recipients, see Campaign Variations).
{
"statusCode": 422,
"error": "Duplicate Broadcast",
"code": "DUPLICATE_BROADCAST",
"message": "The same text was already sent to 8 different numbers from this device in the last 24h. Vary the wording (e.g. {Hi|Hello} spintax) or send it as a campaign so variations are generated for you.",
"timestamp": "2026-05-23T10:15:00.000Z",
"path": "/v1/developer/messages/send"
}Same message to the same person
Sending the same text to the same person 3 times in a row, less than 10 minutes apart, pauses the whole number for 30 minutes and emails you. Messages sent during the pause are queued and go out when it ends. Leave at least 10 minutes between identical reminders.
When WhatsApp restricts a number
WhatsApp sometimes stops a number from starting new chats for a while. During that time messages to new contacts are queued until the restriction lifts, while replies to people who already wrote to you keep working. Campaigns on that number pause and resume by themselves when it lifts. You get an email and an account.status webhook (RESTRICTED, then UNRESTRICTED).
Separate from plan limits
Your plan's monthly message allowance and these safety limits are counted separately. A number can reach its safety limit while your plan still has messages left, and the other way round.
MCP ServersPlanned
A Model Context Protocol (MCP) server would let AI assistants call Msgera with your API key. It isn't available yet.
There is no Msgera MCP server today. It is on our roadmap, but we haven't fixed a date or the exact set of actions it will offer.
Until then, any AI tool that can make HTTP requests can use the endpoints on this page directly: give it an API key that only has the permissions it needs, and limit the key to specific devices or chats if you want to keep it narrow.
SDKs & LibrariesPlanned
There are no official Msgera SDKs yet. The API is plain HTTPS and JSON, so any language's HTTP client works.
Official client libraries are on the roadmap, but none exist today and we haven't fixed which languages come first.
Until then
Every endpoint on this page has a Request Examples box with ready-to-copy code in cURL, JavaScript, Python and Java. Replace the API key and the example values with your own.
Common Error Responses
Errors return a stable JSON envelope. Plan enforcement uses PLAN_LIMIT_EXCEEDED and PLAN_FEATURE_UNAVAILABLE. Account safety returns HTTP 429 with error Account Send Limit when a device's daily, hourly, or per-minute budget is exhausted.
Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state
Unauthorized - missing or invalid API key
Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats.
Not Found - the resource doesn't exist or isn't yours
Conflict - a duplicate (same phone, name or idempotency key), or the resource is busy being processed
Unprocessable Entity - the number can't do this (CAPABILITY_UNAVAILABLE), a Meta number needs a template (TEMPLATE_REQUIRED), the template can't be sent as configured (TEMPLATE_SEND_INVALID), or the same wording already reached too many numbers (DUPLICATE_BROADCAST).
Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead.
Internal Server Error
| Code | Description |
|---|---|
| 400 | Bad Request - a field is missing, has the wrong format, or the action isn't allowed in the current state |
| 401 | Unauthorized - missing or invalid API key |
| 403 | Forbidden - your plan doesn't include this (PLAN_FEATURE_UNAVAILABLE) or its limit is used up (PLAN_LIMIT_EXCEEDED), or the API key lacks the permission for this service (API_KEY_PERMISSION_DENIED) or is limited to other devices or chats. |
| 404 | Not Found - the resource doesn't exist or isn't yours |
| 409 | Conflict - a duplicate (same phone, name or idempotency key), or the resource is busy being processed |
| 422 | Unprocessable Entity - the number can't do this (CAPABILITY_UNAVAILABLE), a Meta number needs a template (TEMPLATE_REQUIRED), the template can't be sent as configured (TEMPLATE_SEND_INVALID), or the same wording already reached too many numbers (DUPLICATE_BROADCAST). |
| 429 | Too Many Requests - the API key's per-minute request limit was exceeded (API_KEY_RATE_LIMITED). Wait a minute and retry. Account safety limits never return 429: those sends are queued instead. |
| 500 | Internal Server Error |
Rate Limits
Plan quotas plus per-device account safety budgets (daily warm-up, hourly/minute burst caps, campaign pacing). See Account Safety for details.
Plan limits
Depend on your plan; see the pricing page and your dashboard Billing page for current numbers. Defaults today: messages per month Starter 2,500, Growth 7,500, Pro 25,000; linked numbers 1, 2, 5; contacts 1,000, 5,000, 25,000; campaigns per month 2, 8, 25; Business is custom.
AI credits
One monthly pool per account (Starter 1,500, Growth 5,000, Pro 15,000 by default). Campaign variations, template drafts, spintax and AI agent replies all spend from it; reused variations are free.
API key requests
120 requests per minute per key by default (set per key); over it returns 429 API_KEY_RATE_LIMITED
Daily send budget (linked device)
By days since the number was first paired: up to 1 day 300, up to 7 days 300, up to 30 days 600, up to 90 days 1,000, after that 2,000 messages per UTC day
Hourly send budget (linked device)
60 messages per UTC hour
Per-minute send budget (linked device)
2, 3, 4, 5 or 6 messages per minute, growing with the same age steps
New contacts per day (linked device)
30% of the daily budget for numbers that never messaged the device; the rest are queued for the following days
Duplicate wording (linked device)
The same text to at most 8 different numbers per device per 24 hours
Campaign pacing (linked device)
15-50 seconds between sends; every 5th send waits about 2 minutes instead. Meta number campaigns send about one message per second.
Campaign wordings
At least one different wording per 8 recipients (TEXT campaigns); up to 12 options per variation slot
Failures before a campaign pauses itself
10 in a row
Webhook delivery
10 seconds per request; 8 attempts (5 s, doubling, max 1 hour apart); failed deliveries kept 90 days
Webhook filters
20 conditions per webhook, 100 values each