API documentation

Supoid REST API v1

Programmatic access to your workspace. Available on Growth and Business plans.

Overview

The Supoid API is a REST-over-HTTPS API. All endpoints sit under https://supoid.com/api/public/v1/. Every request is authenticated with a Bearer token, returns JSON, and is rate limited per API key.

  • Base URL: https://supoid.com/api/public/v1
  • Auth: Authorization: Bearer sup_…
  • Content type: application/json
  • Errors: standard envelope (see below)
  • OpenAPI 3.1 spec: /api/public/v1/openapi

Authentication

Generate a key in Settings → Integrations → API keys. Each key carries one or more scopes (feedback:read, feedback:write, etc.) and an optional expiration. Keys are shown once — copy them immediately.

curl https://supoid.com/api/public/v1/feedback \
  -H "Authorization: Bearer sup_..."

Scopes

  • feedback:read — list, get
  • feedback:write — create, update, delete
  • clusters:read
  • roadmap:read / roadmap:write
  • changelog:read / changelog:write
  • webhooks:manage

Errors

Every error returns the same envelope:

{
  "error": {
    "code": "invalid_request",
    "message": "Validation failed",
    "details": [...]
  }
}

Common codes:

  • missing_authorization — no Bearer header
  • invalid_key / expired_key
  • insufficient_scope — key lacks the required scope
  • plan_no_api — workspace plan excludes API access
  • quota_exceeded — feedback ingest blocked (HTTP 402)
  • rate_limited — slow down (HTTP 429)
  • not_found — resource doesn't exist or isn't yours
  • invalid_request — Zod validation failed

Rate limits

Per API key, sliding-window:

  • Growth: 60 req/min
  • Business: 600 req/min

Responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Resetheaders. On 429 you also get Retry-After in seconds.

Feedback

The customer-facing feedback record. Source is auto-set to api.

Create feedback

POST /api/public/v1/feedback

{
  "title": "Add dark mode toggle",
  "body": "Late-night users want a dark theme preference saved per workspace.",
  "authorEmail": "user@example.com",
  "authorName": "Maya"
}

Returns 201 Created with the new feedback object.

List feedback

GET /api/public/v1/feedback?status=open&limit=50&cursor=<uuid>

Cursor pagination — the response includes nextCursor; pass it back to fetch the next page. Sorted newest first.

Update / delete

PATCH  /api/public/v1/feedback/{id}
DELETE /api/public/v1/feedback/{id}

DELETE soft-deletes (the row is hidden from the dashboard and public board). For GDPR hard-delete, contact support.

Clusters

AI-grouped feedback. Read-only via API — clusters are managed by the embedding pipeline.

GET /api/public/v1/clusters
GET /api/public/v1/clusters/{id}

Roadmap

GET    /api/public/v1/roadmap
POST   /api/public/v1/roadmap
GET    /api/public/v1/roadmap/{id}
PATCH  /api/public/v1/roadmap/{id}
DELETE /api/public/v1/roadmap/{id}

Setting status: completed automatically sets completedAt. Pass position to reorder inside a column.

Changelog

GET    /api/public/v1/changelog
POST   /api/public/v1/changelog
GET    /api/public/v1/changelog/{id}
PATCH  /api/public/v1/changelog/{id}
DELETE /api/public/v1/changelog/{id}

body is markdown. Setting isPublished: true stamps publishedAt server-side and triggers email + webhook fan-out.

Webhooks

Subscribe to events on your own server. Configure in the dashboard or via the API. Each webhook gets a unique signing key — shown once at creation.

Events

  • feedback.created
  • feedback.updated
  • feedback.status_changed
  • feedback.deleted
  • comment.added
  • vote.created
  • cluster.summarised
  • roadmap.created / roadmap.updated
  • changelog.published

Payload envelope

{
  "event": "feedback.created",
  "data": { "workspaceId": "...", "feedbackId": "..." },
  "timestamp": 1735689600
}

Headers

  • X-Supoid-Event — event name
  • X-Supoid-Webhook-Id — webhook uuid
  • X-Supoid-Signaturet=<unix>,v1=<hex hmac sha256>
  • User-AgentSupoid-Webhook/1.0

Verifying signatures (Node.js)

import crypto from "node:crypto";

export function verifySupoidSignature(opts) {
  const { rawBody, header, signingKey } = opts;
  const [t, v1] = header.split(",").map(p => p.split("=")[1]);
  const expected = crypto
    .createHmac("sha256", signingKey)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) {
    throw new Error("Invalid signature");
  }
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) {
    throw new Error("Stale signature (>5min)");
  }
}

Retries + auto-disable

Each delivery has a 10-second timeout. On non-2xx or network error, Inngest retries up to 5 times with exponential backoff. After 10 consecutive failures the webhook is auto-disabled and an audit log entry is written — re-enable it from the dashboard.

OpenAPI spec

The full machine-readable spec lives at /api/public/v1/openapi. Drop it into Swagger Editor, Insomnia, Postman, or your codegen of choice.


Found a bug or want a new endpoint? Email hello@supoid.com.