Introduction
The Seen API lets you send WhatsApp messages, manage contacts and templates, read your inbox conversations, and receive real-time events — all through your own WhatsApp Business Account, via the official Meta Cloud API underneath.
Every request is scoped to a single tenant (your workspace) and authenticated with an API key. All endpoints below are relative to your API base URL and return JSON.
Authentication
Authenticate by sending your API key in the x-api-key header on every request. Create and manage keys from Dashboard → API Keys.
curl "https://wa.vsartech.com/api/v1/contacts" \
-H "x-api-key: YOUR_API_KEY"Requests without a valid key return 401 Unauthorized. Keys are hashed at rest and never shown again after creation, so store yours securely.
Paste a key here to run real requests from the "Try it" tab on every endpoint below. It's kept only in this browser tab's session storage — never sent anywhere except directly to the API — and is cleared when you close the tab.
Base URL & versioning
All endpoints are versioned under a single path prefix:
https://wa.vsartech.com/api/v1The current version is v1. Breaking changes will be introduced under a new version prefix rather than changing existing behavior in place.
Errors
The API uses conventional HTTP status codes. 2xx indicates success, 4xx indicates a problem with the request (bad input, missing auth, not found), and 5xx indicates an error on our side.
{
"success": false,
"error": "Contact not found"
}400Bad Request — missing or invalid parameters401Unauthorized — missing or invalid API key403Forbidden — key does not have permission for this action404Not Found — the resource does not exist for your tenant429Too Many Requests — rate limit exceeded500Internal Server ErrorRate limits
Requests are limited to 1,000 requests per 15 minutes per IP address. Current usage is reported on every response via standard rate-limit headers:
RateLimit-Limit: 1000
RateLimit-Remaining: 998
RateLimit-Reset: 842Exceeding the limit returns 429 Too Many Requests. If you need a higher limit, contact us.
SDKs
Official client libraries for Node.js, Python, Dart/Flutter, and PHP wrap the REST API above. All four require the same API key used for direct REST calls — there is no separate SDK-only credential, and the client cannot be constructed without one.
npm install seenwaimport { SeenClient } from 'seenwa';
const client = new SeenClient({ apiKey: 'YOUR_API_KEY' });
await client.messages.sendText({
to: '+15551234567',
phone_number_id: '1143523638846055',
message: 'Hi! Your order has shipped.',
});pip install seenwafrom seenwa import SeenClient
client = SeenClient(api_key='YOUR_API_KEY')
client.messages.send_text(
to='+15551234567',
phone_number_id='1143523638846055',
message='Hi! Your order has shipped.',
)dart pub add seenwaimport 'package:seenwa/seenwa.dart';
final client = SeenClient(apiKey: 'YOUR_API_KEY');
await client.messages.sendText(
to: '+15551234567',
phoneNumberId: '1143523638846055',
message: 'Hi! Your order has shipped.',
);The Flutter SDK should be called from a backend you control, not bundled directly into a distributed app binary — a compiled app can be decompiled and any embedded API key extracted.
composer require vsartech/seenwause Seen\SeenClient;
$client = new SeenClient('YOUR_API_KEY');
$client->messages->sendText(
to: '+15551234567',
phoneNumberId: '1143523638846055',
message: 'Hi! Your order has shipped.'
);Every resource shown below (Messages, Conversations, Contacts, Templates, Media, Webhooks) has an equivalent example in all four languages — switch tabs on any endpoint's code panel to see it.
MCP Server
Seen runs a hosted Model Context Protocol server at mcp.seen.vsartech.com, so an MCP-compatible AI client (Claude Desktop, Claude Code, or any other MCP client) can read and act on your WhatsApp data directly — sending messages, looking up contacts and conversations, checking campaign status, and more, all in natural language.
Authentication uses the same API key as the REST API and SDKs above, sent as an x-api-key header on the MCP connection. A key's scopes, rate limit, and IP allowlist apply to MCP tool calls exactly as they do to direct API requests.
https://mcp.seen.vsartech.com/mcp{
"mcpServers": {
"seen": {
"url": "https://mcp.seen.vsartech.com/mcp",
"headers": {
"x-api-key": "YOUR_API_KEY"
}
}
}
}Available tools include list_conversations, get_conversation_messages, list_contacts, create_contact, list_phone_numbers, send_message, send_template_message, list_messages, list_templates, list_campaigns, get_campaign, and list_segments.
Messages
Send WhatsApp messages and read message history.
/api/v1/messages/textSend a text message
Sends a free-form text message to a WhatsApp number. Requires an active 24-hour customer service window, or use the template endpoint instead.
Requires scope messages:send
Body parameters
tostringrequiredRecipient phone number in E.164 format.
phone_number_idstringrequiredYour WhatsApp phone number ID (see the WhatsApp page in the dashboard).
messagestringrequiredMessage text, up to 4096 characters.
reply_tostringQuote an earlier message, like Reply in WhatsApp: its wamid, or the Seen message id a send returned. An id that can't be found sends without the quote.
Recipient phone number in E.164 format.
Your WhatsApp phone number ID (see the WhatsApp page in the dashboard).
Message text, up to 4096 characters.
Quote an earlier message, like Reply in WhatsApp: its wamid, or the Seen message id a send returned. An id that can't be found sends without the quote.
{
"success": true,
"data": { "id": "b6b6c1b0-...", "status": "queued" }
}/api/v1/messages/templateSend a template message
Sends a pre-approved WhatsApp template message. Use this to message a customer outside the 24-hour window. For a carousel template, components must include a "carousel" entry with one card per card in the template (card_index from 0), each with a header parameter ({ "type": "image", "image": { "link": "https://..." } }), its body parameters, and a button parameter for each quick-reply or URL button. A send with a different number of cards is rejected with the reason.
Requires scope messages:send
Body parameters
tostringrequiredRecipient phone number in E.164 format.
phone_number_idstringrequiredYour WhatsApp phone number ID.
template_namestringrequiredName of an approved template.
language_codestringTemplate language code. Defaults to "en".
componentsarrayTemplate variable substitutions, in Meta's components format.
Recipient phone number in E.164 format.
Your WhatsApp phone number ID.
Name of an approved template.
Template language code. Defaults to "en".
Template variable substitutions, in Meta's components format.
{
"success": true,
"data": { "id": "b6b6c1b0-...", "status": "queued" }
}/api/v1/messages/mediaSend a media message
Sends an image, video, audio, document, or sticker message. Provide either a previously uploaded media_id (see the Media resource) or a publicly reachable link for Seen to fetch.
Requires scope messages:send
Body parameters
tostringrequiredRecipient phone number in E.164 format.
phone_number_idstringrequiredYour WhatsApp phone number ID.
typestringrequiredOne of image, video, audio, document, sticker.
media_idstringID of a media file previously uploaded via POST /media/upload. Either this or link is required.
linkstringPublicly reachable URL for Seen to fetch and send. Either this or media_id is required.
captionstringCaption text. Not supported for audio or sticker.
filenamestringDisplayed file name, for document messages.
reply_tostringQuote an earlier message, like Reply in WhatsApp: its wamid, or the Seen message id a send returned. An id that can't be found sends without the quote.
Recipient phone number in E.164 format.
Your WhatsApp phone number ID.
One of image, video, audio, document, sticker.
ID of a media file previously uploaded via POST /media/upload. Either this or link is required.
Publicly reachable URL for Seen to fetch and send. Either this or media_id is required.
Caption text. Not supported for audio or sticker.
Displayed file name, for document messages.
Quote an earlier message, like Reply in WhatsApp: its wamid, or the Seen message id a send returned. An id that can't be found sends without the quote.
{
"success": true,
"data": { "id": "b6b6c1b0-...", "status": "sent" }
}/api/v1/messages/interactiveSend an interactive message
Sends reply buttons (up to 3), a list menu (up to 10 rows), or a link button. WhatsApp only accepts these within 24 hours of the customer's last message, and not into groups; outside that window the request fails with the reason WhatsApp gives. When the customer taps a button or picks a row, the reply arrives as a normal incoming message (message.received) whose text is the button or row title.
Requires scope messages:send
Body parameters
tostringrequiredRecipient phone number in E.164 format.
phone_number_idstringrequiredYour WhatsApp phone number ID.
typestringrequiredOne of button, list, cta_url.
bodystringrequiredThe message text (up to 1024 characters).
headerstringShort title above the text (up to 60 characters).
header_mediaobjectA file above the text, for button and cta_url messages (a list takes a text header only): { type: image | video | document, media_id or link, filename (documents) }. Use instead of header.
footerstringSmall text below (up to 60 characters).
buttonsarraytype button: 1 to 3 reply buttons, each { id (up to 256), title (up to 20) }. Ids must differ.
button_labelstringtype list: the text (up to 20 characters) on the button that opens the menu.
sectionsarraytype list: sections of rows, 10 rows in all. Each row is { id, title (up to 24), description (up to 72, optional) }. With more than one section, each needs a title.
cta_labelstringtype cta_url: the text (up to 20 characters) on the link button.
cta_urlstringtype cta_url: the link the button opens.
reply_tostringQuote an earlier message, like Reply in WhatsApp: its wamid, or the Seen message id a send returned.
Recipient phone number in E.164 format.
Your WhatsApp phone number ID.
One of button, list, cta_url.
The message text (up to 1024 characters).
Short title above the text (up to 60 characters).
A file above the text, for button and cta_url messages (a list takes a text header only): { type: image | video | document, media_id or link, filename (documents) }. Use instead of header.
Small text below (up to 60 characters).
type button: 1 to 3 reply buttons, each { id (up to 256), title (up to 20) }. Ids must differ.
type list: the text (up to 20 characters) on the button that opens the menu.
type list: sections of rows, 10 rows in all. Each row is { id, title (up to 24), description (up to 72, optional) }. With more than one section, each needs a title.
type cta_url: the text (up to 20 characters) on the link button.
type cta_url: the link the button opens.
Quote an earlier message, like Reply in WhatsApp: its wamid, or the Seen message id a send returned.
{
"success": true,
"data": { "id": "b6b6c1b0-...", "status": "sent", "conversation_id": "9c1f...", "wamid": "wamid.HBg..." }
}/api/v1/messagesList messages
Returns a paginated list of messages, optionally filtered by contact, conversation, status, or type.
Requires scope messages:read
Query parameters
contactstringFilter by contact ID.
conversationstringFilter by conversation ID.
statusstringFilter by status (queued, sent, delivered, read, failed).
typestringFilter by message type (text, template, image, ...).
pagenumberPage number, defaults to 1.
limitnumberResults per page, defaults to 20, at most 100.
cursorstringFor long lists, page with a cursor instead of page numbers: send an empty cursor for the first page, then the nextCursor from the previous response until hasMore is false. Faster, returns no total, and never skips or repeats rows while new ones arrive.
Filter by contact ID.
Filter by conversation ID.
Filter by status (queued, sent, delivered, read, failed).
Filter by message type (text, template, image, ...).
Page number, defaults to 1.
Results per page, defaults to 20, at most 100.
For long lists, page with a cursor instead of page numbers: send an empty cursor for the first page, then the nextCursor from the previous response until hasMore is false. Faster, returns no total, and never skips or repeats rows while new ones arrive.
{
"success": true,
"data": [{ "id": "...", "direction": "inbound", "content": "hi", "status": "received", "createdAt": "..." }],
"pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}/api/v1/messages/:idGet a message
Returns a single message by ID.
Requires scope messages:read
Path parameters
idstringrequiredMessage ID.
Message ID.
{
"success": true,
"data": { "id": "...", "direction": "outbound", "content": "hi", "status": "sent" }
}Conversations
Read and reply to inbox conversations.
/api/v1/conversationsList conversations
Returns a paginated list of conversations, each with the linked contact and phone number.
Requires scope messages:read
Query parameters
statusstringFilter by status (open, pending, resolved).
assigned_tostringFilter by assigned user ID, or "unassigned".
pagenumberPage number, defaults to 1.
limitnumberResults per page, defaults to 20, at most 100.
cursorstringFor long lists, page with a cursor instead of page numbers: send an empty cursor for the first page, then the nextCursor from the previous response until hasMore is false. Faster, returns no total, and never skips or repeats rows while new ones arrive.
Filter by status (open, pending, resolved).
Filter by assigned user ID, or "unassigned".
Page number, defaults to 1.
Results per page, defaults to 20, at most 100.
For long lists, page with a cursor instead of page numbers: send an empty cursor for the first page, then the nextCursor from the previous response until hasMore is false. Faster, returns no total, and never skips or repeats rows while new ones arrive.
{
"success": true,
"data": [{ "id": "...", "status": "open", "lastMessagePreview": "hi", "contacts": { "name": "Kundan" } }],
"pagination": { "limit": 20, "hasMore": true, "nextCursor": "WyIyMDI2LTA4LTMx..." }
}/api/v1/conversations/:idGet a conversation
Returns a single conversation with its contact, phone number, and notes.
Requires scope messages:read
Path parameters
idstringrequiredConversation ID.
Conversation ID.
{
"success": true,
"data": { "id": "...", "status": "open", "contacts": { "name": "Kundan" } }
}/api/v1/conversations/:id/sendReply in a conversation
Sends an outbound text message within an existing conversation, to the conversation's contact.
Requires scope messages:send
Path parameters
idstringrequiredConversation ID.
Body parameters
messagestringrequiredMessage text.
Conversation ID.
Message text.
{
"success": true,
"data": { "id": "...", "status": "queued" }
}/api/v1/conversations/:id/resolveResolve a conversation
Marks a conversation as resolved.
Requires scope messages:send
Path parameters
idstringrequiredConversation ID.
Conversation ID.
{
"success": true,
"data": { "id": "...", "status": "resolved" }
}Contacts
Create, read, update, and manage your contacts.
/api/v1/contactsList contacts
Returns a paginated list of contacts, optionally searched or filtered by opt-in status.
Requires scope contacts:read
Query parameters
searchstringMatches against phone, name, or email.
opted_inbooleanFilter by opt-in status.
pagenumberPage number, defaults to 1.
limitnumberResults per page, defaults to 20.
Matches against phone, name, or email.
Filter by opt-in status.
Page number, defaults to 1.
Results per page, defaults to 20.
{
"success": true,
"data": [{ "id": "...", "phone": "+15551234567", "name": "Jane Doe", "optedIn": true }],
"pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}/api/v1/contacts/:idGet a contact
Returns a single contact, including its tags.
Requires scope contacts:read
Path parameters
idstringrequiredContact ID.
Contact ID.
{
"success": true,
"data": { "id": "...", "phone": "+15551234567", "name": "Jane Doe", "tags": ["vip"] }
}/api/v1/contactsCreate or update a contact
Creates a new contact, or updates the existing one if the phone number already exists for your account.
Requires scope contacts:write
Body parameters
phonestringrequiredPhone number in E.164 format.
namestringContact name.
emailstringContact email.
languagestringPreferred language code. Defaults to "en".
tagsarrayTags to attach to the contact.
opted_inbooleanWhether the contact has opted in to receive messages. Defaults to false.
Phone number in E.164 format.
Contact name.
Contact email.
Preferred language code. Defaults to "en".
Tags to attach to the contact.
Whether the contact has opted in to receive messages. Defaults to false.
{
"success": true,
"data": { "id": "...", "phone": "+15551234567", "name": "Jane Doe" }
}/api/v1/contacts/:idUpdate a contact
Updates a contact's name, email, or custom fields.
Requires scope contacts:write
Path parameters
idstringrequiredContact ID.
Body parameters
namestringContact name.
emailstringContact email.
Contact ID.
Contact name.
Contact email.
{
"success": true,
"data": { "id": "...", "name": "Jane Doe" }
}/api/v1/contacts/:idDelete a contact
Permanently deletes a contact.
Requires scope contacts:write
Path parameters
idstringrequiredContact ID.
Contact ID.
{
"success": true,
"message": "Contact deleted"
}/api/v1/contacts/importBulk import contacts
Creates multiple contacts at once. Contacts with a phone number that already exists are skipped.
Requires scope contacts:write
Body parameters
contactsarrayrequiredArray of { phone, name?, email? } objects.
Array of { phone, name?, email? } objects.
{
"success": true,
"data": { "imported": 1 }
}/api/v1/contacts/opt-out/:phoneOpt out a contact
Marks a contact as opted out, by phone number.
Requires scope contacts:write
Path parameters
phonestringrequiredContact phone number.
Contact phone number.
{
"success": true,
"message": "Contact opted out"
}Templates
Create and read your WhatsApp message templates.
/api/v1/templatesCreate a template
Submits a new WhatsApp message template to Meta for approval. Newly created templates start in the "pending" status until Meta reviews them. For a carousel (a message with 2 to 10 swipeable cards), add a CAROUSEL component whose cards each have an IMAGE or VIDEO HEADER (with example.header_handle from POST /templates/media-upload), an optional BODY of up to 160 characters, and 1 or 2 BUTTONS; every card must have the same shape. A card that breaks a rule is rejected with the reason before the template reaches Meta.
Requires scope templates:write
Body parameters
namestringrequiredTemplate name (lowercase, unique per language).
categorystringrequiredTemplate category.
languagestringTemplate language code. Defaults to "en".
componentsarrayrequiredTemplate structure in Meta's components format (header, body, footer, buttons, carousel).
Template name (lowercase, unique per language).
Template category.
Template language code. Defaults to "en".
Template structure in Meta's components format (header, body, footer, buttons, carousel).
{
"success": true,
"data": { "id": "...", "name": "order_confirmation", "status": "pending" }
}/api/v1/templatesList templates
Returns a paginated list of templates, optionally filtered by status or category.
Requires scope templates:read
Query parameters
statusstringFilter by status (pending, approved, rejected).
categorystringFilter by category (marketing, utility, authentication).
pagenumberPage number, defaults to 1.
limitnumberResults per page, defaults to 20.
Filter by status (pending, approved, rejected).
Filter by category (marketing, utility, authentication).
Page number, defaults to 1.
Results per page, defaults to 20.
{
"success": true,
"data": [{ "id": "...", "name": "order_confirmation", "status": "approved" }],
"pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}/api/v1/templates/:idGet a template
Returns a single template, including its component structure.
Requires scope templates:read
Path parameters
idstringrequiredTemplate ID.
Template ID.
{
"success": true,
"data": { "id": "...", "name": "order_confirmation", "components": [] }
}/api/v1/templates/:id/variablesGet a template's required variables
Returns the {{1}}, {{2}}, ... placeholders used by a template's header, body, and button URLs, plus the CSV column names a campaign audience upload needs (e.g. "var1", "var2"). Use this before building a CSV audience for the Create a campaign endpoint, so the file has exactly the right columns.
Requires scope templates:read
Path parameters
idstringrequiredTemplate ID.
Template ID.
{
"success": true,
"data": {
"templateId": "...",
"templateName": "order_confirmation",
"variables": [{ "placeholder": "{{1}}", "column": "var1", "component": "BODY" }],
"requiredCsvColumns": ["phone", "var1"]
}
}Campaigns
Create and send bulk template campaigns, including CSV-uploaded audiences with per-recipient template variables.
/api/v1/campaignsList campaigns
Returns a paginated list of campaigns, optionally filtered by status.
Query parameters
statusstringFilter by status (draft, scheduled, running, completed, paused, failed).
pagenumberPage number, defaults to 1.
limitnumberResults per page, defaults to 20.
Filter by status (draft, scheduled, running, completed, paused, failed).
Page number, defaults to 1.
Results per page, defaults to 20.
{
"success": true,
"data": [{ "id": "...", "name": "Summer Sale", "status": "completed", "sentCount": 120 }],
"pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}/api/v1/campaigns/:idGet a campaign
Returns a single campaign, including its template and phone number.
Path parameters
idstringrequiredCampaign ID.
Campaign ID.
{
"success": true,
"data": { "id": "...", "name": "Summer Sale", "status": "draft", "audienceType": "csv", "contactCount": 3 }
}/api/v1/campaignsCreate a campaign
Creates a draft campaign targeting either all opted-in contacts, a specific audience_type, or a CSV-style uploaded list of recipients with per-recipient template variables. For a "csv" audience, first call Get a template's required variables to find out which variables the template needs, then supply one entry in recipients per row with a "phone" and a variables map keyed by placeholder number (e.g. { "1": "Kundan", "2": "ORD-42" } for a template using {{1}} and {{2}}). Every recipient must supply a value for every variable the template requires, or the request is rejected.
Requires scope campaigns:write
Body parameters
namestringrequiredCampaign name.
template_idstringrequiredApproved template to send.
phone_number_idstringrequiredPhone number to send from.
audience_typestringrequiredOne of "all" (all opted-in contacts), "segment", "custom" (contact_ids), or "csv" (recipients).
segment_idstringRequired when audience_type is "segment".
contact_idsarrayRequired when audience_type is "custom".
recipientsarrayRequired when audience_type is "csv". Array of { phone, variables }, where variables is an object keyed by placeholder number as a string.
csv_filenamestringOriginal CSV filename, stored for reference on the campaign.
Campaign name.
Approved template to send.
Phone number to send from.
One of "all" (all opted-in contacts), "segment", "custom" (contact_ids), or "csv" (recipients).
Required when audience_type is "segment".
Required when audience_type is "custom".
Required when audience_type is "csv". Array of { phone, variables }, where variables is an object keyed by placeholder number as a string.
Original CSV filename, stored for reference on the campaign.
{
"success": true,
"data": { "id": "...", "name": "Summer Sale Announcement", "status": "draft", "audienceType": "csv", "contactCount": 2 }
}/api/v1/campaigns/:id/sendSend a campaign
Starts sending a draft or scheduled campaign to its full audience. For a "csv" audience, each recipient's message is personalized with the variable values supplied at creation time.
Requires scope campaigns:write
Path parameters
idstringrequiredCampaign ID.
Campaign ID.
{
"success": true,
"message": "Campaign started"
}/api/v1/campaigns/:id/analyticsGet campaign analytics
Returns send/delivery/read counts and rates for a campaign.
Path parameters
idstringrequiredCampaign ID.
Campaign ID.
{
"success": true,
"data": { "sent": 2, "delivered": 2, "read": 1, "failed": 0, "deliveryRate": "100.00", "readRate": "50.00" }
}/api/v1/campaigns/:idDelete a campaign
Permanently deletes a campaign and its recipient/message records.
Requires scope campaigns:write
Path parameters
idstringrequiredCampaign ID.
Campaign ID.
{
"success": true,
"message": "Campaign deleted"
}Calls
WhatsApp Calling (VoIP) — a separate Meta approval from base messaging. Requires a Cloud-API-native phone number, calling explicitly enabled on that number, and either 2,000+ daily unique message recipients or Tech Partner sandbox access. Outbound/inbound audio uses WebRTC (SDP offer/answer relayed through these endpoints); Seen does not provide a media relay of its own.
/api/v1/callsList calls
Returns a paginated list of inbound and outbound calls, optionally filtered by status.
Query parameters
statusstringFilter by status (ringing, in_progress, completed, rejected, no_answer, failed).
pagenumberPage number, defaults to 1.
limitnumberResults per page, defaults to 20.
Filter by status (ringing, in_progress, completed, rejected, no_answer, failed).
Page number, defaults to 1.
Results per page, defaults to 20.
{
"success": true,
"data": [{ "id": "...", "direction": "inbound", "status": "completed", "durationSeconds": 42 }],
"pagination": { "page": 1, "limit": 20, "total": 1, "totalPages": 1 }
}/api/v1/calls/:idGet a call
Returns a single call.
Path parameters
idstringrequiredCall ID.
Call ID.
{
"success": true,
"data": { "id": "...", "direction": "inbound", "status": "completed", "durationSeconds": 42 }
}/api/v1/callsPlace an outbound call
Places an outbound WhatsApp call, relaying your WebRTC SDP offer through Meta. Meta assigns and returns a call ID, which is then used to accept/reject/terminate this call.
Body parameters
phone_number_idstringrequiredYour connected phone number ID to call from.
tostringrequiredRecipient WhatsApp number in E.164 format.
sdp_offerobjectrequiredWebRTC SDP offer: { type: "offer", sdp: "..." }.
Your connected phone number ID to call from.
Recipient WhatsApp number in E.164 format.
WebRTC SDP offer: { type: "offer", sdp: "..." }.
{
"success": true,
"data": { "id": "...", "direction": "outbound", "status": "ringing" }
}/api/v1/calls/:id/acceptAccept an inbound call
Answers a ringing inbound call by relaying your WebRTC SDP answer through Meta.
Path parameters
idstringrequiredCall ID.
Body parameters
sdp_answerobjectrequiredWebRTC SDP answer: { type: "answer", sdp: "..." }.
Call ID.
WebRTC SDP answer: { type: "answer", sdp: "..." }.
{
"success": true,
"data": { "id": "...", "status": "in_progress" }
}/api/v1/calls/:id/rejectReject an inbound call
Declines a ringing inbound call before it connects.
Path parameters
idstringrequiredCall ID.
Call ID.
{
"success": true,
"data": { "id": "...", "status": "rejected" }
}/api/v1/calls/:id/terminateEnd a call
Ends an in-progress call, or cancels one that is still ringing.
Path parameters
idstringrequiredCall ID.
Call ID.
{
"success": true,
"data": { "id": "...", "status": "completed", "durationSeconds": 42 }
}Media
Upload and manage media files for use in messages and templates.
/api/v1/mediaList media files
Returns a paginated list of uploaded media files.
Query parameters
pagenumberPage number, defaults to 1.
limitnumberResults per page, defaults to 20.
Page number, defaults to 1.
Results per page, defaults to 20.
{
"success": true,
"data": [{ "id": "...", "originalName": "logo.png", "publicUrl": "https://..." }]
}/api/v1/media/uploadUpload a media file
Uploads a file (as base64) and, if a WABA is connected, also uploads it to WhatsApp so it can be referenced in outbound media messages.
Body parameters
filestringrequiredBase64-encoded file content.
file_typestringMIME type of the file.
file_namestringOriginal file name.
Base64-encoded file content.
MIME type of the file.
Original file name.
{
"success": true,
"data": { "id": "...", "publicUrl": "https://...", "whatsappMediaId": "..." }
}/api/v1/media/:idDelete a media file
Permanently deletes an uploaded media file.
Path parameters
idstringrequiredMedia file ID.
Media file ID.
{
"success": true,
"message": "Media file deleted"
}Webhooks
Register a URL to receive real-time events (inbound messages, delivery status) instead of polling.
/api/v1/webhooksList webhooks
Returns every webhook registered for your account. The signing secret is not included — it is only ever returned once, at creation.
{
"success": true,
"data": [{ "id": "...", "url": "https://example.com/webhooks/seen", "events": ["message.received"], "is_active": true }]
}/api/v1/webhooksRegister a webhook
Registers a URL to receive events. The response is the only time the signing secret is returned — store it, since it is needed to verify the X-Webhook-Signature header on every delivery. Owner/admin only.
Body parameters
urlstringrequiredHTTPS URL that will receive event POSTs.
eventsarrayrequiredEvent types to subscribe to: message.received, message.sent, message.delivered, message.read, message.failed.
HTTPS URL that will receive event POSTs.
Event types to subscribe to: message.received, message.sent, message.delivered, message.read, message.failed.
{
"success": true,
"data": { "id": "...", "url": "https://example.com/webhooks/seen", "events": ["message.received"], "is_active": true, "secret": "b6b6c1b0-..." }
}/api/v1/webhooks/:idUpdate a webhook
Updates a webhook's URL, subscribed events, or active status. This is the way to point an existing webhook at a new URL (e.g. a new tunnel/domain) without losing its signing secret — delete-and-recreate would issue a new secret instead. Owner/admin only.
Path parameters
idstringrequiredWebhook ID.
Body parameters
urlstringNew HTTPS URL for this webhook.
eventsarrayReplaces the full set of subscribed event types.
is_activebooleanPause (false) or resume (true) deliveries without deleting the webhook.
Webhook ID.
New HTTPS URL for this webhook.
Replaces the full set of subscribed event types.
Pause (false) or resume (true) deliveries without deleting the webhook.
{
"success": true,
"data": { "id": "...", "url": "https://new-tunnel.example.com/webhooks/seen", "events": ["message.received"], "is_active": true }
}/api/v1/webhooks/:idDelete a webhook
Permanently deletes a webhook registration. Owner/admin only.
Path parameters
idstringrequiredWebhook ID.
Webhook ID.
{
"success": true,
"message": "Webhook deleted"
}/api/v1/webhooks/:id/testSend a test event
Queues a synthetic message.received event to this webhook, so you can verify your endpoint and signature verification without waiting for a real message.
Path parameters
idstringrequiredWebhook ID.
Webhook ID.
{
"success": true,
"message": "Test event queued"
}Integrations
Helpers for third-party products (like a helpdesk) that let their users connect their own Seen account.
/api/v1/integrations/validate-keyValidate an API key + phone number pair
Lets a third-party product confirm that a phone number and API key a user pasted into its own UI are genuine and belong to the same tenant, before storing them. Unauthenticated — the API key is passed in the body, not the x-api-key header, since the caller is verifying credentials on someone else's behalf.
Body parameters
apiKeystringrequiredThe API key to validate.
phoneNumberstringrequiredThe phone number to check against the key's tenant. Matches on either the Meta phone_number_id or the display number.
The API key to validate.
The phone number to check against the key's tenant. Matches on either the Meta phone_number_id or the display number.
{
"success": true,
"data": { "valid": true, "phoneNumberId": "1143523638846055", "displayNumber": "+1 555 123 4567", "status": "connected" }
}