What you can do
Manage contacts
List, read, create, update, and delete the people on the other side of your channels.
Send messages
One endpoint for text, media, location, contacts, reactions, approved templates, interactive blocks and Flows — on WhatsApp, Instagram or Messenger.
Broadcast
Schedule a template to many recipients in one call and poll the campaign’s status.
Manage templates
List the approved catalog, create new templates, and submit them to Meta for approval.
Build WhatsApp Flows
Create, publish and send in-chat forms — bookings, surveys, lead capture — without leaving WhatsApp.
Connect channels
List connected channels and onboard a new WhatsApp Business number without the portal.
Drive it from an agent
Every send and every contact operation is also an MCP tool.
Scopes
messages:send and messages:bulk are deliberately separate: bulk is the higher-risk one, since it costs money at Meta and can damage sender reputation. Keep it in a key the marketing tool owns, not the one your transactional backend uses.
Contacts are the identity layer
A contact is a person on one of your channels. Keebai creates one automatically the first time someone writes to you, and the Contacts endpoints let you read and manage them from your own backend. Two identifiers matter, and confusing them is the most common integration bug:
Contacts are also how the messaging and CRM surfaces meet: a CRM ticket hangs off a contact’s
_id, not off a CRM customer.
Picking the sending channel
Every send declares achannel — whatsapp, whatsapp_qr, instagram or messenger. Which concrete channel it uses is resolved in this order:
channel_id, the Keebai id of the channel. Authoritative. List them withGET /v1/channels.phone_number_id, a WhatsApp-only alias: the numeric identifier Meta issues for the sending number, like109876543210987. It is not a KeebaiObjectId, and it is not the phone number itself. List yours withGET /v1/whatsapp/numbers.- Neither — then the project must have exactly one active channel of that type, and it is used. With more than one, the request fails with
400 CHANNEL_AMBIGUOUSand lists the candidates.
template, location, contacts, catalog and flow are WhatsApp-only, and an unsupported pair fails with 422 MESSAGE_TYPE_NOT_SUPPORTED_FOR_CHANNEL. The full matrix is on the send endpoint.
The 24-hour window
WhatsApp only allows free-form messages inside 24 hours of the recipient’s last inbound message. Outside that window Meta rejects the send.Single sends and broadcasts return different things
The two paths are not the same shape, and mixing them up is a common mistake.POST /v1/messages gives you a provider message_id — keep it to correlate the delivery and read webhooks. Only POST /v1/messages/bulk returns a broadcast_id, and it is the only thing GET /v1/messages/bulk/:broadcastId accepts.
This also makes the cost model clear: one broadcast to 5,000 recipients is one request against your daily quota; a loop over 5,000 single sends is 5,000.
Errors
Branch on
error.code, not on the message text.
Next steps
Send your first message
One endpoint, sixteen message types, four channels.
Build a WhatsApp Flow
Collect structured data inside the chat instead of linking out to a form.