Zapfeeddocs

Developer docs

Automate Zapfeed with an organization API key.

One key unlocks both a REST API and a built-in MCP server. Wire up your app to file bugs automatically, script your triage, or let an AI agent read votes and move posts across the roadmap - all scoped to your org, all auditable.

Overview

The integrations surface lives under https://zapfeed.io/api/integrations for REST and https://zapfeed.io/api/mcp for MCP. Every request authenticates with a single bearer token - no OAuth dance, no per-host config. The org (and optionally a single board) is derived from the key, so a key can never touch another org’s data. Posts and comments created through the API are tagged source=API so your team can tell integrations apart from real users in the dashboard.

Get an API key

  1. 1Open Dashboard → Settings → API keys (/admin/api-keys). API & MCP access is available on the Base plan and up.
  2. 2Click New key, give it a name (e.g. ci-bug-reporter), optionally pin it to one board, and set a rate limit (default 300/hour).
  3. 3Copy the plaintext key - it looks like zf_… and is shown once. Only a hash is stored; if you lose it, revoke and mint a new one.
  4. 4Store it as a secret (env var, secret manager). Never commit it or expose it in client-side code.
A key is scoped to your organization. Pin it to a board to further restrict it - that key can then only read and write posts on that one board.

Authentication

Send the key as a bearer token on every request:

Authorization: Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx

A missing or malformed header returns 401. An invalid or revoked key also returns 401. If the org’s plan no longer includes API access, requests return 403 with code: "UPGRADE_REQUIRED".

Rate limits

Each key has its own hourly budget (default 300 req/hour, configurable when you mint it). When you exceed it, requests return 429 with these headers:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 2026-07-16T12:00:00.000Z
Retry-After: 1800

Back off until Retry-After seconds have passed. Sustained abuse blocks the key for an hour.

REST endpoints

All paths are relative to https://zapfeed.io. All bodies are JSON.

GET/api/integrations/boards

List boards. Discover the boards this key can see. A board-pinned key returns only its board.

curl https://zapfeed.io/api/integrations/boards \
  -H "Authorization: Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx"

# → { "boards": [ { "id", "name", "slug", "description",
#                   "color", "icon", "visibility",
#                   "_count": { "posts": 42 } } ] }
GET/api/integrations/posts

List posts. Query bugs, features and feedback. Filters: board (slug or id), type, status (comma-separated), q (search), sort. Paginates via cursor.

curl "https://zapfeed.io/api/integrations/posts?type=BUG&status=NEW,UNDER_REVIEW&sort=most-voted&limit=25" \
  -H "Authorization: Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx"

# → { "posts": [ ... ], "nextCursor": "clx..." | null }
# Follow pages with ?cursor=<nextCursor>
POST/api/integrations/posts

Create a post. File a bug/feature/feedback item. If the key is pinned to a board, boardId is optional; otherwise pass a boardId in the org. Created posts are tagged source=API for auditing.

curl -X POST https://zapfeed.io/api/integrations/posts \
  -H "Authorization: Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Checkout fails on Safari 17",
    "description": "Stack trace + repro steps...",
    "type": "BUG",
    "anonymousEmail": "reporter@acme.com",
    "browserInfo": { "ua": "Safari/17" }
  }'

# → 201 Created - the full post object
GET/api/integrations/posts/{id}

Get a post. Fetch one post by id or by full dashboard URL (e.g. https://zapfeed.io/posts/<id>).

curl https://zapfeed.io/api/integrations/posts/clx123 \
  -H "Authorization: Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx"
PATCH/api/integrations/posts/{id}

Update status. Move a post through your workflow. A status change fires subscriber notification emails automatically.

curl -X PATCH https://zapfeed.io/api/integrations/posts/clx123 \
  -H "Authorization: Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "status": "PLANNED" }'
GET/api/integrations/posts/{id}/comments

List comments. All comments on a post, oldest first.

curl https://zapfeed.io/api/integrations/posts/clx123/comments \
  -H "Authorization: Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx"
POST/api/integrations/posts/{id}/comments

Add a comment. Comment as the key. It is recorded as "<key-name> (API)" so it is distinguishable from human replies.

curl -X POST https://zapfeed.io/api/integrations/posts/clx123/comments \
  -H "Authorization: Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{ "content": "On the roadmap for Q3 - thanks!" }'
priority is server-controlled on API posts (always created as MEDIUM) so an automated reporter can’t flood your triage queue with CRITICAL. Reclassify in the dashboard.

MCP - for AI agents

The same key powers a Model Context Protocol server over Streamable HTTP at https://zapfeed.io/api/mcp. Point any MCP-capable client (Claude, editors, your own agents) at it and the tools below appear automatically - auth, scoping and rate limits are shared with the REST surface.

{
  "mcpServers": {
    "zapfeed": {
      "type": "http",
      "url": "https://zapfeed.io/api/mcp",
      "headers": { "Authorization": "Bearer zf_live_xxxxxxxxxxxxxxxxxxxxxxxx" }
    }
  }
}
list_boardsDiscover boards visible to the key.
list_postsQuery posts. Defaults to open posts only unless you pass status.
get_postFetch one post by id or URL.
list_commentsRead a post’s comment thread.
create_postFile a new bug/feature/feedback item.
add_commentReply on a post as the key.
update_post_statusMove a post to a new status.

list_posts returns open posts (NEW, UNDER_REVIEW, PLANNED, IN_PROGRESS) by default - pass status explicitly to see completed or closed items.

Enum reference

type
BUG, FEATURE, FEEDBACK
status
NEW, UNDER_REVIEW, PLANNED, IN_PROGRESS, COMPLETED, CLOSED, REJECTED, WONT_FIX

Field limits: title 1–300 chars, description 1–20,000 chars, comment content 1–10,000 chars. screenshotUrl must be an https:// URL.

Errors

400Bad request - invalid JSON, missing/invalid fields, or no board resolved.
401Missing/malformed Authorization header, or invalid/revoked key.
403Out of scope for the key, board locked, or plan lacks API access (UPGRADE_REQUIRED).
404Post or board not found within the key’s scope.
429Rate limit exceeded - honour Retry-After.

Error bodies are JSON: { "error": "..." } (with an optional code).

API & MCP docs - automate Zapfeed | Zapfeed