Get started
MCP server
Operate WhatsApp from an AI agent: Claude Code, Cursor or your own agent calls Nodium through the Model Context Protocol, with your API key.
Nodium serves its own MCP server at https://nodium.io/api/v1/mcp. An agent connected to it reads everything (numbers, conversations, contacts, templates, broadcasts, clients, the journal), sends messages one person at a time and keeps contacts up to date — as tools, without writing an HTTP client. It is the API, nothing more: each tool calls the matching route with your key, so roles, client isolation, quotas, rate limits and the request log apply exactly as they do to your code.
Not to be confused with Muse (MCP): there, Nodium serves *your* API to Meta's assistant, at /api/v1/apps/{id}/mcp, with OAuth for your end users. Here, *your* agent operates *Nodium*, with your account's key.
Connect an agent
# Claude Codeclaude mcp add --transport http nodium https://nodium.io/api/v1/mcp \ --header "Authorization: Bearer $NODIUM_KEY"# Acting for one of your clients by default (the tools can still name another)claude mcp add --transport http nodium-crm-4187 https://nodium.io/api/v1/mcp \ --header "Authorization: Bearer $NODIUM_KEY" --header "Nodium-Tenant: crm-4187"// .cursor/mcp.json, or the "mcpServers" block of any MCP client{ "mcpServers": { "nodium": { "url": "https://nodium.io/api/v1/mcp", "headers": { "Authorization": "Bearer nod_…" } } }}- No mass or configuration writes. Broadcasts, templates, clients and connect links are not tools: an agent reads messages written by anyone, and an instruction slipped into one of them must never trigger a send to everyone or change your account. Those stay with the API, called by your code.
- The key decides what the agent may do. Give an agent that answers customers an
agentkey (read and send) and a reporting agent amemberkey (read-only). Tools the key cannot use are not even listed. - A key pinned to one client (
POST /keyswithclientId) confines the agent to that client, whatever it asks. - Keep the key out of prompts and repositories: put it in the environment of the agent, as above.
The tools
- Numbers:
list_channels,get_channel,diagnose_channel. - Conversations and messages:
list_conversations,get_conversation,list_messages,get_message,send_message,mark_read. - Contacts:
list_contacts,get_contact,update_contact. - Templates and Flows:
list_templates,get_template,list_flows. - Broadcasts:
list_broadcasts,get_broadcast,preview_broadcast(who it would reach; sends nothing). - Clients:
list_clients,get_client. - Everything else:
search,read_journal,get_billing_summary.
Each tool takes the fields of its route — its schema and description are drawn from this reference — plus an optional tenant: the client to act on, by Nodium id or your externalId. Without it, the Nodium-Tenant header of the connection applies, then the key's default client. Reads are marked read-only for the agent; writes are not.
What the agent receives
- A success returns the content of
data, as JSON text. A refusal returnsisError: truewith the API's{ "error": { "code", "message", … } }: the agent branches oncodelike your code does (window_closed→ send a template;rate_limited→ wait). send_messageneeds your ownidempotencyKey(8 to 200 characters): the agent reuses it when it retries, never for a new message.- A long answer is cut at 100,000 characters with a note: ask for fewer items (
limit) or the next page (cursor).
Protocol
- Streamable HTTP, revisions
2025-06-18and2025-03-26:initialize,ping,tools/list,tools/call. POST only — no server stream, and no session (Mcp-Session-Idis never issued): every call carries the key. - Without a valid key:
401with a JSON-RPC error. A call from a web page (anOriginother than Nodium's) is refused: a key never lives in a browser. - 600 calls per minute per address on the endpoint, then the usual limits of each route (600 per minute per client, 6,000 per key).
Without an MCP client
curl -X POST https://nodium.io/api/v1/mcp \ -H "Authorization: Bearer $NODIUM_KEY" \ -H "Content-Type: application/json" \ -H "MCP-Protocol-Version: 2025-06-18" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "send_message", "arguments": { "channelId": "4c1d…", "to": "+33612345678", "type": "text", "text": "Your order has shipped.", "idempotencyKey": "order-4187-shipped", "tenant": "crm-4187" } } }'# → { "jsonrpc": "2.0", "id": 1,# "result": { "isError": false, "content": [{ "type": "text",# "text": "{\"conversationId\":\"…\",\"message\":{\"status\":\"sent\",…},\"duplicate\":false}" }] } }