Connecting an External Agent to Meta-AI
Guide for any AI agent or automation (Claude Agent SDK, n8n, Zapier, custom script, etc.) that needs to send WhatsApp/Messenger/Instagram messages or read analytics from the meta-ai platform.
There are two integration paths:
| Path | Best for | Setup time |
|---|---|---|
REST API with X-API-Key | Any HTTP-capable client. Custom scripts, n8n, Zapier, server-side workflows. | 2 min |
| MCP server | Claude Desktop, other MCP-aware clients. Tools appear natively in the chat. | 5 min |
Both paths use the same API key.
Get an API Key
-
Log in at
https://meta.agenticlabs.siteas an owner or admin of your tenant.
(Members and viewers can't create keys — ask an admin.) -
Settings → API Keys.
-
Click Create key.
-
Give it a name (e.g.
n8n production,claude-desktop), optional expiry, and select the permissions you need:Scope Allows messages:sendPOST /api/messages/sendmessages:readinbox contacts, conversation history, message stats analytics:readsocial analytics (IG/FB), ads analytics templates:readlist WhatsApp + Messenger templates Pick the minimum the agent needs.
-
Copy the full key shown in the dialog. It's only displayed once. Format:
mta_<40-char-alphanum>. -
Store it as a secret in your agent system. Revoke from the same page if it ever leaks.
1. REST API
Base URL: https://api-meta.agenticlabs.site
Header: X-API-Key: mta_...
Interactive docs: https://api-meta.agenticlabs.site/docs (Swagger UI)
Minimal curl test
export KEY="mta_yourkeyhere"
curl -sS -H "X-API-Key: $KEY" \
"https://api-meta.agenticlabs.site/api/messages/inbox/contacts?tenant_id=YOUR_TENANT_UUID"
Tenant ID: visible in the URL of your dashboard, or returned in account_membership.tenantId after login.
Allowlisted endpoints
| Operation | Method + path | Scope |
|---|---|---|
| Send a message | POST /api/messages/send | messages:send |
| Inbox contacts | GET /api/messages/inbox/contacts?tenant_id=… | messages:read |
| Message stats | GET /api/messages/stats?days=7 | messages:read |
| Inbox messages | GET /api/messages/inbox?tenant_id=… | messages:read |
| User profile | GET /api/messages/user-profile/{channel_id}/{user_id} | messages:read |
| Conversation history | GET /api/conversations/history/by-channel/{channel_id}/{user_id} | messages:read |
| Social summary (IG + FB) | GET /api/social/summary | analytics:read |
| IG posts | GET /api/social/instagram/posts?limit=12 | analytics:read |
| FB posts | GET /api/social/facebook/posts?limit=12 | analytics:read |
| Ad accounts | GET /api/ads/accounts | analytics:read |
| Ad account summary | GET /api/ads/accounts/{id}/summary?date_preset=last_7d | analytics:read |
...and the rest of /api/ads/* and /api/social/* | analytics:read |
Routes not in this list (OAuth, team management, billing, channel deletion) require a human JWT — API keys won't work there by design.
Sending a message
POST /api/messages/send. Body shape depends on type:
WhatsApp — text
curl -X POST https://api-meta.agenticlabs.site/api/messages/send \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{
"channel_id": "CHANNEL_UUID",
"to": "5215512345678",
"type": "text",
"text": "Hello from my agent"
}'
WhatsApp — pre-approved template
curl -X POST https://api-meta.agenticlabs.site/api/messages/send \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{
"channel_id": "CHANNEL_UUID",
"to": "5215512345678",
"type": "template",
"template_name": "hello_world",
"language_code": "en_US"
}'
Messenger — button template
curl -X POST https://api-meta.agenticlabs.site/api/messages/send \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{
"channel_id": "CHANNEL_UUID",
"to": "USER_PSID",
"type": "button_template",
"text": "Pick one:",
"template_buttons": [
{"type": "postback", "title": "Option A", "payload": "opt_a"},
{"type": "web_url", "title": "Open link", "url": "https://example.com"}
]
}'
Instagram — reaction
curl -X POST https://api-meta.agenticlabs.site/api/messages/send \
-H "X-API-Key: $KEY" -H "Content-Type: application/json" \
-d '{
"channel_id": "CHANNEL_UUID",
"to": "USER_IGSID",
"type": "reaction",
"message_id": "WAMID...",
"emoji": "❤️"
}'
For the full list of supported type values and per-type fields, see the Swagger schema at /docs or frontend/lib/api/client.ts in this repo.
Discovering channel IDs
# Requires JWT (admin route). One-time: log in via the UI, copy your JWT
# from the network tab or call /api/auth/login.
curl -H "Authorization: Bearer $JWT" \
"https://api-meta.agenticlabs.site/api/auth/meta/channels?tenant_id=YOUR_TENANT_UUID"
Returns an array of {id, channel_type, channel_name, meta_page_id, ig_account_id, phone_number_id, disconnected_at}. Use id as channel_id in send calls. (Future improvement: expose a key-callable read-only GET /api/channels — tracked in the Mission Control epic.)
Errors
| Status | Meaning |
|---|---|
401 | Missing/invalid/revoked/expired key |
403 | Key lacks the required scope, OR the requested tenant/channel doesn't belong to this key's tenant |
404 | Channel not found |
400 / 422 | Bad request body (read detail in the JSON response) |
5xx | Server error — check the Status page and retry with backoff |
last_used_at on your key updates on every successful call — useful for spotting unused or compromised keys in the UI.
Node / fetch example
const KEY = process.env.META_AI_API_KEY!;
const BASE = 'https://api-meta.agenticlabs.site';
async function api(path: string, init: RequestInit = {}) {
const res = await fetch(`${BASE}${path}`, {
...init,
headers: {
'X-API-Key': KEY,
'Content-Type': 'application/json',
...(init.headers || {}),
},
});
if (!res.ok) throw new Error(`${res.status}: ${await res.text()}`);
return res.json();
}
await api('/api/messages/send', {
method: 'POST',
body: JSON.stringify({
channel_id: 'CHANNEL_UUID',
to: '5215512345678',
type: 'text',
text: 'Hello from Node',
}),
});
Python example
import os, requests
KEY = os.environ["META_AI_API_KEY"]
BASE = "https://api-meta.agenticlabs.site"
s = requests.Session()
s.headers.update({"X-API-Key": KEY})
r = s.post(f"{BASE}/api/messages/send", json={
"channel_id": "CHANNEL_UUID",
"to": "5215512345678",
"type": "text",
"text": "Hello from Python",
})
r.raise_for_status()
print(r.json())
2. MCP Server (for Claude Desktop)
If you're using Claude Desktop or another MCP-aware client, install our MCP server. It exposes the REST operations above as native MCP tools so Claude can call them with structured arguments.
See mcp-server/README.md for full install + config. TL;DR for Claude Desktop:
git clone https://github.com/SandPalace/meta-ai.git
cd meta-ai/mcp-server && npm install && npm run build
Then add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"meta-ai": {
"command": "node",
"args": ["/absolute/path/to/meta-ai/mcp-server/dist/index.js"],
"env": {
"META_AI_API_KEY": "mta_..."
}
}
}
}
Restart Claude Desktop. The tools (send_message, list_inbox_contacts, get_conversation_history, get_social_summary, etc. — 10 total) will appear in the tools list.
Security & Best Practices
- Treat API keys like passwords. Never commit them, never log them.
- Use the minimum scopes. If your agent only reads, don't grant
messages:send. - Set an expiration for short-lived integrations (demos, testing).
- Rotate periodically. Revoke and create a new key every quarter for production keys.
- One key per workflow. Don't share a single key across many agents — separate keys make it easy to revoke one without breaking others, and the
last_used_ataudit becomes meaningful. - HTTPS only. Our API rejects HTTP. Your client must trust the
agenticlabs.sitecertificate chain (standard).
Known limits (today)
- No rate limiting yet. A leaked key has unlimited blast radius until revoked. Tracked in Mission Control: rate-limit story under the "API key & external integration hardening" epic.
- No per-route fine-grained scopes beyond the 4 above.
analytics:readgrants both social and ads. Splitting intosocial:read+ads:readis tracked in the same epic. - No usage audit log beyond
last_used_at. A/recent activityview per key is on the roadmap. - MCP server isn't published to npm yet — install from source for now.
If any of these are blockers for your use case, open an issue.