sgeraDocs
API Reference

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.

Live
api.msgera.io

BASE URL

https://api.msgera.io/v1

AUTH HEADER

X-API-Key: wak_your_api_key_here
Request format

POST /developer/messages
Content-Type: application/json

Request contract

Shared rules across every endpoint.

15 rules
  • 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.

API docs workspace

96 endpoints across 16 feature groups

X-API-Keyhttps://api.msgera.io/v1

Overview

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.

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.

bash
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.

8endpoints
GET

List API Keys

/v1/api-keys

Lists all your keys, newest first, with their permissions and limits. The secret itself is never shown again after creation.

0 required1 response

Request Examples

curl -X GET "https://api.msgera.io/v1/api-keys" \
  -H "Authorization: Bearer <dashboard session token>"

Responses

200Success
{
  "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

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

Create API Key

/v1/api-keys

Creates 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

Request Fields

name

Optional

string, 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 bot

allowedAccountIds

Optional

string[], 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

Optional

string[], 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

Optional

integer 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: 60

permissions

Optional

string[], 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"]

Request Body

json
{
  "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

201Created
{
  "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"
}
400Unknown account id
{
  "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

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

List Grantable Services

/v1/api-keys/services

Lists 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

Request Examples

curl -X GET "https://api.msgera.io/v1/api-keys/services" \
  -H "Authorization: Bearer <dashboard session token>"

Responses

200Success
{
  "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

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

Update API Key

/v1/api-keys/:id

Changes 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

Request Fields

id

Required

string (UUID) • path

The key to act on. Use the id from List API Keys.

Example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f

name

Optional

string, 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 bot

allowedAccountIds

Optional

string[], 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

Optional

string[], 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

Optional

integer 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: 60

permissions

Optional

string[], 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"]

Request Body

json
{
  "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

200Success
{
  "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

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

Rotate API Key

/v1/api-keys/:id/rotate

Gives 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

Request Fields

id

Required

string (UUID) • path

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

201Rotated
{
  "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

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

API Key Activity

/v1/api-keys/audit

Shows 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

Request Fields

keyId

Optional

string (UUID) • query

Only show activity for one key. Use the id from List API Keys.

Example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f

page

Optional

integer, from 1 • query

Which page of results to return. Starts at 1.

Example: 1

limit

Optional

integer, 1-100 • query

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

200Success
{
  "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

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

API Key Activity for One Key

/v1/api-keys/:id/audit

Same 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

Request Fields

id

Required

string (UUID) • path

The key to act on. Use the id from List API Keys.

Example: 7c1d2e3f-4a5b-4c6d-8e7f-9a0b1c2d3e4f

page

Optional

integer, from 1 • query

Which page of results to return. Starts at 1.

Example: 1

limit

Optional

integer, 1-100 • query

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

200Success
{
  "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

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

Revoke API Key

/v1/api-keys/:id

Deletes 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

Request Fields

id

Required

string (UUID) • path

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

200Revoked
{ "deleted": true }

Status Codes

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.

2endpoints
GET

List Devices

/v1/developer/devices

Lists 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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/devices" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "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

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

Get Device

/v1/developer/devices/:deviceUid

Returns 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

Request Fields

deviceUid

Required

string • path

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

200Success
{
  "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
  }
}
404Unknown device
{
  "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

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.

6endpoints
POST

Send Message

/v1/developer/messages/send

Sends 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

Request Fields

deviceUid

Required

string • body

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

Required

string, 7-15 digits with optional leading + • body

The phone number that receives the message, with country code. Group IDs are not accepted here.

Example: +201001234567

text

Optional

string • 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

Optional

string (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-7f8a9b0c1d2e

variables

Optional

string[] • 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

Optional

string, 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

Request Body

json
{
  "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

202Sent now
{
  "accepted": true,
  "queued": false,
  "status": "SENT",
  "queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
  "messageId": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
  "providerMessageId": "3EB0C767D26A1D8E4F2B",
  "retryAt": null,
  "reason": null
}
202Queued for later
{
  "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."
}
202Template sent (Meta number)
{
  "accepted": true,
  "queued": false,
  "status": "DISPATCHED",
  "queueId": null,
  "messageId": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
  "providerMessageId": "wamid.HBgMMjAxMDAxMjM0NTY3FQIAERgS",
  "retryAt": null,
  "reason": null
}
409clientMessageId reused
{
  "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"
}
403Monthly limit reached
{
  "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"
}
422Template required (Meta number)
{
  "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"
}
422Template can't be sent
{
  "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"
}
422Template on a linked device
{
  "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"
}
422Duplicate broadcast
{
  "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

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

Send Media Message

/v1/developer/messages/send-media

Sends 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

Request Fields

deviceUid

Required

string • body

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

Required

string, 7-15 digits with optional leading + • body

The phone number that receives the message, with country code. Group IDs are not accepted here.

Example: +201001234567

mediaId

Required

string • body

The file to send. Use the id returned by Upload Media. Each upload can be sent once.

Example: 3f9a1c2e-7b4d-4e8f-9a10-5c6d7e8f9a0b

caption

Optional

string • body

Text shown under the photo, video or document.

Example: Your invoice for May

clientMessageId

Optional

string, 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

Request Body

json
{
  "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

202Sent now
{
  "accepted": true,
  "queued": false,
  "status": "SENT",
  "queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
  "messageId": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
  "providerMessageId": "3EB0C767D26A1D8E4F2B",
  "retryAt": null,
  "reason": null
}
202Queued for later
{
  "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."
}
202Refused by WhatsApp
{
  "accepted": false,
  "queued": false,
  "status": "FAILED",
  "queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
  "messageId": null,
  "retryAt": null,
  "reason": "WhatsApp did not accept the message"
}
403Media not on your plan
{
  "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"
}
404Media already used or unknown
{
  "statusCode": 404,
  "error": "Not Found",
  "message": "Media not found",
  "timestamp": "2026-05-23T10:15:00.000Z",
  "path": "/v1/developer/messages/send-media"
}
422Template required (Meta number)
{
  "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"
}
422Duplicate broadcast
{
  "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

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

Send Voice Message

/v1/developer/messages/send-voice

Sends 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

Request Fields

deviceUid

Required

string • body

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

Required

string, 7-15 digits with optional leading + • body

The phone number that receives the message, with country code. Group IDs are not accepted here.

Example: +201001234567

mediaId

Required

string • 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-5c6d7e8f9a0b

clientMessageId

Optional

string, 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

Request Body

json
{
  "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

202Sent now
{
  "accepted": true,
  "queued": false,
  "status": "SENT",
  "queueId": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
  "messageId": "b2c4d6e8-1a3b-4c5d-8e9f-0a1b2c3d4e5f",
  "providerMessageId": "3EB0C767D26A1D8E4F2B",
  "retryAt": null,
  "reason": null
}
202Queued for later
{
  "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."
}
400Not an audio file
{
  "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"
}
422Template required (Meta number)
{
  "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

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

Check WhatsApp Numbers

/v1/developer/messages/check-whatsapp

Tells 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

Request Fields

deviceUid

Required

string • body

Which of your numbers runs the check. It must be connected. Copy it from List Devices.

Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw

numbers

Required

string[], 1-50 items, each 7-15 digits with optional leading + • body

The phone numbers to check, with country code.

Example: ["+201001234567", "+15551234567"]

Request Body

json
{
  "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

201Checked
{
  "results": [
    { "phone": "+201001234567", "exists": true },
    { "phone": "+15551234567", "exists": false }
  ],
  "verified": true
}
400Device not connected
{
  "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

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

List Messages

/v1/developer/messages

Lists 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

Request Fields

deviceUid

Optional

string • query

Only show messages of this number. Copy it from List Devices. Required for keys limited to certain devices.

Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw

to

Optional

string • query

Only show messages with this phone number, sent to it or received from it. Part of a number also matches.

Example: +201001234567

page

Optional

integer, from 1 • query

Which page of results to return. Starts at 1.

Example: 1

limit

Optional

integer, 1-100 • query

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

200Success
{
  "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

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

Get Message

/v1/developer/messages/:id

Returns 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

Request Fields

id

Required

string (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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/messages/{id}" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "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

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.

1endpoint
GET

List Templates

/v1/developer/templates

Lists 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

Request Fields

deviceUid

Required

string • query

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

200Success
{
  "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."
        }
      ]
    }
  ]
}
400deviceUid missing
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "deviceUid is required",
  "timestamp": "2026-05-23T10:15:00.000Z",
  "path": "/v1/developer/templates"
}
422Linked device
{
  "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

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.

4endpoints
POST

Upload Media

/v1/developer/media/upload

Uploads 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

Request Fields

file

Required

file (multipart/form-data), up to 16 MB • body

The file to upload, sent as the form field named file.

Example: invoice-may.pdf

kind

Optional

string • 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

Request Examples

curl -X POST "https://api.msgera.io/v1/developer/media/upload" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

201Created
{
  "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"
}
413File too large
{
  "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

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

List Media

/v1/developer/media

Lists 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

Request Fields

page

Optional

integer, from 1 • query

Which page of results to return. Starts at 1.

Example: 1

limit

Optional

integer, 1-100 • query

How many files per page. Default 20.

Example: 20

kind

Optional

string • query

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

200Success
{
  "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

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

Get Media

/v1/developer/media/:id

Returns the details of one file. contentUrl shows the file and downloadUrl downloads it; both need the same authentication.

1 required1 response

Request Fields

id

Required

string (UUID) • path

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

200Success
{
  "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

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

Delete Media

/v1/developer/media/:id

Deletes 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

Request Fields

id

Required

string (UUID) • path

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

200Deleted
{ "deleted": true }
400Still in use
{
  "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

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).

14endpoints
POST

Create Campaign

/v1/developer/campaigns

Sends 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

Request Fields

deviceUid

Required

string • body

The number that sends the campaign. Copy its deviceUid from List Devices.

Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw

deviceUids

Optional

string[] • 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

Required

string • body

A name to recognise the campaign in lists and reports. Recipients never see it.

Example: Summer Sale

text

Optional

string (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

Optional

string • 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: TEXT

mediaAssetId

Optional

string • 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-7f8a1b2c3d4e

caption

Optional

string • 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

Optional

string[] (UUID) • body

Send to everyone in these contact groups. Get group IDs from List Contact Groups.

Example: ["3f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11"]

tagIds

Optional

string[] (UUID) • body

Send to every contact with these tags. Get tag IDs from List Contact Tags.

Example: ["a9e8d7c6-b5a4-4321-8f0e-d1c2b3a4f5e6"]

contactIds

Optional

string[] (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

Optional

string[] • 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

Optional

string (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:00Z

isRecurring

Optional

boolean • 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: true

recurrenceRule

Optional

string (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 * * 1

recurrenceEndAt

Optional

string (ISO 8601) • body

When a recurring campaign should stop repeating. Leave it out to repeat until you stop it.

Example: 2026-12-31T23:59:59Z

templateId

Optional

string (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-e4d3c2b1a090

variables

Optional

string[] • 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

Optional

string • 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: AUTO

autoApproveVariations

Optional

boolean • 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: false

expectedRecipients

Optional

integer (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: 5000

variationSegments

Optional

object[] (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

Optional

string[] • 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

Optional

string • 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: english

templateTone

Optional

string • body

Tone for generated variations. Leave it out to match the tone of your text.

Allowed: formal · friendly · casual · urgent · persuasive · gratefulExample: friendly

Request Body

json
{
  "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

201Started
{
  "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"
      }
    ]
  }
}
201Scheduled (other campaign fields as above)
{
  "id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
  "status": "QUEUED",
  "message": "Campaign scheduled for later",
  "scheduledAt": "2026-06-01T09:00:00.000Z"
}
201Waiting for variation review (other campaign fields as above)
{
  "id": "8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d",
  "status": "AWAITING_REVIEW",
  "variationStatus": "GENERATING",
  "message": "Message variations are being generated for your review",
  "totalRecipients": 150
}
201Recurring (other campaign fields as above)
{
  "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"
      }
    ]
  }
}
201Meta number, AUTO (other campaign fields as above)
{
  "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
  }
}
400Meta and linked numbers mixed
{
  "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"
}
400Reviewed variations don't cover the audience
{
  "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"
}
400Meta template recipients without opt-in
{
  "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"
}
422Template on a linked number
{
  "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

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

List Campaigns

/v1/developer/campaigns

Lists 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

Request Fields

page

Optional

integer • query

Which page of results to return, starting at 1. Defaults to 1.

Example: 1

limit

Optional

integer • query

How many results per page. Defaults to 20.

Example: 20

parentOnly

Optional

boolean • query

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

200Success
{
  "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

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

Get Campaign

/v1/developer/campaigns/:id

Returns 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

Request Fields

id

Required

string (UUID) • path

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

200Success
{
  "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

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

Get Campaign Progress

/v1/developer/campaigns/:id/progress

Quick 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

Request Fields

id

Required

string (UUID) • path

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

200Success
{
  "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

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

Get Campaign Analytics

/v1/developer/campaigns/:id/analytics

The 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

Request Fields

id

Required

string (UUID) • path

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

200Success
{
  "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
}
200Meta AUTO campaign
{
  "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

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

Get Campaign Recipients

/v1/developer/campaigns/:id/recipients

One 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

Request Fields

id

Required

string (UUID) • path

The campaign to work with. It's the id returned by Create Campaign or List Campaigns.

Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d

page

Optional

integer • query

Which page of results to return, starting at 1. Defaults to 1.

Example: 1

limit

Optional

integer • query

How many results per page. Defaults to 20.

Example: 20

status

Optional

string • 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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/campaigns/{id}/recipients" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "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

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

Pause Campaign

/v1/developer/campaigns/:id/pause

Stops 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

Request Fields

id

Required

string (UUID) • path

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

201Paused
{
  "status": "PAUSED"
}
400Not running
{
  "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

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

Resume Campaign

/v1/developer/campaigns/:id/resume

Continues a PAUSED campaign from where it stopped, including one that paused itself. Needs a free active-campaign slot on your plan.

1 required2 responses

Request Fields

id

Required

string (UUID) • path

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

201Resumed
{
  "status": "RUNNING",
  "message": "Campaign resumed"
}
400Not paused
{
  "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

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

Retry Failed Recipients

/v1/developer/campaigns/:id/retry-failed

Tries 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

Request Fields

id

Required

string (UUID) • path

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

201Retrying
{
  "status": "RUNNING",
  "message": "Retrying failed recipients"
}
400Wrong state
{
  "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

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

Pause Recurring Campaign

/v1/developer/campaigns/:id/pause-recurring

Puts 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

Request Fields

id

Required

string (UUID) • path

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

201On hold
{
  "status": "RECURRING_PAUSED"
}
400Not recurring
{
  "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

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

Resume Recurring Campaign

/v1/developer/campaigns/:id/resume-recurring

Restarts 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

Request Fields

id

Required

string (UUID) • path

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

201Repeating again
{
  "status": "RECURRING"
}
400Not on hold
{
  "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

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

Stop Recurring Campaign

/v1/developer/campaigns/:id/stop-recurring

Ends 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

Request Fields

id

Required

string (UUID) • path

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

201Stopped
{
  "status": "RECURRING_STOPPED"
}
400Not recurring
{
  "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

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

List Recurring Campaign Runs

/v1/developer/campaigns/:id/runs

Lists 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

Request Fields

id

Required

string (UUID) • path

The campaign to work with. It's the id returned by Create Campaign or List Campaigns.

Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d

page

Optional

integer • query

Which page of results to return, starting at 1. Defaults to 1.

Example: 1

limit

Optional

integer • query

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

200Success
{
  "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
}
400Not a recurring campaign
{
  "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

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

Delete Campaign

/v1/developer/campaigns/:id

Deletes 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

Request Fields

id

Required

string (UUID) • path

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

200Deleted
{
  "deleted": true
}
400Campaign still active
{
  "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

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.

5endpoints
POST

Draft Variations

/v1/developer/campaigns/variations/draft

Writes 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

Request Fields

message

Required

string (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

Required

integer (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: 32

language

Optional

string • body

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

Optional

string • body

Tone of the variations. Leave it out to match your message.

Allowed: formal · friendly · casual · urgent · persuasive · gratefulExample: friendly

fresh

Optional

boolean • body

Write new variations even if you approved some for this message before. Costs AI credits. Defaults to false.

Example: false

segments

Optional

object[] (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

Optional

string[] (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"]

Request Body

json
{
  "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

201Generated
{
  "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"]
}
201Reused (no cost)
{
  "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": []
}
201No variations needed
{
  "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

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

Get Variations

/v1/developer/campaigns/:id/variations

Shows 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

Request Fields

id

Required

string (UUID) • path

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

200Success
{
  "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

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

Edit Variations

/v1/developer/campaigns/:id/variations

Changes 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

Request Fields

id

Required

string (UUID) • path

The campaign waiting for review. It's the id returned by Create Campaign.

Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d

ops

Required

object[] (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" }]

Request Body

json
{
  "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

200Success
{
  "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
}
400Invalid operation
{
  "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

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

Regenerate Variations

/v1/developer/campaigns/:id/variations/regenerate

Asks 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

Request Fields

id

Required

string (UUID) • path

The campaign waiting for review. It's the id returned by Create Campaign.

Example: 8b0d6a52-3c1e-4f7a-9d2b-5e6f7a8b9c0d

slotIds

Optional

string[] (max 20) • body

The phrases to rewrite, using slot ids from Get Variations. Leave it out to rewrite all of them.

Example: ["c1d2e3f4a5"]

Request Body

json
{
  "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

201Regenerated
{
  "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
}
400Not in review
{
  "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

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

Approve Variations

/v1/developer/campaigns/:id/variations/approve

Accepts 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

Request Fields

id

Required

string (UUID) • path

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

201Started
{
  "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
  }
}
400Not enough variety
{
  "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

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.

5endpoints
GET

List Contacts

/v1/developer/contacts

Lists your contacts, newest first, a page at a time. Each contact includes the groups and tags it belongs to.

0 required1 response

Request Fields

search

Optional

string • query

Only show contacts whose name, phone or email contains this text. Matching is case-sensitive.

Example: Jane

groupId

Optional

string (UUID) • query

Only show members of this group. Get the id from List Contact Groups.

Example: 6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11

tagId

Optional

string (UUID) • query

Only show contacts with this tag. Get the id from List Contact Tags.

Example: a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b

page

Optional

integer, 1 or more • query

Which page to show. Starts at 1 (the default); an invalid value shows page 1.

Example: 1

limit

Optional

integer, 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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/contacts" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "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

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

Create Contact

/v1/developer/contacts

Adds 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

Request Fields

phone

Required

string, +? 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: +201001234567

name

Optional

string, at least 1 char • body

The person's name, shown in the dashboard and usable in messages. Send null to remove it.

Example: Jane Doe

email

Optional

string (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

Optional

boolean • 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: true

emailOptIn

Optional

boolean • 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

Request Body

json
{
  "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

201Created
{
  "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": []
}
409Duplicate phone
{
  "statusCode": 409,
  "error": "Conflict",
  "message": "Contact with phone +201001234567 already exists",
  "timestamp": "2026-05-23T10:15:00.000Z",
  "path": "/v1/developer/contacts"
}

Status Codes

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

Get Contact

/v1/developer/contacts/:id

Returns one contact with its groups and tags.

1 required1 response

Request Fields

id

Required

string (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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/contacts/{id}" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "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

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

Update Contact

/v1/developer/contacts/:id

Changes one contact. Send only the fields you want to change; the rest stay as they are. Returns the updated contact.

1 required1 response

Request Fields

id

Required

string (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

phone

Optional

string, +? 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: +201001234568

name

Optional

string, at least 1 char • body

The person's name, shown in the dashboard and usable in messages. Send null to remove it.

Example: Jane Doe

email

Optional

string (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

Optional

boolean • 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: true

emailOptIn

Optional

boolean • 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

Request Body

json
{
  "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

200Success
{
  "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

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

Delete Contact

/v1/developer/contacts/:id

Deletes the contact for good and removes it from its groups and tags. Messages already sent to the number are kept.

1 required1 response

Request Fields

id

Required

string (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

Request Examples

curl -X DELETE "https://api.msgera.io/v1/developer/contacts/{id}" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Deleted
{ "deleted": true }

Status Codes

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

Contact Groups & Tags

Sort your contacts into groups (for example customer segments) and colored tags (for example stages). A contact can be in many groups and have many tags. Use an API key with the contacts permission; keys limited to certain devices or chats can't use these routes (403). Groups and tags start empty: add people with the add-contacts or import endpoints. Then filter List Contacts by groupId or tagId, or send them to a campaign as contactGroupIds and tagIds.

15endpoints
GET

List Contact Groups

/v1/contact-groups

Lists all your groups, newest first, with how many contacts each one has. Not paginated: total is the number of groups.

0 required1 response

Request Examples

curl -X GET "https://api.msgera.io/v1/contact-groups" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "data": [
    {
      "id": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
      "name": "VIP Clients",
      "description": "High-value customers",
      "contactCount": 42,
      "createdAt": "2026-05-23T10:15:00.000Z",
      "updatedAt": "2026-05-23T10:15:00.000Z"
    }
  ],
  "total": 1
}

Status Codes

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

Get Contact Group

/v1/contact-groups/:id

Returns one group and every contact in it (id, name and phone).

1 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the group. You get it when you create the group or from List Contact Groups.

Example: 6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11

Request Examples

curl -X GET "https://api.msgera.io/v1/contact-groups/{id}" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "id": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
  "name": "VIP Clients",
  "description": "High-value customers",
  "contactCount": 2,
  "contacts": [
    { "id": "1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "name": "Jane Doe", "phone": "+201001234567" },
    { "id": "4e8a2c6f-0b3d-4f7a-9c1e-5d7b9f3a1c20", "name": null, "phone": "+14155550123" }
  ],
  "createdAt": "2026-05-23T10:15:00.000Z",
  "updatedAt": "2026-05-23T10:15:00.000Z"
}

Status Codes

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

Create Contact Group

/v1/contact-groups

Creates an empty group. Add people afterwards with Add Contacts to Group or Import Contacts to Group.

1 required2 responses

Request Fields

name

Required

string, at least 1 char • body

A name for the group, for example a customer segment. Must be different from your other groups.

Example: VIP Clients

description

Optional

string • body

A note about who belongs in this group. Only you see it.

Example: High-value customers

Request Body

json
{ "name": "VIP Clients", "description": "High-value customers" }

Request Examples

curl -X POST "https://api.msgera.io/v1/contact-groups" \
  -H "X-API-Key: wak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "VIP Clients", "description": "High-value customers" }'

Responses

201Created
{
  "id": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
  "name": "VIP Clients",
  "description": "High-value customers",
  "contactCount": 0,
  "createdAt": "2026-05-23T10:15:00.000Z",
  "updatedAt": "2026-05-23T10:15:00.000Z"
}
403Group limit reached
{
  "statusCode": 403,
  "error": "Error",
  "code": "PLAN_LIMIT_EXCEEDED",
  "message": "contactGroups limit exceeded for the Growth plan",
  "planKey": "growth",
  "limitKey": "contactGroups",
  "limit": 20,
  "current": 20,
  "requested": 1,
  "timestamp": "2026-05-23T10:15:00.000Z",
  "path": "/v1/contact-groups"
}

Status Codes

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 group limit is reached (PLAN_LIMIT_EXCEEDED), or the API key lacks the contacts permission or is limited to certain devices or chats.

409

Conflict - you already have a group with this name.

500

Internal Server Error

PATCH

Update Contact Group

/v1/contact-groups/:id

Renames a group or changes its description. Send only what you want to change. Members stay the same.

1 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the group. You get it when you create the group or from List Contact Groups.

Example: 6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11

name

Optional

string, at least 1 char • body

A new name. Must be different from your other groups.

Example: Top Clients

description

Optional

string • body

A new note about the group. Send null to remove it.

Example: Spent over $1,000 this year

Request Body

json
{ "name": "Top Clients", "description": "Spent over $1,000 this year" }

Request Examples

curl -X PATCH "https://api.msgera.io/v1/contact-groups/{id}" \
  -H "X-API-Key: wak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Top Clients", "description": "Spent over $1,000 this year" }'

Responses

200Success
{
  "id": "6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11",
  "name": "Top Clients",
  "description": "Spent over $1,000 this year",
  "contactCount": 42,
  "createdAt": "2026-05-23T10:15:00.000Z",
  "updatedAt": "2026-05-23T10:15:00.000Z"
}

Status Codes

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 - you already have a group with this name.

500

Internal Server Error

DELETE

Delete Contact Group

/v1/contact-groups/:id

Deletes the group. The contacts in it are not deleted; they just stop being members.

1 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the group. You get it when you create the group or from List Contact Groups.

Example: 6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11

Request Examples

curl -X DELETE "https://api.msgera.io/v1/contact-groups/{id}" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Deleted
{ "deleted": true }

Status Codes

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

Add Contacts to Group

/v1/contact-groups/:id/contacts

Puts existing contacts into the group. Ids that aren't your contacts are skipped; added counts the ones that were. If none of the ids are your contacts (or the list is empty) you get 404 No valid contacts found.

2 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the group. You get it when you create the group or from List Contact Groups.

Example: 6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11

contactIds

Required

array of strings • body

The ids of the contacts to add, from List Contacts or Create Contact.

Example: ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "4e8a2c6f-0b3d-4f7a-9c1e-5d7b9f3a1c20"]

Request Body

json
{ "contactIds": ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "4e8a2c6f-0b3d-4f7a-9c1e-5d7b9f3a1c20"] }

Request Examples

curl -X POST "https://api.msgera.io/v1/contact-groups/{id}/contacts" \
  -H "X-API-Key: wak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed", "4e8a2c6f-0b3d-4f7a-9c1e-5d7b9f3a1c20"] }'

Responses

201Added
{ "added": 2 }

Status Codes

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 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 group isn't yours, or none of the ids are your contacts.

500

Internal Server Error

DELETE

Remove Contacts from Group

/v1/contact-groups/:id/contacts

Takes contacts out of the group. The contacts themselves are not deleted. removed is the number of ids you sent, even if some weren't members.

2 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the group. You get it when you create the group or from List Contact Groups.

Example: 6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11

contactIds

Required

array of strings • body

The ids of the contacts to take out of the group.

Example: ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"]

Request Body

json
{ "contactIds": ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"] }

Request Examples

curl -X DELETE "https://api.msgera.io/v1/contact-groups/{id}/contacts" \
  -H "X-API-Key: wak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"] }'

Responses

200Removed
{ "removed": 1 }

Status Codes

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

Import Contacts to Group

/v1/contact-groups/:id/import

Uploads a CSV or Excel (.xlsx) file of people and puts all of them in this group. Numbers that are already contacts are reused; new numbers become new contacts. This is a multipart/form-data upload with one field named file, not JSON - for example: curl -F "[email protected]" with your X-API-Key header. Needs the contact import feature on your plan.

2 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the group. You get it when you create the group or from List Contact Groups.

Example: 6f1c2b9e-1d2a-4c5e-9f00-2b8f1e7a9c11

defaultCountry

Optional

string, 2-letter country code • query

The country to assume for numbers in the file written without a country code (for example 01001234567). Numbers that already start with + are kept as they are.

Example: EG

file

Required

file (.csv or .xlsx) • body

The spreadsheet to import, sent as a multipart/form-data field named file (up to 16 MB). It needs a phone column (CSV: phone or number; Excel: phone, phone number, number or mobile) and may have a name column. The first row is the header.

Example: contacts.csv

Request Examples

curl -X POST "https://api.msgera.io/v1/contact-groups/{id}/import" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

201Imported
{
  "linked": 5,
  "created": 12,
  "errors": [{ "row": 4, "message": "Invalid phone number: 12345" }]
}

Status Codes

201

Created - the file was processed. linked counts people who were already contacts and are now in the group, created counts new contacts, errors lists the rows that were skipped (row 0 means no file was sent).

400

Bad Request - the file isn't .csv or .xlsx, has no rows, or an Excel file has no phone column.

401

Unauthorized - missing or invalid API key

403

Forbidden - your plan doesn't include contact import (PLAN_FEATURE_UNAVAILABLE), or the contact limit was reached during the import (PLAN_LIMIT_EXCEEDED; rows before that point are kept). Also returned when the API key lacks the contacts permission or is limited to certain devices or chats.

404

Not Found - the resource doesn't exist or isn't yours

413

Payload Too Large - the uploaded file is over 16 MB

500

Internal Server Error

GET

List Contact Tags

/v1/contact-tags

Lists all your tags, newest first, with their color and how many contacts have each one. Not paginated: total is the number of tags.

0 required1 response

Request Examples

curl -X GET "https://api.msgera.io/v1/contact-tags" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "data": [
    {
      "id": "a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b",
      "name": "Leads",
      "color": "#22c55e",
      "contactCount": 18,
      "createdAt": "2026-05-23T10:15:00.000Z",
      "updatedAt": "2026-05-23T10:15:00.000Z"
    }
  ],
  "total": 1
}

Status Codes

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

Create Contact Tag

/v1/contact-tags

Creates a colored label you can put on contacts. Tag people afterwards with Add Contacts to Tag or Import Contacts to Tag.

1 required2 responses

Request Fields

name

Required

string, at least 1 char • body

The label text, for example a stage or interest. Must be different from your other tags.

Example: Leads

color

Optional

string, #RRGGBB • body

The color the tag is shown in, as a hex code. Default #6366f1 (indigo).

Example: #22c55e

Request Body

json
{ "name": "Leads", "color": "#22c55e" }

Request Examples

curl -X POST "https://api.msgera.io/v1/contact-tags" \
  -H "X-API-Key: wak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Leads", "color": "#22c55e" }'

Responses

201Created
{
  "id": "a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b",
  "name": "Leads",
  "color": "#22c55e",
  "contactCount": 0,
  "createdAt": "2026-05-23T10:15:00.000Z",
  "updatedAt": "2026-05-23T10:15:00.000Z"
}
403Tag limit reached
{
  "statusCode": 403,
  "error": "Error",
  "code": "PLAN_LIMIT_EXCEEDED",
  "message": "contactTags limit exceeded for the Growth plan",
  "planKey": "growth",
  "limitKey": "contactTags",
  "limit": 30,
  "current": 30,
  "requested": 1,
  "timestamp": "2026-05-23T10:15:00.000Z",
  "path": "/v1/contact-tags"
}

Status Codes

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 tag limit is reached (PLAN_LIMIT_EXCEEDED), or the API key lacks the contacts permission or is limited to certain devices or chats.

409

Conflict - you already have a tag with this name.

500

Internal Server Error

PATCH

Update Contact Tag

/v1/contact-tags/:id

Renames a tag or changes its color. Send only what you want to change. Tagged contacts keep the tag.

1 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the tag. You get it when you create the tag or from List Contact Tags.

Example: a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b

name

Optional

string, at least 1 char • body

A new label text. Must be different from your other tags.

Example: Hot Leads

color

Optional

string, #RRGGBB • body

A new color as a hex code.

Example: #22c55e

Request Body

json
{ "name": "Hot Leads", "color": "#ef4444" }

Request Examples

curl -X PATCH "https://api.msgera.io/v1/contact-tags/{id}" \
  -H "X-API-Key: wak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Hot Leads", "color": "#ef4444" }'

Responses

200Success
{
  "id": "a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b",
  "name": "Hot Leads",
  "color": "#ef4444",
  "contactCount": 18,
  "createdAt": "2026-05-23T10:15:00.000Z",
  "updatedAt": "2026-05-23T10:15:00.000Z"
}

Status Codes

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 - you already have a tag with this name.

500

Internal Server Error

DELETE

Delete Contact Tag

/v1/contact-tags/:id

Deletes the tag and removes it from every contact. The contacts themselves are not deleted.

1 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the tag. You get it when you create the tag or from List Contact Tags.

Example: a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b

Request Examples

curl -X DELETE "https://api.msgera.io/v1/contact-tags/{id}" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Deleted
{ "deleted": true }

Status Codes

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

Add Contacts to Tag

/v1/contact-tags/:id/contacts

Puts the tag on existing contacts. Ids that aren't your contacts are skipped; added counts the ones that were tagged. If none are your contacts you get 404 No valid contacts found.

2 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the tag. You get it when you create the tag or from List Contact Tags.

Example: a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b

contactIds

Required

array of UUIDs, at least 1 • body

The ids of the contacts to tag, from List Contacts or Create Contact.

Example: ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"]

Request Body

json
{ "contactIds": ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"] }

Request Examples

curl -X POST "https://api.msgera.io/v1/contact-tags/{id}/contacts" \
  -H "X-API-Key: wak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"] }'

Responses

201Added
{ "added": 1 }

Status Codes

201

Created

400

Bad Request - contactIds is empty or contains something that isn't a contact id (UUID).

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 tag isn't yours, or none of the ids are your contacts.

500

Internal Server Error

DELETE

Remove Contacts from Tag

/v1/contact-tags/:id/contacts

Takes the tag off contacts. The contacts themselves are not deleted. removed is the number of ids you sent, even if some didn't have the tag.

2 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the tag. You get it when you create the tag or from List Contact Tags.

Example: a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b

contactIds

Required

array of UUIDs, at least 1 • body

The ids of the contacts to untag.

Example: ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"]

Request Body

json
{ "contactIds": ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"] }

Request Examples

curl -X DELETE "https://api.msgera.io/v1/contact-tags/{id}/contacts" \
  -H "X-API-Key: wak_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "contactIds": ["1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed"] }'

Responses

200Removed
{ "removed": 1 }

Status Codes

200

Success

400

Bad Request - contactIds is empty or contains something that isn't a contact id (UUID).

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

Import Contacts to Tag

/v1/contact-tags/:id/import

Uploads a CSV or Excel (.xlsx) file of people and puts all of them in this tag. Numbers that are already contacts are reused; new numbers become new contacts. This is a multipart/form-data upload with one field named file, not JSON - for example: curl -F "[email protected]" with your X-API-Key header. Needs the contact import feature on your plan.

2 required1 response

Request Fields

id

Required

string (UUID) • path

The id of the tag. You get it when you create the tag or from List Contact Tags.

Example: a3d5f7b9-2c4e-4f6a-8b0d-1e3f5a7c9e2b

defaultCountry

Optional

string, 2-letter country code • query

The country to assume for numbers in the file written without a country code (for example 01001234567). Numbers that already start with + are kept as they are.

Example: EG

file

Required

file (.csv or .xlsx) • body

The spreadsheet to import, sent as a multipart/form-data field named file (up to 16 MB). It needs a phone column (CSV: phone or number; Excel: phone, phone number, number or mobile) and may have a name column. The first row is the header.

Example: contacts.csv

Request Examples

curl -X POST "https://api.msgera.io/v1/contact-tags/{id}/import" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

201Imported
{
  "linked": 5,
  "created": 12,
  "errors": [{ "row": 4, "message": "Invalid phone number: 12345" }]
}

Status Codes

201

Created - the file was processed. linked counts people who were already contacts and are now in the tag, created counts new contacts, errors lists the rows that were skipped (row 0 means no file was sent).

400

Bad Request - the file isn't .csv or .xlsx, has no rows, or an Excel file has no phone column.

401

Unauthorized - missing or invalid API key

403

Forbidden - your plan doesn't include contact import (PLAN_FEATURE_UNAVAILABLE), or the contact limit was reached during the import (PLAN_LIMIT_EXCEEDED; rows before that point are kept). Also returned when the API key lacks the contacts permission or is limited to certain devices or chats.

404

Not Found - the resource doesn't exist or isn't yours

413

Payload Too Large - the uploaded file is over 16 MB

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).

5endpoints
POST

Create Scheduled Message

/v1/developer/scheduled-messages

Schedules 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

Request Fields

deviceUid

Required

string • body

The number to send from. Copy its deviceUid from List Devices.

Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw

to

Required

string (7-15 digits, optional +) • body

Who receives the message: a phone number with country code.

Example: +201001234567

text

Required

string • 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

Required

string (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:00Z

messageType

Optional

string • body

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

Optional

string • 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-7f8a1b2c3d4e

caption

Optional

string • body

Text shown under the photo, video or document of a MEDIA message.

Example: Your invoice for May

Request Body

json
{
  "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

201Scheduled
{
  "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"
}
400Time in the past
{
  "statusCode": 400,
  "error": "Bad Request",
  "message": "scheduledAt must be in the future",
  "timestamp": "2026-05-22T10:00:00.000Z",
  "path": "/v1/developer/scheduled-messages"
}
403Too many 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

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

List Scheduled Messages

/v1/developer/scheduled-messages

Lists 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

Request Fields

page

Optional

integer • query

Which page of results to return, starting at 1. Defaults to 1.

Example: 1

limit

Optional

integer • query

How many results per page. Defaults to 20, at most 100.

Example: 20

status

Optional

string • 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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/scheduled-messages" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "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

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

Get Scheduled Message

/v1/developer/scheduled-messages/:id

Returns 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

Request Fields

id

Required

string (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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/scheduled-messages/{id}" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "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

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

Update Scheduled Message

/v1/developer/scheduled-messages/:id

Changes 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

Request Fields

id

Required

string (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

text

Optional

string • body

New message text.

Example: Reminder: your appointment moved to 11:00.

caption

Optional

string • body

New caption for a MEDIA message.

Example: Updated invoice for May

mediaAssetId

Optional

string • body

A different file to send. Upload it with Upload Media first and use the new id.

Example: 9a8b7c6d-5e4f-4a3b-9c2d-1e0f9a8b7c6d

scheduledAt

Optional

string (ISO 8601) • body

A new send time. Must be in the future.

Example: 2026-06-01T09:00:00Z

timezone

Optional

string (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

Request Body

json
{
  "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

200Updated
{
  "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"
}
400Queued send
{
  "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"
}
409Already sending
{
  "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

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

Cancel Scheduled Message

/v1/developer/scheduled-messages/:id

Cancels 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

Request Fields

id

Required

string (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

Request Examples

curl -X DELETE "https://api.msgera.io/v1/developer/scheduled-messages/{id}" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Cancelled
{
  "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"
}
400Already sent
{
  "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

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.

4endpoints
GET

List Auto-Reply Rules

/v1/developer/auto-reply

Lists 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

Request Fields

deviceUid

Required

string • query

The number whose rules you want to see. Copy its deviceUid from List Devices.

Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw

page

Optional

integer • query

Which page of results to return, starting at 1. Defaults to 1.

Example: 1

limit

Optional

integer • query

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

200Success
{
  "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

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

Create Auto-Reply Rule

/v1/developer/auto-reply

Retired: 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

Request Fields

deviceUid

Required

string • body

The number the rule was meant for, from List Devices.

Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw

triggerType

Required

string • body

How the keyword was matched. Kept only for old integrations.

Allowed: CONTAINS · EXACT · STARTS_WITH · ENDS_WITHExample: CONTAINS

triggerValue

Required

string • body

The keyword. Kept only for old integrations.

Example: price

replyText

Required

string • body

The reply. Kept only for old integrations.

Example: Our prices start at $15.99/mo.

Request Body

json
{
  "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

410Retired
{
  "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

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

Update Auto-Reply Rule

/v1/developer/auto-reply/:id

Retired: always answers 410 and changes nothing. Connect an AI agent to the number instead.

1 required1 response

Request Fields

id

Required

string (UUID) • path

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

410Retired
{
  "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

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

Delete Auto-Reply Rule

/v1/developer/auto-reply/:id

Permanently removes an old rule. Use it to clean up rules that no longer do anything.

1 required1 response

Request Fields

id

Required

string (UUID) • path

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

200Deleted
{
  "ok": true
}

Status Codes

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.

4endpoints
GET

List Webhooks

/v1/developer/webhooks

Lists 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

Request Fields

deviceUid

Optional

string • query

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

200Success
{
  "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

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

Create or Update Webhook

/v1/developer/webhooks

Sets 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

Request Fields

deviceUid

Required

string • body

Which of your numbers this webhook is for. Copy it from List Devices.

Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw

personalWebhookUrl

Optional

string (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/whatsapp

groupWebhookUrl

Optional

string (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-groups

isActive

Optional

boolean • body

Turn deliveries off (false) or back on (true) without losing the URLs. New webhooks start on.

Example: true

Request Body

json
{
  "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

200Saved
{
  "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

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

Delete Webhook

/v1/developer/webhooks/:id

Removes 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

Request Fields

id

Required

string (UUID) • path

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

200Deleted
{ "deleted": true }

Status Codes

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

Webhook Delivery History

/v1/developer/webhooks/history

Lists 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

Request Fields

deviceUid

Optional

string • query

Only show results for this number. Copy it from List Devices. Required for keys limited to certain devices.

Example: wa_dev_Q2xpZW50RGV2aWNlSWQwMDAwMDAwMDAw

status

Optional

string • query

Only show successful or failed attempts. Use FAILED to find problems.

Allowed: SUCCESS · FAILEDExample: FAILED

page

Optional

integer, from 1 • query

Which page of results to return. Starts at 1.

Example: 1

limit

Optional

integer, 1-100 • query

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

200Success
{
  "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

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.

2endpoints
POST

Preview Spintax Variations

/v1/developer/spintax/preview

Returns 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

Request Fields

template

Required

string, 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

Optional

integer, 1-50 • body

How many different wordings you want back. Default 5.

Example: 5

Request Body

json
{
  "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

201Success
{
  "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

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

Spin Single Variation

/v1/developer/spintax/spin

Returns 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

Request Fields

template

Required

string, 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.

Request Body

json
{
  "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

201Success
{
  "template": "{Hi|Hello|Hey} {there|friend}! Our {sale|offer} ends today.",
  "result": "Hey friend! Our sale ends today."
}

Status Codes

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.

12endpoints
GET

List Domain Prices

/v1/developer/domains/tlds

Shows 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

Request Fields

q

Optional

string, up to 63 chars • query

Only show endings that contain this text. Leave it out to see everything.

Example: co

page

Optional

integer, 1 or more • query

Which page of results to show. Starts at 1 (the default).

Example: 1

pageSize

Optional

integer, 1-200 • query

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

200Success
{
  "total": 412,
  "page": 1,
  "pageSize": 50,
  "currency": "USD",
  "channel": "reseller",
  "items": [
    { "tld": "com", "registerPriceCents": 1049, "renewPriceCents": 1149 },
    { "tld": "io", "registerPriceCents": 3899, "renewPriceCents": 4299 }
  ]
}
403Key without the domains permission
{
  "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

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

Check Domain Availability

/v1/developer/domains/check

Tells 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

Request Fields

domain

Required

string, 1-253 chars • body

The full domain you want to check, including its ending.

Example: example.com

Request Body

json
{ "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

201Quoted
{
  "domain": "example.com",
  "available": true,
  "purchasable": true,
  "priceCents": 1049,
  "renewPriceCents": 1149,
  "currency": "USD"
}
201Already taken
{
  "domain": "google.com",
  "available": false,
  "purchasable": false,
  "priceCents": null,
  "renewPriceCents": null,
  "currency": "USD"
}
400Invalid name
{
  "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

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

Register Domain

/v1/developer/domains/register

Buys 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

Request Fields

domain

Required

string • body

The domain to buy. Check it first with Check Domain Availability.

Example: example.com

years

Required

integer, 1-10 • body

How many years to register it for. You pay for all of them now.

Example: 1

contact

Required

object • body

Who owns the domain. Registries require these details; the fields are listed below.

contact.firstName

Required

string, 1-80 chars • body

First name of the domain owner (the registrant).

Example: Jane

contact.lastName

Required

string, 1-80 chars • body

Last name of the domain owner.

Example: Doe

contact.organization

Optional

string, 1-120 chars • body

Company name, if the domain belongs to a business. Leave it out for a person.

Example: Acme Inc.

contact.address1

Required

string, 1-120 chars • body

Street address of the owner.

Example: 1 Market St

contact.address2

Optional

string, 1-120 chars • body

Second address line, such as a suite or floor.

Example: Suite 400

contact.city

Required

string, 1-80 chars • body

City.

Example: San Francisco

contact.state

Optional

string, 1-80 chars • body

State, province or region, where the country uses one.

Example: CA

contact.postalCode

Required

string, 1-20 chars • body

Postal or ZIP code.

Example: 94105

contact.country

Required

string, 2 uppercase letters • body

Country of the owner as a two-letter ISO code.

Example: US

contact.email

Required

string (email) • body

Email of the owner. Domain registries may send ownership notices here, so use a real inbox.

Example: [email protected]

contact.phone

Required

string, + then 7-15 digits • body

Phone of the owner in international format, starting with + and the country code.

Example: +14155550123

privacy

Optional

boolean • body

Hide the owner's details from public domain lookups (WHOIS). On by default.

Example: true

idempotencyKey

Optional

string (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

Request Body

json
{
  "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

201Registered
{
  "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"
}
201Still processing
{
  "id": "4b0f6c2e-8a1d-4f3b-9c7e-2d5a6b8c9e01",
  "name": "example.com",
  "status": "PENDING",
  "autoRenew": true,
  "nameserverMode": "MSGERA",
  "expiresAt": null,
  "createdAt": "2026-10-02T09:14:41.000Z"
}
400Not available
{
  "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"
}
402Insufficient funds
{
  "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

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

Renew Domain

/v1/developer/domains/:name/renew

Adds 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

Request Fields

name

Required

string • path

The domain you want to work with, exactly as it appears in List Domains.

Example: example.com

years

Optional

integer, 1-10 • body

How many years to add. Default 1.

Example: 1

idempotencyKey

Optional

string, 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

Request Body

json
{ "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

201Renewed
{
  "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"
}
402Insufficient funds
{
  "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"
}
409Renewal already running
{
  "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

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

List Domains

/v1/developer/domains

Lists 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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/domains" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
[
  {
    "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

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

Get Domain

/v1/developer/domains/:name

Returns 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

Request Fields

name

Required

string • path

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

200Success
{
  "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

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

List DNS Records

/v1/developer/domains/:name/dns

Lists 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

Request Fields

name

Required

string • path

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

200Success
{
  "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"
    }
  ]
}
200Own nameservers
{
  "domain": "example.com",
  "nameserverMode": "CUSTOM",
  "items": []
}

Status Codes

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

Save DNS Records

/v1/developer/domains/:name/dns

Adds 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

Request Fields

name

Required

string • path

The domain you want to work with, exactly as it appears in List Domains.

Example: example.com

items

Required

array of objects, 1-500 • body

The records to save. Each one uses the fields below.

items[].type

Required

string • body

Kind of record.

Allowed: A · AAAA · CNAME · MX · TXT · SRV · CAA · NSExample: A

items[].name

Required

string, 1-253 chars • body

The host the record is for: @ for the domain itself, or a subdomain part such as www.

Example: @

items[].value

Optional

string, 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 ~all

items[].address

Optional

string, 1-2048 chars • body

The IP address, for A and AAAA records. Send value or address.

Example: 203.0.113.10

items[].ttl

Optional

integer, 60-86400 • body

How many seconds others may cache the record. Default 3600 (one hour).

Example: 3600

items[].preference

Optional

integer • body

MX only: priority of this mail server; lower numbers are tried first. Default 0.

Example: 10

items[].exchange

Optional

string • body

MX only: the mail server host. Used instead of value when sent.

Example: mx1.mail.example.net

items[].cname

Optional

string • body

CNAME only: the host this name is an alias of. Used instead of value when sent.

Example: example.com

items[].nameserver

Optional

string • body

NS only: the nameserver host. Used instead of value when sent.

Example: ns1.example.net

items[].service

Optional

string • body

SRV only: the service name.

Example: _sip

items[].protocol

Optional

string • body

SRV only: the protocol.

Example: _tcp

items[].port

Optional

integer • body

SRV only: the port. Default 0.

Example: 5060

items[].weight

Optional

integer • body

SRV only: share of traffic among servers with the same priority. Default 0.

Example: 5

items[].target

Optional

string • body

SRV only: the host that runs the service. Used instead of value when sent.

Example: sip.example.com

items[].priority

Optional

integer, 0-65535 • body

SRV priority. Accepted, but SRV records are currently saved with priority 0.

Example: 10

items[].group

Optional

string • 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

Request Body

json
{
  "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

200Saved
{
  "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"
    }
  ]
}
400Own nameservers in use
{
  "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

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

Get Nameservers

/v1/developer/domains/:name/nameservers

Shows 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

Request Fields

name

Required

string • path

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

200Own nameservers
{
  "domain": "example.com",
  "mode": "CUSTOM",
  "hosts": ["ns1.example.net", "ns2.example.net"]
}
200Msgera nameservers
{ "domain": "example.com", "mode": "MSGERA", "hosts": [] }

Status Codes

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

Update Nameservers

/v1/developer/domains/:name/nameservers

Switches 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

Request Fields

name

Required

string • path

The domain you want to work with, exactly as it appears in List Domains.

Example: example.com

mode

Required

string • body

MSGERA to let Msgera host DNS, CUSTOM to use nameservers from another DNS provider.

Allowed: MSGERA · CUSTOMExample: CUSTOM

hosts

Optional

array 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"]

Request Body

json
{
  "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

200Updated
{
  "domain": "example.com",
  "mode": "CUSTOM",
  "hosts": ["ns1.example.net", "ns2.example.net"]
}
400Nameserver not found
{
  "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

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

Pay for a Domain by Card

/v1/developer/domains/purchases/checkout

Use 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

Request Fields

kind

Required

string • body

What you are buying: a new domain or more years for one you own.

Allowed: DOMAIN_REGISTER · DOMAIN_RENEWExample: DOMAIN_REGISTER

payload

Required

object • 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

Required

string, 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

Request Body

json
{
  "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

201Checkout opened
{
  "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"
}
409Wallet already covers it
{
  "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

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

Get Domain Purchase

/v1/developer/domains/purchases/:id

Shows 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

Request Fields

id

Required

string (UUID) • path

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

200Completed
{
  "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

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.

8endpoints
GET

List Mailbox Plans

/v1/catalog/email/plans/me

Lists 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

Request Examples

curl -X GET "https://api.msgera.io/v1/catalog/email/plans/me" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
[
  {
    "id": "c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34",
    "name": "Business 5 GB",
    "quotaGb": 5,
    "priceCents": 199,
    "renewPriceCents": 199,
    "currency": "USD"
  }
]

Status Codes

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

Get Mailbox Prices for You

/v1/email/mailboxes/quote

Shows 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

Request Examples

curl -X GET "https://api.msgera.io/v1/email/mailboxes/quote" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "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

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

Check a Mailbox Address

/v1/email/mailboxes/check

Tells 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

Request Fields

domain

Required

string • query

The domain the mailbox goes on. It must be a domain you registered with Msgera and that is active.

Example: mybrand.com

localPart

Required

string (1-64 chars) • query

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

200Available
{ "status": "available", "address": "[email protected]" }
200Taken
{
  "status": "taken",
  "address": "[email protected]",
  "message": "This address is already in use."
}

Status Codes

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

List Mailboxes

/v1/developer/email/mailboxes

Lists 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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/email/mailboxes" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
[
  {
    "id": "9d1e7a3c-2b4f-4e6a-8c0d-5f7b9a1c3e2d",
    "address": "[email protected]",
    "status": "ACTIVE",
    "quotaGb": 5,
    "periodEnd": "2026-11-02T09:20:00.000Z",
    "autoRenew": true,
    "hasStoredCredentials": true
  }
]
403Key without the email permission
{
  "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

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

Create Mailbox

/v1/developer/email/mailboxes

Creates 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

Request Fields

domain

Required

string • body

An ACTIVE domain on your account (see List Domains).

Example: example.com

localPart

Required

string, 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: hello

planId

Required

string (UUID) • body

Which mailbox plan (storage size and price) to use. Get it from List Mailbox Plans.

Example: c8a4e2f0-6b1d-4f3a-9e7c-2d5b8a0f1e34

password

Optional

string, 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

Optional

string (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

Request Body

json
{
  "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

201Created
{
  "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"
}
402Insufficient funds
{
  "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"
}
409Address in use
{
  "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

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

Delete Mailbox

/v1/developer/email/mailboxes/:id

Deletes 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

Request Fields

id

Required

string (UUID) • path

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

200Deleted
{ "ok": true }

Status Codes

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

Pay for a Mailbox by Card

/v1/developer/email/purchases/checkout

Use 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

Request Fields

kind

Required

string • body

What you are buying. Always MAILBOX_CREATE here.

Allowed: MAILBOX_CREATEExample: MAILBOX_CREATE

payload

Required

object • 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

Required

string, 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

Request Body

json
{
  "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

201Checkout opened
{
  "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"
}
409Wallet already covers it
{
  "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

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

Get Mailbox Purchase

/v1/developer/email/purchases/:id

Shows 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

Request Fields

id

Required

string (UUID) • path

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

200Completed
{
  "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

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.

1endpoint
GET

Get Wallet Balance

/v1/developer/wallet

Shows 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

Request Examples

curl -X GET "https://api.msgera.io/v1/developer/wallet" \
  -H "X-API-Key: wak_your_api_key_here"

Responses

200Success
{
  "balanceCents": 7450,
  "currency": "USD"
}
403Key without the wallet permission
{
  "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

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.
json
{
  "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.

json
{
  "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).

json
{
  "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.

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