GlobVoice API
A simple REST API with one universal /send endpoint for WhatsApp, Email, SMS, Telegram and Push, plus contacts, templates and campaigns, from your own backend. JSON in, JSON out, authenticated with an API key.
Base URL: universal endpoints (/send, /status)
https://globvoice.com/api/v1Base URL: channel-specific endpoints (messages, contacts, templates, campaigns)
https://globvoice.com/api/v1/publicAll requests must be made over HTTPS. All responses are JSON. Successful responses return200/201; errors use the shape described under Errors.
Quickstart
- Open your brand dashboard → API Keys and create a key. Copy it: it is shown only once.
- Send the key in the
X-API-Keyheader on every request. - Make your first call with the universal
/sendendpoint: one call, any channel:
curl -X POST https://globvoice.com/api/v1/send \
-H "X-API-Key: $GLOBVOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"to": "919876543210",
"message": "Hello from GlobVoice!"
}'Try it: live API playground
Paste a key from your dashboard, fill in the fields, and run a real request. Your key stays in your browser and is sent only to GlobVoice. Note: this sends real messages.
Request
curl -X POST https://globvoice.com/api/v1/send \
-H "X-API-Key: $GLOBVOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"message": "Hello from the GlobVoice playground!"
}'Response
Check delivery status
Authentication
Authenticate by sending your secret key in the X-API-Key request header. Keys are scoped to a single brand and are stored only as a SHA-256 hash, so GlobVoice can never show you a key again after creation, so store it securely (e.g. in an environment variable). A key can be revoked at any time from the dashboard.
X-API-Key: gv_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxEvery key starts with gv_ followed by 43 URL-safe base64 characters (46 characters in total). The dashboard shows only the first 8 characters after creation.
Never expose your API key in client-side code. All calls should originate from your server.
Rate limits
Each API key is limited to 100 requests per minute. Exceeding the limit returns429 rate_limited. Back off and retry after the current minute window.
{
"error": "rate_limited",
"message": "100 req/min limit exceeded",
"statusCode": 429
}Scopes
When you create a key you choose its scopes, and they are fixed for the life of the key. A key with no scopes has no access: every authenticated endpoint requires the scope listed next to it, and calling one without that scope returns 403 insufficient_scope. The one public, unauthenticated endpoint is POST /verify-emails: it takes no API key and no scope.
| Scope | Grants |
|---|---|
messages:send | Send on any channel via POST /api/v1/send, and WhatsApp via /api/v1/public/messages/send |
messages:read | Read message delivery status (GET /api/v1/status/{messageId} and /api/v1/public/messages/{id}/status) |
contacts:read | List and search contacts |
contacts:write | Create and update contacts |
templates:read | List WhatsApp templates |
campaigns:send | Create and send campaigns |
attributions:read | Read click-to-WhatsApp ad attributions (GET /api/v1/public/ad-attributions, documented below) |
sms:sender:<HEADER> | Pin the key to one DLT sender header for SMS sends through POST /api/v1/send (see SMS sender pinning below). A pinned key may only send as that header; an unpinned key may use any active header on the brand |
SMS sender pinning
The sms:sender:<HEADER> scope guards the SMS branch of POST /api/v1/send. A brand can hold several DLT-registered sender headers, and the optional senderId field on an SMS send picks one. A key carrying sms:sender:PARLUN may only send as PARLUN: if senderId is omitted the pinned header is used, and if it names any other header the request fails with 403 sender_not_allowed. A key with no sms:sender: scope may name any active header on the brand, or omit senderId to use the brand's oldest active one. The pin is set when the key is created and, like every scope, cannot be changed afterwards.
Errors
Errors return a non-2xx status and a consistent JSON body:
{
"error": "insufficient_scope",
"message": "Missing scope: messages:send",
"statusCode": 403
}| Status | Error codes | Meaning |
|---|---|---|
| 400 | validation_error / missing_text / missing_template | The request body failed validation. |
| 401 | missing_api_key / invalid_api_key / revoked_api_key / expired_api_key | Authentication failed. Check your X-API-Key header. |
| 403 | insufficient_scope | The key is missing the scope required for this endpoint. |
| 404 | message_not_found | The requested resource does not exist for your brand. |
| 409 | no_sender | No active WhatsApp sender is configured for the brand. |
| 429 | rate_limited | You exceeded 100 requests per minute for this key. |
Endpoint reference
Universal Send: one endpoint, any channel
The simplest way to send. Pick a channel and GlobVoice routes the message through the right provider configured for your brand. Base URL: https://globvoice.com/api/v1.
/sendscope: messages:sendSend a message on whatsapp, email or sms. Provide channel + to + message; add the optional fields relevant to the channel. The brand must have that channel configured. Returns a channel-prefixed messageId you can pass to /status. The schema also accepts telegram and push, but those channels are coming soon and not yet available: a send on them returns 400 until the channel is connected for your brand.
| Parameter | Type | Description |
|---|---|---|
channelrequired | "whatsapp" | "email" | "sms" | "telegram" | "push" | Which channel to send on. Available today: whatsapp, email, sms. telegram: coming soon. push: coming soon. |
to | string | Phone number or email address. Telegram chat ID once telegram is available. Optional for push once available (broadcasts to the brand's subscribers; pass an endpoint to target one). |
message | string | Message body. Required for all channels except a WhatsApp template-only send. |
subject | string | Email only: subject line. |
templateName | string | WhatsApp only: approved template name. |
templateVars | string[] | WhatsApp only: ordered template variables. |
mediaUrl | string | WhatsApp (and Telegram, coming soon): image URL to send. |
title | string | Push only (coming soon): notification title. |
url | string | Push only (coming soon): click-through URL. |
senderId | string | SMS only: the DLT sender header to send as. Subject to the key's sms:sender:<HEADER> pin (see SMS sender pinning). |
Request
curl -X POST https://globvoice.com/api/v1/send \
-H "X-API-Key: $GLOBVOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"to": "919876543210",
"message": "Hello from GlobVoice!"
}'Response
{
"success": true,
"messageId": "wa_a1b2c3d4",
"channel": "whatsapp",
"status": "queued"
}/status/{messageId}scope: messages:readLook up the delivery status of any message returned by /send, across every channel. Pass the channel-prefixed messageId (e.g. wa_…, sms_…, email_…). Status is one of: queued, sending, sent, delivered, failed.
| Parameter | Type | Description |
|---|---|---|
messageIdrequired | string (path) | The messageId returned by POST /send. |
Request
curl https://globvoice.com/api/v1/status/sms_8f1c2e3a \
-H "X-API-Key: $GLOBVOICE_API_KEY"Response
{
"success": true,
"messageId": "sms_8f1c2e3a",
"channel": "sms",
"status": "delivered"
}Channel-specific endpoints
For finer control, call the dedicated endpoints below (relative to https://globvoice.com/api/v1/public).
/messages/sendscope: messages:sendSend a WhatsApp message from the brand's active sender. Supports plain text, an approved template, or a raw Meta Cloud API payload for advanced message types.
| Parameter | Type | Description |
|---|---|---|
torequired | string | Recipient phone number in international format, digits only (e.g. 919876543210). |
typerequired | "text" | "template" | "raw" | Message kind. |
text | string | Body text. Required when type=text (max 4096 chars). |
templateName | string | Approved template name. Required when type=template. |
templateParams | object | Map of template variable values, e.g. { "1": "Jane" }. |
rawPayload | object | Raw Meta payload (after messaging_product/to). Required when type=raw. |
idempotencyKey | string | Optional key to safely retry without double-sending. |
Request
curl -X POST https://globvoice.com/api/v1/public/messages/send \
-H "X-API-Key: $GLOBVOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "919876543210",
"type": "template",
"templateName": "order_update",
"templateParams": { "1": "Jane", "2": "#A1234" },
"idempotencyKey": "order-A1234"
}'Response
{
"ok": true,
"jobId": "a1b2c3d4-..."
}/messages/{id}/statusscope: messages:readFetch the delivery status and timestamps for a previously sent message.
| Parameter | Type | Description |
|---|---|---|
idrequired | uuid (path) | The message ID. |
Request
curl https://globvoice.com/api/v1/public/messages/MSG_ID/status \
-H "X-API-Key: $GLOBVOICE_API_KEY"Response
{
"message": {
"id": "a1b2c3d4-...",
"status": "delivered",
"recipient": "919876543210",
"sentAt": "2026-06-05T10:00:00.000Z",
"deliveredAt": "2026-06-05T10:00:02.000Z",
"readAt": null,
"failedAt": null,
"errorCode": null,
"errorDetail": null
}
}/contactsscope: contacts:readList contacts for the brand, newest first. Optionally search by phone or name.
| Parameter | Type | Description |
|---|---|---|
limit | number | Max results, 1–500 (default 100). |
search | string | Filter by phone substring or case-insensitive name. |
Request
curl "https://globvoice.com/api/v1/public/contacts?limit=50&search=jane" \
-H "X-API-Key: $GLOBVOICE_API_KEY"Response
{
"contacts": [
{
"id": "c1...",
"phone": "919876543210",
"name": "Jane Doe",
"optedOut": false,
"lastSeenAt": "2026-06-04T09:12:00.000Z",
"createdAt": "2026-05-01T08:00:00.000Z"
}
]
}/contactsscope: contacts:writeCreate a contact, or update it if the phone number already exists for the brand (upsert).
| Parameter | Type | Description |
|---|---|---|
phonerequired | string | Phone number in international format. |
name | string | Display name. |
attributes | object | Custom string attributes, e.g. { "plan": "pro" }. |
Request
curl -X POST https://globvoice.com/api/v1/public/contacts \
-H "X-API-Key: $GLOBVOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "919876543210",
"name": "Jane Doe",
"attributes": { "plan": "pro" }
}'Response
{
"contact": {
"id": "c1...",
"phone": "919876543210",
"name": "Jane Doe",
"attributes": { "plan": "pro" }
}
}/templatesscope: templates:readList the brand's WhatsApp message templates and their approval status.
Request
curl https://globvoice.com/api/v1/public/templates \
-H "X-API-Key: $GLOBVOICE_API_KEY"Response
{
"templates": [
{
"id": "t1...",
"name": "order_update",
"language": "en",
"category": "UTILITY",
"status": "APPROVED",
"syncedAt": "2026-06-01T00:00:00.000Z"
}
]
}/campaigns/sendscope: campaigns:sendCreate a campaign from an approved template and send it to an audience: specific contacts, a saved segment, or all contacts.
| Parameter | Type | Description |
|---|---|---|
namerequired | string | Campaign name. |
templateNamerequired | string | Approved template to send. |
templateParams | object[] | Per-recipient template variable maps. |
recipientFilterrequired | object | One of: { contactIds: [...] }, { segmentId }, or { all: true }. |
Request
curl -X POST https://globvoice.com/api/v1/public/campaigns/send \
-H "X-API-Key: $GLOBVOICE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "June promo",
"templateName": "promo_june",
"recipientFilter": { "all": true }
}'Response
{
"ok": true,
"campaignId": "cmp_...",
"queued": 1240,
"total": 1300,
"errors": []
}/verify-emailsPublic, unauthenticated: the one endpoint that takes no API key and no scope. Free email-list verification running syntax, disposable-domain and MX checks. IP rate-limited to 5 requests/hour; max 500 emails per call.
| Parameter | Type | Description |
|---|---|---|
emails | string[] | Array of email addresses (max 500). |
text | string | Alternatively, a raw blob separated by newlines, commas or semicolons. |
Request
curl -X POST https://globvoice.com/api/v1/public/verify-emails \
-H "Content-Type: application/json" \
-d '{ "emails": ["jane@example.com", "bad@@nope"] }'Response
{
"results": [
{ "email": "jane@example.com", "result": "valid", "score": 80 },
{ "email": "bad@@nope", "result": "invalid", "score": 0 }
],
"summary": { "total": 2, "valid": 1, "invalid": 1, "risky": 0 }
}/ad-attributionsscope: attributions:readList click-to-WhatsApp ad attributions (leads whose first message arrived from a Meta ad) for the key's brand, newest first, with the contact's name joined in where a contact exists. Read-only. The brand is taken from the key, never from the request. Pages are keyset-paginated: a full page returns nextCursor, the last page returns null.
| Parameter | Type | Description |
|---|---|---|
limit | number (query) | Rows per page, 1-500. Default 200. |
since | ISO date (query) | Only attributions first seen at or after this instant, for incremental pulls. |
adId | string (query) | Restrict to one Meta ad ID. |
cursor | string (query) | The nextCursor from the previous page. Any other value returns 400 invalid_cursor. |
Request
curl "https://globvoice.com/api/v1/public/ad-attributions?limit=100&since=2026-09-01T00:00:00Z" \
-H "X-API-Key: $GLOBVOICE_API_KEY"Response
{
"attributions": [
{
"id": "2f1c9a7e-…",
"contactPhone": "+919876543210",
"contactName": "Jane",
"ctwaClid": "ARAb…",
"adId": "120212345678901234",
"adHeadline": "Book a plumber today",
"adBody": null,
"sourceUrl": null,
"firstMessageAt": "2026-09-18T10:12:44.000Z",
"convertedAt": null,
"revenue": null,
"createdAt": "2026-09-18T10:12:44.000Z"
}
],
"nextCursor": "2026-09-18T10:12:44.000Z|2f1c9a7e-…"
}Ready to build?
Create a free account, then generate an API key from your brand dashboard.
