Quickstart

Key in hand to first message

Three steps: mint a workspace API key, send a message with one REST call, then mount the same catalog as an MCP server so your agent can drive it natively.

1. Get an API key

API keys are minted by an operator holding the shared service key (the X-Internal-Key header). Each key belongs to one workspace and carries scopes (send-only, read-only, full — default is send-only, least privilege).

POST/admin/api-keysservice-key gated
mint a key
curl -X POST "$AGENTPUSH_URL/admin/api-keys" \
  -H "X-Internal-Key: $INTERNAL_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "workspaceId": "acme",
    "label": "support-agent",
    "scopes": ["full"]
  }'
response — 201
{
  "id": "5f0c2f04-…",
  "key": "apk_9c41d2…",        // shown exactly once — store it now
  "workspaceId": "acme",
  "label": "support-agent",
  "scopes": ["full"]
}

Local development: when the API runs without an internal service key configured, auth is bypassed entirely — every request resolves to the default workspace with full scopes. Handy locally, never in production.

2. Send your first message

Every tool in the catalog is callable over REST at POST /tools/:namewith the tool's input as the JSON body. Sending a WhatsApp message is one call:

POST/tools/send_messageAuthorization: Bearer <key>
curl
curl -X POST "$AGENTPUSH_URL/tools/send_message" \
  -H "Authorization: Bearer apk_9c41d2…" \
  -H "Content-Type: application/json" \
  -d '{
    "to": { "channel": "whatsapp", "address": "+33612345678" },
    "content": { "text": "Hello from my agent 👋" }
  }'

The recipient is either a direct { channel, address } pair (E.164 number for WhatsApp/SMS, email address for mail, user id for Discord) or a { contact_id } from find_contact / upsert_contact.

possible responses
// sent (or queued while the provider confirms)
{ "status": "sent", "message_id": "wamid.HBgL…" }

// blocked — not an error: read the suggestion and correct course
{
  "status": "blocked",
  "blocked_reason": "outside the 24h WhatsApp session window",
  "suggestion": "Use a pre-approved template — call list_templates, then retry with content.templateName."
}
StatusMeaning
sentAccepted by the channel provider; a message_id is returned.
queuedJournaled and pending provider confirmation.
blockedPolicy stopped the send (session window, opt-out, unapproved template). Carries blocked_reason + suggestion — designed for the agent to self-correct.
failedThe provider rejected the send.

Invalid input returns 422 with Zod issues; an unknown tool name returns 404; a missing/invalid key returns 401.

3. Mount it as MCP

The same catalog is served as a stateless MCP server over streamable HTTP at POST /mcp, authenticated with the same Bearer key. Your agent gets typed tools with rich descriptions — no glue code.

ALL/mcpMCP streamable HTTP, stateless
mcp client config (json)
{
  "mcpServers": {
    "agentpush": {
      "type": "http",
      "url": "https://<your-agentpush-host>/mcp",
      "headers": { "Authorization": "Bearer apk_9c41d2…" }
    }
  }
}
or with claude code
claude mcp add --transport http agentpush \
  "https://<your-agentpush-host>/mcp" \
  --header "Authorization: Bearer apk_9c41d2…"

Discover the catalog

A public manifest lists every tool with its name, read/write tag and description — useful for building your own client or just browsing:

GET/toolscatalog manifest
curl
curl "$AGENTPUSH_URL/tools"
// → { "tools": [ { "name": "find_contact", "tag": "read", "description": "…" }, … ] }