- Home
- Developers
The WACRM Core API for developers and agents
The WACRM Core API lets a developer or an AI agent read and write contacts, conversations, templates and WhatsApp messages for a WACRM project, call the metered AI endpoints, and check the token wallet. Every request authenticates with a scoped API key, and a send carries its own Idempotency-Key so a retry never happens twice.
By the WACRM team Updated
Production base URL https://api.wacrm.ai/v1 — sandbox https://api-sandbox.wacrm.ai/v1 for testing
Authenticate with an API key (Authorization: Bearer) or a Workspace session (Basic/JWT); scopes are granted per key at mint time
POST /v1/messages, POST /v1/agent-actions and POST .../tasks all require an Idempotency-Key header
Every error is a JSON object: {"error": {"code", "message"}} — never a bare string or a stack trace
A full endpoint reference below is rendered straight from docs/api/openapi-v1.yaml at build time
No interactive console on this page — keys are created and used only from your own Workspace
Auth, scopes and idempotency
Every v1 route checks the credential's scopes before it checks anything else: a key minted without
contacts:write, for example, gets 403 insufficient_scope on POST /v1/contacts
regardless of which plan the project is on. Most scopes are opt-in at mint time — only a handful of defaults
(like templates:read for a store key) are granted automatically, so a key only ever carries the
access it was actually given.
A write that should never run twice — sending a message, proposing an agent action, creating a task from a
conversation — requires a caller-supplied Idempotency-Key header. Retrying the exact same request
with the same key replays the first response verbatim instead of sending or creating anything a second time,
which is what lets a flaky network connection retry safely. For POST /v1/messages that replay
window is 24 hours; for proposing an agent action or creating a task, the key is unique per project with no
expiry.
Talk to an expert
Huy Nguyen (David), Founder & CEO
Ask which endpoint fits your integration, or how scopes and idempotency work.
Endpoint reference
Generated at build time from the same openapi-v1.yaml spec the Core API itself is contract-tested against — every path, method and required scope below comes straight from that file, grouped by its OpenAPI tag.
OpenAPI version: 1.0.0
| Servers | URL |
|---|---|
| Production | https://api.wacrm.ai/v1 |
| Sandbox (test API keys only, no wallet charges) | https://api-sandbox.wacrm.ai/v1 |
Projects
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /health | Liveness/readiness check (public, no auth, no DB write) | none (public) |
GET | /crm-projects | List projects owned by the authenticated account | projects:read |
GET | /crm-projects/current | Resolve the project for the current credential (store API key, Wix app instance, etc.) | projects:read |
GET | /crm-projects/{project} | Get a project by id | projects:read |
GET | /crm-projects/{project}/plan | Current plan, the full plan catalog, and seat/contact usage for a project (screen 21 "Plans & Upgrade") | projects:read |
GET | /crm-projects/{project}/storage-usage | Storage quota usage for a project (job m5-storage-backup, M5 R-STO-02) | projects:read |
GET | /crm-projects/{project}/ai-pause | Read the project's AI pause switch and auto-send opt-in | projects:read |
PUT | /crm-projects/{project}/ai-pause | Pause/resume AI auto-reply and set the auto-send opt-in | agent:approve |
GET | /crm-projects/{project}/export | Export this project's own data as NDJSON | export:read |
POST | /crm-projects/{project}/plan/checkout | Start a PayPal recurring-subscription checkout for a WACRM plan (SANDBOX ONLY) | billing:write |
GET | /crm-projects/{project}/meta-pages | List this project's connected Messenger/Instagram Pages | meta_dm:read |
POST | /crm-projects/{project}/meta-pages | Connect a Facebook Page (Messenger) or linked Instagram Professional account | meta_dm:write |
DELETE | /crm-projects/{project}/meta-pages/{metaPage} | Disconnect a Messenger/Instagram Page | meta_dm:write |
GET | /crm-projects/{project}/contacts/{contact}/billing-history | A contact's payment/billing history, for the Inbox contact panel (R-WRK-04) | projects:read |
GET | /crm-projects/{project}/cost-report | Meta WhatsApp cost report (by number/campaign/date range) and a 30-day forecast (job m5-cost-reports, R-WA-09) | cost_reports:read |
GET | /crm-projects/{project}/contacts/{contact}/quotes | A contact's quotes ("Deal"/"Quote draft"), for the Sales B2B Chrome edition (job m8-chrome-sales-b2b) | quotations:read |
GET | /crm-projects/{project}/social-comments | List this project's Facebook Page / Instagram comments | meta_comments:read |
POST | /crm-projects/{project}/social-comments/{comment}/replies | Propose a public reply to a comment (ALWAYS needs human approval) | meta_comments:write |
POST | /crm-projects/{project}/social-comments/{comment}/private-reply | Send a one-time private reply to a comment | meta_comments:write |
POST | /crm-projects/{project}/social-comments/{comment}/hide | Hide a comment (direct human action) | meta_comments:write |
DELETE | /crm-projects/{project}/social-comments/{comment} | Delete a comment (direct human action) | meta_comments:write |
Auth
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
POST | /auth/extension/sessions | Start a device-code login session for the Chrome extension | none (public) |
GET | /auth/extension/sessions/{session_id} | Poll a device-code login session | none (public) |
POST | /auth/extension/sessions/{session_id}/approve | Approve a pending extension session (called from the logged-in Workspace tab) | none (public) |
Wallet
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /token-balance | Current token balance and this cycle's usage | wallet:read |
AI
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /account/ai-usage | Per-AI-action call count and net tokens deducted | ai:read |
POST | /ai/translate | Translate text | ai:write |
POST | /ai/detect-language | Detect the language of a text | ai:read |
POST | /ai/summarize | Summarize a contact's conversation | ai:write |
POST | /ai/suggest-reply | Suggest reply drafts for a conversation (never sent automatically) | ai:write |
POST | /ai/product-answer | Draft a grounded answer to a product question (never an invented price) | ai:write, products:read |
POST | /ai/classify-message | Classify a message's intent and sentiment (job P27-ai-services-batch-a) | ai:write |
POST | /ai/extract-entities | Extract structured entities for a kind (order/address/booking/invoice) (job P27-ai-services-batch-a) | ai:write |
POST | /ai/score-lead | Score how sales-qualified a contact is, with a suggested next step (job P27-ai-services-batch-b) | ai:write |
POST | /ai/triage-alert | Group and rank the priority of a batch of Care alerts (job P27-ai-services-batch-b) | ai:write |
POST | /ai/suggest-next-action | Suggest the next best action for a B2B sales deal (job P27-ai-services-batch-b) | ai:write |
POST | /ai/booking-faq | Answer a booking FAQ (never an invented price or opening hour) (job m4-booking-ai-receptionist) | ai:write |
POST | /ai/booking-actions | Propose a book/reschedule/cancel action (job m4-booking-ai-receptionist) | ai:write, bookings:write |
POST | /ai/booking-actions/{action}/approve | Approve a proposed booking action, executing it through the booking core (job m4-booking-ai-receptionist) | ai:write, bookings:write |
GET | /glossary-terms | List this project's translation glossary terms (ordered by target_lang, term) | ai:read |
POST | /glossary-terms | Add a forced replacement applied to `POST /ai/translate` for a language pair | ai:write |
DELETE | /glossary-terms/{term} | Delete a glossary term | ai:write |
POST | /ai/transcribe-voice | Transcribe an existing voice-note attachment of this project (job P27-ai-services-batch-c) | ai:write |
POST | /ai/ocr-image | Extract text from an existing image attachment of this project (job P27-ai-services-batch-c) | ai:write |
POST | /ai/draft-template | Draft a WhatsApp template body from a freeform instruction (job P27-ai-services-template-tools) | ai:write |
POST | /ai/check-template-policy | Check draft template content against the brand-safety lint rules (job P27-ai-services-template-tools) | ai:read |
Products
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /commerce/products | List products (search by name or SKU) | products:read |
GET | /commerce/products/{product} | Get a product, with its SKU/price/currency/stock and variants | products:read |
Orders
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /commerce/orders | List orders (filter by contact phone/email, status, date) | orders:read |
GET | /commerce/orders/{order} | Get an order, with its lines (quantity, resolved product, sku, price) | orders:read |
GET | /commerce/orders/{order}/confirmation | Order-confirmation and COD two-way confirmation state for one order | orders:read |
GET | /commerce/order-automation | Read the project's order-automation settings (new order -> WhatsApp utility template, COD confirm) | orders:read |
PUT | /commerce/order-automation | Update the project's order-automation settings | orders:write |
Contacts
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /contacts | List contacts | contacts:read |
POST | /contacts | Create a contact | contacts:write |
GET | /contacts/{contact} | Get a contact | contacts:read |
PATCH | /contacts/{contact} | Update a contact | contacts:write |
DELETE | /contacts/{contact} | Erase a contact on request (hard delete) | contacts:write |
Audit
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /audit-logs | List this project's activity/audit log (newest first) | audit:read |
Labels
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
POST | /contacts/{contact}/labels | Attach a label to a contact | labels:write |
DELETE | /contacts/{contact}/labels/{label} | Remove a label from a contact | labels:write |
GET | /labels | List labels | labels:read |
POST | /labels | Create a label | labels:write |
DELETE | /labels/{label} | Delete a label | labels:write |
Notes
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /contacts/{contact}/notes | List notes on a contact | notes:read |
POST | /contacts/{contact}/notes | Add a note to a contact | notes:write |
Tickets
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /contacts/{contact}/tickets | List tickets on a contact | tickets:read |
POST | /contacts/{contact}/tickets | Create a ticket on a contact | tickets:write |
PATCH | /tickets/{ticket} | Update a ticket (e.g. change status) | tickets:write |
Templates
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /message-templates | List quick replies / WhatsApp message templates | templates:read |
POST | /message-templates | Create a template | templates:write |
PATCH | /message-templates/{template} | Update a template | templates:write |
Messages
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /contacts/{contact}/messages | Read a contact's WhatsApp conversation (watermark pagination) | contacts:read |
POST | /messages | Send a WhatsApp message (text or template) | messages:send |
Conversations
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /conversations | List conversations (Workspace Inbox) | contacts:read |
MCP
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
POST | /mcp/actions/authorize | Authorize one MCP tool call before `apps/mcp` invokes the tool's own endpoint (job P27-mcp-metering) | none (public) |
Channels
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
POST | /channels/whatsapp/connect-sessions | Start a WhatsApp Embedded Signup connect session | channels:write |
POST | /channels/whatsapp/connect-sessions/{session}/complete | Complete a connect session (code -> token, debug_token-verified before trusting waba_id/phone_number_id) | channels:write |
POST | /channels/{channel}/activate | Activate a connected number (subscribed_apps) — connected -> active | channels:write |
GET | /channels | List this project's connected channels | channels:read |
Export
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /export/conversations | WorkChat-compat alias of GET /conversations (read-only, same scope and shape) | contacts:read |
GET | /export/contacts | WorkChat-compat alias of GET /contacts (read-only, same scope and shape) | contacts:read |
GET | /export/messages | WorkChat-compat alias of GET /contacts/{contact}/messages (contact_id moved to query — see docs/api/README.md) | contacts:read |
Webhooks
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /webhook-endpoints | List this project's outbound webhook endpoints (secret never included) | wa_webhooks:read |
POST | /webhook-endpoints | Create a webhook endpoint — the secret is returned once, in this response only | wa_webhooks:write |
DELETE | /webhook-endpoints/{endpoint} | Delete a webhook endpoint | wa_webhooks:write |
Broadcasts
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /segments | List this project's saved audience segments | broadcasts:read |
POST | /segments | Create a saved audience segment | broadcasts:write |
GET | /contacts/{contact}/consent | Read a contact's current WhatsApp consent status | contacts:read |
POST | /contacts/{contact}/consent | Record a contact's WhatsApp consent decision (upserts the current row) | consent:write |
POST | /broadcasts | Create a draft broadcast (template + segment, not started) | broadcasts:write |
GET | /broadcasts/{broadcast} | Read a broadcast's status and counts | broadcasts:read |
POST | /broadcasts/{broadcast}/start | Start (or continue pumping) a broadcast — draft/paused/running -> running | broadcasts:write |
POST | /broadcasts/{broadcast}/pause | Pause a running broadcast | broadcasts:write |
Work
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
POST | /conversations/{conversation}/tasks | Create a task from a conversation | work:write |
GET | /conversations/{conversation}/tasks | List tasks created from a conversation | work:read |
POST | /work/callbacks | Inbound task status update from the Work side | none (public) |
Agent
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /agent-actions | List this project's agent actions | agent:propose |
POST | /agent-actions | Propose an agent action (auto-executes when the policy engine decides low risk) | agent:propose |
POST | /agent-actions/{action}/approve | Approve a proposed action, executing it through the guarded services | agent:approve |
POST | /agent-actions/{action}/reject | Reject a proposed action — it is never executed | agent:approve |
Relay
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
POST | /webhooks/meta/relay | Inbound WhatsApp traffic forwarded by the path-B relay (internal, not a customer-facing resource) | none (public) |
Bookings
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
GET | /crm-projects/{project}/booking-services | List this project's bookable services | bookings:read |
POST | /crm-projects/{project}/booking-services | Create a bookable service | bookings:write |
PATCH | /crm-projects/{project}/booking-services/{service} | Update a bookable service | bookings:write |
GET | /crm-projects/{project}/availability | Derived free slots for a service on one calendar date | bookings:read |
GET | /crm-projects/{project}/bookings | List this project's bookings | bookings:read |
POST | /crm-projects/{project}/bookings | Book a slot | bookings:write |
PATCH | /crm-projects/{project}/bookings/{booking} | Reschedule a booking (moves it, never creates a second row) | bookings:write |
POST | /crm-projects/{project}/bookings/{booking}/cancel | Cancel a booking (sets status + cancelled_at, keeps the row) | bookings:write |
GET | /public/store/{slug}/booking-services | Public, token-free list of a project's bookable services (WACRM-hosted booking page) | none (public) |
GET | /public/store/{slug}/availability | Public, token-free derived free slots for one service/date (WACRM-hosted booking page) | none (public) |
POST | /public/store/{slug}/bookings | Public, token-free booking create (WACRM-hosted booking page) | none (public) |
Client portal
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
POST | /crm-projects/{project}/contacts/{contact}/client-portal-links | Mint a client portal link for a contact | client_portal:write |
POST | /crm-projects/{project}/client-portal-links/{link}/revoke | Revoke a client portal link (one-way; mint a new one if needed) | client_portal:write |
GET | /client-portal/{token} | Public, token-guarded read of one contact's own tickets + bookings | none (public) |
Care
| Method | Path | Summary | Scope(s) |
|---|---|---|---|
POST | /care/alerts | Post a keyed Care alert, routed to the project's recipient | care:alerts:write |
GET | /care/alerts/{alert}/ack/{token} | Public, token-guarded ack of a Care alert — stops further escalation | none (public) |
POST | /care/alerts/{alert}/ack/{token} | Same handler as GET — ack a Care alert via an explicit POST | none (public) |
Error shape
Every non-2xx response is a JSON object with the same shape:
{ "error": { "code": "insufficient_scope", "message": "human-readable reason" } } | Status | Meaning |
|---|---|
| 400 | Validation failed |
| 401 | Missing or invalid credential |
| 402 | Token wallet balance too low for an AI call, or a plan limit (contacts/channels/seats) is used up |
| 403 | Credential valid but missing the required scope |
| 404 | Not found, or not owned by this project — the two are never distinguished |
| 409 | A conflicting state, e.g. a text send outside the 24h customer-service window |
| 422 | Semantically invalid, e.g. a template that is not yet approved |
| 429 | Rate limited |
Frequently asked questions
Do I need a special plan to call the Core API?
Any WACRM project can mint an API key and call the endpoints its key's scopes allow. AI endpoints are metered against your project's token wallet, same as using AI assist in the Workspace inbox.
What happens if I retry a request?
Endpoints that change state the most (sending a message, proposing an agent action, creating a task from a conversation) require an Idempotency-Key header. A retry with the same key replays the original response instead of repeating the action — for sending a message that replay window is 24 hours; for an agent action or a task the key never expires.
Is there a sandbox I can test against?
Yes — api-sandbox.wacrm.ai/v1 is the sandbox base URL: try your calls there before pointing your integration at production.
Where do I create and manage API keys?
From your WACRM Workspace, under Developer / API & MCP. Each key is minted with only the scopes you choose — nothing is granted by default beyond what config/api_keys.php lists as a default scope for that key kind.
Can I call the API from an AI agent?
Yes — any scoped API key works the same way for a script, a backend integration, or an agent. See the wacrm-mcp page for a ready-made MCP server that wraps five of these calls as agent tools.
Start building on the WACRM Core API
Request early access to get a Workspace and mint your first API key.