Reading
Every read is scoped to your workspace and takes the scope named under it.
The message log
Section titled “The message log”GET /messages/whatsapp — scope MESSAGES_READ
| Query | Notes |
|---|---|
page |
from 1. Default 1 |
perPage |
default 20, maximum 100 |
direction |
INBOUND | OUTBOUND |
status |
QUEUED | SENT | DELIVERED | READ | FAILED | RECEIVED |
contact |
a number in any form; its digits are matched |
from |
which of your numbers, in any form. A workspace with more than one holds more than one set of threads |
curl "https://vaarto.com/api/v1/messages/whatsapp?direction=OUTBOUND&perPage=20" \ -H "Authorization: Bearer $VAARTO_API_KEY"const res = await fetch("https://vaarto.com/api/v1/messages/whatsapp?direction=OUTBOUND&perPage=20", { headers: { Authorization: `Bearer ${process.env.VAARTO_API_KEY}` },});
const { data } = await res.json();import os, requests
res = requests.get( "https://vaarto.com/api/v1/messages/whatsapp?direction=OUTBOUND&perPage=20", headers={"Authorization": f"Bearer {os.environ['VAARTO_API_KEY']}"}, timeout=10,)
data = res.json()["data"]$data = Http::withToken(env('VAARTO_API_KEY')) ->get('https://vaarto.com/api/v1/messages/whatsapp', [ 'direction' => 'OUTBOUND', 'perPage' => 20, ]) ->json('data');Each row carries id, direction, status, kind, text, caption, templateName,
variables, metadata, providerMessageId, error, to, from, contactName,
conversationId, and the timestamps createdAt, sentAt, deliveredAt, readAt,
failedAt.
Attachments are described in the row too — hasMedia, mediaId, mediaMimeType and
mediaFilename — so drawing a thread does not mean fetching every message again to find
out which of them carry one. hasMedia is the field to branch on: a template can carry a
media header while its kind reads TEMPLATE.
One message
Section titled “One message”GET /messages/whatsapp/:id — scope MESSAGES_READ.
The same fields, plus idempotencyKey. The list already carries everything else, so this
is for one message you hold the id of rather than a second call per row. 404 NOT_FOUND if
the id is not one of yours.
Poll the log if you are not using webhooks.
Discovery
Section titled “Discovery”Three endpoints so nothing has to be hard-coded. /templates and /numbers take
CATALOG_READ, which any send scope already includes — a sender needs to know what it may
send. /conversations takes CONVERSATIONS_READ, kept separate because a thread list is
every contact name and number you hold.
A template your code keeps a copy of can be rejected or paused by Meta without your code knowing. Asking is how it finds out.
# Approved templates, and the variables each one needs.curl "https://vaarto.com/api/v1/templates?status=APPROVED" \ -H "Authorization: Bearer $VAARTO_API_KEY"
# Numbers you may send from. isDefault is used when you omit "from".curl "https://vaarto.com/api/v1/numbers" \ -H "Authorization: Bearer $VAARTO_API_KEY"
# Threads, each carrying windowOpen — whether free text would arrive now.curl "https://vaarto.com/api/v1/conversations?perPage=20" \ -H "Authorization: Bearer $VAARTO_API_KEY"// Approved templates, and the variables each one needs.const templatesRes = await fetch("https://vaarto.com/api/v1/templates?status=APPROVED", { headers: { Authorization: `Bearer ${process.env.VAARTO_API_KEY}` },});
const { data: templates } = await templatesRes.json();
// Numbers you may send from. isDefault is used when you omit "from".const numbersRes = await fetch("https://vaarto.com/api/v1/numbers", { headers: { Authorization: `Bearer ${process.env.VAARTO_API_KEY}` },});
const { data: numbers } = await numbersRes.json();
// Threads, each carrying windowOpen — whether free text would arrive now.const threadsRes = await fetch("https://vaarto.com/api/v1/conversations?perPage=20", { headers: { Authorization: `Bearer ${process.env.VAARTO_API_KEY}` },});
const { data: threads } = await threadsRes.json();import os, requests
# Approved templates, and the variables each one needs.templates_res = requests.get( "https://vaarto.com/api/v1/templates?status=APPROVED", headers={"Authorization": f"Bearer {os.environ['VAARTO_API_KEY']}"}, timeout=10,)
templates = templates_res.json()["data"]
# Numbers you may send from. isDefault is used when you omit "from".numbers_res = requests.get( "https://vaarto.com/api/v1/numbers", headers={"Authorization": f"Bearer {os.environ['VAARTO_API_KEY']}"}, timeout=10,)
numbers = numbers_res.json()["data"]
# Threads, each carrying windowOpen — whether free text would arrive now.threads_res = requests.get( "https://vaarto.com/api/v1/conversations?perPage=20", headers={"Authorization": f"Bearer {os.environ['VAARTO_API_KEY']}"}, timeout=10,)
threads = threads_res.json()["data"]// Approved templates, and the variables each one needs.$templates = Http::withToken(env('VAARTO_API_KEY')) ->get('https://vaarto.com/api/v1/templates', [ 'status' => 'APPROVED', ]) ->json('data');
// Numbers you may send from. isDefault is used when you omit "from".$numbers = Http::withToken(env('VAARTO_API_KEY')) ->get('https://vaarto.com/api/v1/numbers') ->json('data');
// Threads, each carrying windowOpen — whether free text would arrive now.$threads = Http::withToken(env('VAARTO_API_KEY')) ->get('https://vaarto.com/api/v1/conversations', [ 'perPage' => 20, ]) ->json('data');| Endpoint | Query | Returns |
|---|---|---|
GET /templates |
status (default APPROVED), language |
name, language, category, status, bodyText, variables, variableCount, parameterFormat, rejectedReason |
GET /numbers |
— | displayNumber, verifiedName, status, isDefault, qualityRating |
GET /conversations |
page, perPage, contact, from |
id, contactWaId, contactName, from, lastInboundAt, lastOutboundAt, lastMessageAt, lastMessageText, unreadCount, windowOpen |
Only a number with status: "REGISTERED" can send.
The call log
Section titled “The call log”GET /calls — scope CALLS_READ. Takes page, perPage, direction
(INBOUND | OUTBOUND), status (RINGING, ACCEPTED, REJECTED, MISSED,
COMPLETED, FAILED) and from, which narrows to one of your own numbers — the same
number each row reports as on. Each row has id, direction, status, with, on,
conversationId, startedAt, endedAt, durationSeconds, createdAt.
This endpoint is the log. Acting on a call — answering it, declining it, placing one — is a
separate scope (CALLS_WRITE) and a separate guide, because it means exchanging an SDP
offer and carrying audio over DTLS-SRTP rather than making one HTTP request. See
calls. To act on a call as it rings rather than afterwards, subscribe to
call.status.