curl -X POST "https://api.bulkneo.com/v1/messages/buttons" -H "apikey: YOUR_API_KEY" -H "Content-Type: application/json" -d "{\"instance_id\":\"3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34\",\"to\":\"919000000000\",\"title\":\"Confirm your order\",\"description\":\"Order #2043 — ₹1,299\",\"buttons\":[{\"type\":\"reply\",\"text\":\"Confirm\",\"id\":\"confirm_2043\"},{\"type\":\"url\",\"text\":\"View order\",\"url\":\"https://example.com/orders/2043\"},{\"type\":\"call\",\"text\":\"Call us\",\"phone_number\":\"919000000001\"}]}"const options = {
method: 'POST',
headers: {apikey: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
instance_id: '3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34',
to: '919000000000',
title: 'Confirm your order',
description: 'Order #2043 — ₹1,299',
buttons: [
{type: 'reply', text: 'Confirm', id: 'confirm_2043'},
{type: 'url', text: 'View order', url: 'https://example.com/orders/2043'},
{type: 'call', text: 'Call us', phone_number: '919000000001'}
]
})
};
fetch('https://api.bulkneo.com/v1/messages/buttons', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.bulkneo.com/v1/messages/buttons"
payload = {
"instance_id": "3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34",
"to": "919000000000",
"title": "Confirm your order",
"description": "Order #2043 — ₹1,299",
"buttons": [
{
"type": "reply",
"text": "Confirm",
"id": "confirm_2043"
},
{
"type": "url",
"text": "View order",
"url": "https://example.com/orders/2043"
},
{
"type": "call",
"text": "Call us",
"phone_number": "919000000001"
}
]
}
headers = {
"apikey": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"success": true,
"data": {
"instance_id": "3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34",
"to": "919000000000",
"type": "buttons",
"status": "sent",
"message_id": "3EB0C1D2F4A5B6C7D8E9",
"provider_status": "PENDING",
"best_effort": true,
"note": "Interactive messages are best-effort: many WhatsApp clients no longer render them and may show a plain-text fallback instead. A successful response means the message was accepted for delivery, not that the buttons will appear. Do not depend on it for anything critical — send a text message with clear instructions as a fallback."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Field \"text\" is required and must be a non-empty string."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "invalid_api_key",
"message": "Invalid or revoked API key."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "subscription_expired",
"message": "Your subscription has expired. Renew it to continue sending messages."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "trial_quota_exceeded",
"message": "Trial message quota reached (500 messages). Upgrade to a paid plan to keep sending.",
"details": {
"trial_message_limit": 500
}
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "instance_not_found",
"message": "Instance not found. Check the instance_id and that it belongs to your account."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "instance_not_connected",
"message": "This WhatsApp instance is not connected. Connect it before sending messages."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "invalid_recipient",
"message": "The recipient number is not a valid WhatsApp number."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded, retry in 12s.",
"details": {
"retry_after_seconds": 12,
"limit_per_minute": 20,
"scope": "messages"
}
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "internal_error",
"message": "An unexpected error occurred."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "upstream_unavailable",
"message": "The messaging service is temporarily unavailable. Please retry shortly."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}Send buttons
POST /v1/messages/buttons — up to three tappable buttons.
curl -X POST "https://api.bulkneo.com/v1/messages/buttons" -H "apikey: YOUR_API_KEY" -H "Content-Type: application/json" -d "{\"instance_id\":\"3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34\",\"to\":\"919000000000\",\"title\":\"Confirm your order\",\"description\":\"Order #2043 — ₹1,299\",\"buttons\":[{\"type\":\"reply\",\"text\":\"Confirm\",\"id\":\"confirm_2043\"},{\"type\":\"url\",\"text\":\"View order\",\"url\":\"https://example.com/orders/2043\"},{\"type\":\"call\",\"text\":\"Call us\",\"phone_number\":\"919000000001\"}]}"const options = {
method: 'POST',
headers: {apikey: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
instance_id: '3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34',
to: '919000000000',
title: 'Confirm your order',
description: 'Order #2043 — ₹1,299',
buttons: [
{type: 'reply', text: 'Confirm', id: 'confirm_2043'},
{type: 'url', text: 'View order', url: 'https://example.com/orders/2043'},
{type: 'call', text: 'Call us', phone_number: '919000000001'}
]
})
};
fetch('https://api.bulkneo.com/v1/messages/buttons', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.bulkneo.com/v1/messages/buttons"
payload = {
"instance_id": "3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34",
"to": "919000000000",
"title": "Confirm your order",
"description": "Order #2043 — ₹1,299",
"buttons": [
{
"type": "reply",
"text": "Confirm",
"id": "confirm_2043"
},
{
"type": "url",
"text": "View order",
"url": "https://example.com/orders/2043"
},
{
"type": "call",
"text": "Call us",
"phone_number": "919000000001"
}
]
}
headers = {
"apikey": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"success": true,
"data": {
"instance_id": "3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34",
"to": "919000000000",
"type": "buttons",
"status": "sent",
"message_id": "3EB0C1D2F4A5B6C7D8E9",
"provider_status": "PENDING",
"best_effort": true,
"note": "Interactive messages are best-effort: many WhatsApp clients no longer render them and may show a plain-text fallback instead. A successful response means the message was accepted for delivery, not that the buttons will appear. Do not depend on it for anything critical — send a text message with clear instructions as a fallback."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "invalid_request",
"message": "Field \"text\" is required and must be a non-empty string."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "invalid_api_key",
"message": "Invalid or revoked API key."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "subscription_expired",
"message": "Your subscription has expired. Renew it to continue sending messages."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "trial_quota_exceeded",
"message": "Trial message quota reached (500 messages). Upgrade to a paid plan to keep sending.",
"details": {
"trial_message_limit": 500
}
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "instance_not_found",
"message": "Instance not found. Check the instance_id and that it belongs to your account."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "instance_not_connected",
"message": "This WhatsApp instance is not connected. Connect it before sending messages."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "invalid_recipient",
"message": "The recipient number is not a valid WhatsApp number."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded, retry in 12s.",
"details": {
"retry_after_seconds": 12,
"limit_per_minute": 20,
"scope": "messages"
}
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "internal_error",
"message": "An unexpected error occurred."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}{
"success": false,
"error": {
"code": "upstream_unavailable",
"message": "The messaging service is temporarily unavailable. Please retry shortly."
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}200 means the message was accepted for delivery — not that the buttons will appear. The response carries "best_effort": true to say so explicitly. Read Buttons and lists are best-effort before you build anything on this.https://api.bulkneo.com — a send delivers a real WhatsApp message to the number you type, and an instance call really changes your account. Use your own number while you are exploring, and paste a key only from a machine you trust.Your browser remembers what you type into the playground, so a key you paste here is still in the Authorization box on your next visit. Nobody else sees it — it never leaves your machine and it is not part of this site — but on a shared or borrowed computer, clear the site data when you are done.Notes
- 1 to 3 buttons. WhatsApp will not render more than three.
titleis required, up to 1,024 characters.description(up to 1,024) andfooter(up to 60) are optional.textis the button label, 1–24 characters, required on every button.
type needs one extra field:
type | Extra field | Rules |
|---|---|---|
reply | id | Required. 1–200 characters. Must be unique within the message. |
url | url | Required. A public http(s) link — private, internal and loopback addresses are refused, as are links containing a username or password. |
call | phone_number | Required. With country code. |
reply, url and call are accepted. Sending anything else returns 400 invalid_request listing the allowed values.
Example
curl -X POST https://api.bulkneo.com/v1/messages/buttons \
-H "apikey: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"instance_id": "YOUR_INSTANCE_ID",
"to": "919000000000",
"title": "Confirm your order",
"description": "Order #2043 — ₹1,299",
"footer": "Reply STOP to opt out",
"buttons": [
{ "type": "reply", "text": "Confirm", "id": "confirm_2043" },
{ "type": "url", "text": "View order", "url": "https://example.com/orders/2043" },
{ "type": "call", "text": "Call us", "phone_number": "919000000001" }
]
}'
curl -X POST "https://api.bulkneo.com/v1/messages/buttons" -H "apikey: YOUR_API_KEY" -H "Content-Type: application/json" -d "{\"instance_id\":\"YOUR_INSTANCE_ID\",\"to\":\"919000000000\",\"title\":\"Confirm your order\",\"description\":\"Order #2043 — ₹1,299\",\"footer\":\"Reply STOP to opt out\",\"buttons\":[{\"type\":\"reply\",\"text\":\"Confirm\",\"id\":\"confirm_2043\"},{\"type\":\"url\",\"text\":\"View order\",\"url\":\"https://example.com/orders/2043\"},{\"type\":\"call\",\"text\":\"Call us\",\"phone_number\":\"919000000001\"}]}"
The response
{
"success": true,
"data": {
"instance_id": "3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34",
"to": "919000000000",
"type": "buttons",
"status": "sent",
"message_id": "3EB0C1D2F4A5B6C7D8E9",
"provider_status": "PENDING",
"best_effort": true,
"note": "Interactive messages are best-effort: many WhatsApp clients no longer render them and may show a plain-text fallback instead. …"
},
"request_id": "b1f2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d"
}
Authorizations
Your API key, sent in an apikey request header. Never in the URL, never as a Bearer token.
Headers
OPTIONAL. A unique string you choose, 8-255 characters of letters, digits or . _ : ~ -
Send the same key with the same body again within 24 hours and the ORIGINAL result is returned without sending a second message — which is what makes a network retry safe. A different body on the same key is refused with 422 idempotency_key_reused rather than silently overwriting either result.
Responses carry Idempotency-Replayed: true|false while this is active. If that header is ABSENT, idempotency is not enabled on this deployment and retries will send again.
8 - 255^[A-Za-z0-9._:~-]{8,255}$Body
Which of your connected numbers to send FROM. Copy it from your numbers screen in the portal, or from GET /v1/instances. It must belong to your account.
"3f9c1a2b-7d4e-4c81-9f0a-2b6d5e8c1a34"
The recipient, with country code and no leading zero — for example 919000000000. Spaces, dashes, brackets and a leading + are accepted and stripped; 6 to 15 digits must remain.
"919000000000"
Heading of the message.
1 - 1024"Confirm your order"
1 to 3 buttons. WhatsApp will not render more than three.
1 - 3 elementsShow child attributes
Show child attributes
Body text under the title.
1024"Order #2043 — ₹1,299"
Small print under the buttons. Up to 60 characters.
60Send this message as a reply, quoting an earlier one. Use the data.message_id returned by a previous send.
128"3EB0C1D2F4A5B6C7D8E9"
Set true when the quoted message is one you sent from this number. Only valid together with quoted_message_id.

