Docs

The API contract.

A channel is a URL. POST JSON to it — there is no SDK, and this page is the whole contract.

Send an event
curl -X POST "https://<host>/events/<username>/<Channel%20Name>" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Deploy complete", "message": "api-service deployed to production" }'

Payload

Full event payload title and message are the only required fields
{
  "title":    "Deploy failed",                 // required, ≤200 chars
  "message":  "api-service · main @ abc123",   // required, ≤4000 chars

  "priority": "Crit",                           // free-form, shown on the card
  "nature":   "error",                          // free-form, shown on the card
  "dataJson": "{\"run\":\"4821\"}",              // ≤64000 chars, previewed

  "callback": [                                 // action buttons
    { "label": "Re-run pipeline",
      "webhook": "https://ci.example.com/hooks/rerun" }
  ],

  "ask": {                                      // blocking question, ≤8 options
    "options": [
      { "id": "approve", "label": "Approve", "tone": "positive" },
      { "id": "deny",    "label": "Deny",    "tone": "destructive" }
    ],
    "timeoutSec": 300                           // 5–86400
  }
}

Auth

Two ways to authorize a POST, bearer wins if both are present:

  • X-Paperplane-Endpoint-Key — per-channel, only if the channel has one set.
  • Authorization: Bearer pplt_… — a personal access token. Works across every channel you belong to, no endpoint key needed.

Tokens are minted in the app under Settings → Developer tokens, shown once, and stored server-side only as a SHA-256 hash. Six scopes exist (events:write, channels:read, messages:read, messages:write, tickets:read, tickets:write); a token cannot create or delete channels, manage membership, or mint or revoke another token.

Limits

FieldLimit
title200 characters
message4,000 characters
dataJson64,000 characters
ask options1–8
ask timeoutSec5–86,400
WebSocket backlog1,000 messages per connection
Free plan1 channel, 1,000 events/day

Asks — blocking questions

Add an ask object to an event and it renders as buttons on the notification. Only a signed-in human, tapping in the app, can answer — a personal access token cannot resolve the question it was used to send, even holding every scope. See the agents section for the full model.

MCP

paperplane-mcp is an MCP server for Claude Code, Claude Desktop, Cursor, or any MCP client, over stdio.

.mcp.json
{
  "mcpServers": {
    "paperplane": {
      "command": "npx",
      "args": ["-y", "paperplane-mcp"],
      "env": {
        "PAPERPLANE_TOKEN": "pplt_...",
        "PAPERPLANE_DEFAULT_CHANNEL": "Builds"
      }
    }
  }
}
ToolWhat it does
paperplane_askSend a question with buttons; block until answered or timed out
paperplane_check_askRecover an answer after the client's own tool-call timeout
paperplane_send_eventPush a notification, with priority and buttons
paperplane_list_channelsSee which channels exist
paperplane_list_messagesRead recent events and replies, with alert status
paperplane_set_alert_stateAcknowledge or resolve an alert
paperplane_replyLeave context in the thread
paperplane_list_ticketsCheck what is already tracked
paperplane_create_ticketFile a follow-up, linked back to the alert