swarmrise

Developer Documentation

Build integrations with the swarmrise API. Explore the architecture, endpoints, authentication, and webhook system.

REST API + Real-time + Outbound Webhooks

Architecture Overview

swarmrise uses Convex as its backend runtime. The frontend communicates via Convex's real-time WebSocket protocol for queries and mutations. HTTP endpoints handle inbound webhooks and file serving. The public REST API (v1) provides external access to organization resources and outbound webhook management.

System Architecture
                        Frontend (React 19 + Vite)
                                  |
                 Convex real-time protocol (WebSocket)
                                  |
                 +----------------+----------------+
                 |        Convex Backend           |
                 |                                 |
                 |  queries / mutations / actions   |
                 |  (domain functions in convex/*/) |
                 +-------+--------+--------+-------+
                         |        |        |
                  POST /webhooks/clerk  |  Public REST API v1
                         |        |
                  Clerk (Svix webhook)  GET /files/{storageId}

Current API Surface

The HTTP layer currently exposes two endpoints plus the full public REST API v1.

POST/webhooks/clerk
Auth:

Svix signature verification (HMAC via CLERK_WEBHOOK_SECRET)

Purpose:

Syncs user data from Clerk to Convex on user.created and user.updated events

Responses:

200 on success, 400 on missing headers or invalid signature, 500 on processing failure

GET/files/{storageId}
Auth:

Convex identity (session cookie) -- verifies org membership through storageFiles tracking table

Purpose:

Serves stored files (images inline, others as attachment download)

Responses:

File blob with Content-Type and Content-Disposition. 400 missing storageId, 403 not a member, 404 file not found

GET/api/v1/ping
Purpose:Health check endpoint
GET/api/v1/auth/ping
Purpose:Authenticated health check to verify API key validity

Response Envelope

All API responses use a consistent JSON envelope.

Success Response
{
  "data": { ... },
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-02-24T10:30:00Z"
  }
}
Paginated Response
{
  "data": [ ... ],
  "meta": {
    "requestId": "req_abc123",
    "timestamp": "2026-02-24T10:30:00Z",
    "hasMore": true,
    "nextCursor": "eyJpZCI6..."
  }
}

Authentication

swarmrise supports multiple authentication methods depending on the integration type.

Session Token Flow (Frontend)

The primary auth flow for browser clients uses Clerk JWTs.

1.User signs in via Clerk UI
2.Clerk issues a JWT session token
3.Convex client SDK sends JWT with every request
4.Convex validates JWT using Clerk JWT issuer domain
5.ctx.auth.getUserIdentity() returns verified identity

API Key Flow (External Integrations)

External systems authenticate using Clerk API keys scoped to an organization.

1.Send API key in Authorization header as Bearer token
2.API middleware validates key via Clerk
3.Middleware resolves identity and org context
4.Domain function executes with same auth as frontend
Example Request
curl -H "Authorization: Bearer sk_live_xxxx" \
     https://your-convex-url.convex.site/api/v1/orgas

Where to get your API keys

To use the REST API, you need an API key scoped to your organization. You can create and manage API keys from the API Keys page.

1.Sign in and select your organization
2.Go to the API Keys page to create a new key
3.Copy the key immediately -- it is only shown once
Manage API keys

Webhook Signature Flow (Inbound)

Inbound Clerk webhooks use Svix HMAC signature verification.

1.Clerk sends event with Svix headers
2.Server verifies signature using CLERK_WEBHOOK_SECRET
3.Event is processed (user.created, user.updated)

Public API Roadmap

The public REST API is organized into five tiers by integration value and implementation effort.

ImplementedInfrastructure required before public endpoints
API key authentication middleware

HTTP action that validates Clerk API keys and resolves them to an identity

Request/response envelope

Standardized JSON wrapper with data, meta, and error fields

Error handling middleware

Catches Convex errors and maps them to HTTP status codes

Rate limiting

Token-bucket rate limiter scoped per API key

API versioning header

X-Api-Version header with date-based versioning (2026-02)

ImplementedSubscribe to swarmrise events via HTTPS webhook endpoints
MethodEndpointDescription
GET/api/v1/orgas/:orgaId/webhooksList all webhook endpoints (secret redacted)
POST/api/v1/orgas/:orgaId/webhooksCreate a webhook endpoint (returns secret once)
GET/api/v1/orgas/:orgaId/webhooks/:webhookIdGet a webhook endpoint (secret redacted)
PATCH/api/v1/orgas/:orgaId/webhooks/:webhookIdUpdate url, events, or isActive
DELETE/api/v1/orgas/:orgaId/webhooks/:webhookIdDelete endpoint + cascade deliveries

Error Handling

All errors follow a consistent JSON shape.

Error Response Shape
{
  "error": {
    "code": "FORBIDDEN",
    "message": "User is not a member of this organization",
    "details": {},
    "requestId": "req_abc123"
  }
}

Error Code Mapping

Convex ErrorHTTP StatusAPI Error Code
Not authenticated401UNAUTHENTICATED
User not found401UNAUTHENTICATED
User is not a member of this organization403FORBIDDEN
Only the organization owner can...403FORBIDDEN
Only the role holder can...403FORBIDDEN
Organization not found404NOT_FOUND
Team not found404NOT_FOUND
Role not found404NOT_FOUND
Cannot delete team with child teams409CONFLICT
Cannot delete organization with multiple members409CONFLICT
A pending invitation already exists for this email409CONFLICT
Rate limit exceeded429RATE_LIMITED
Webhook endpoint not found404NOT_FOUND
Webhook URL must use HTTPS422VALIDATION_ERROR
Maximum 10 webhook endpoints per organization422VALIDATION_ERROR
Team already has a leader422VALIDATION_ERROR
Invalid domain format422VALIDATION_ERROR
ConvexError (validation)422VALIDATION_ERROR
Unexpected errors500INTERNAL_ERROR

Rate Limiting

Rate limiting uses a token-bucket algorithm scoped per API key.

Rate Limit Tiers

TierRequests/minuteBurst
Free6010
Standard30050
Enterprise1,500200

Rate Limit Headers

When exceeded, the API returns 429 Too Many Requests with a Retry-After header.

Response Headers
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Reset: 1709290800
Retry-After: 42
Existing Rate Limits: Invitations: Max 50 per member per 24 hours

Versioning

Date-Based Header Versioning

Version is specified via the X-Api-Version header (e.g., 2026-02). If omitted, the latest stable version is used.

Versioning Rules

  • --Breaking changes increment the date version
  • --Non-breaking additions (new fields, new endpoints) do not require a version bump
  • --URL path includes a major version prefix (/api/v1/) for catastrophic breaking changes only

What Constitutes a Breaking Change

  • xRemoving a field from a response
  • xChanging the type of a field
  • xRemoving an endpoint
  • xChanging the meaning of a status code
  • xChanging the auth requirements of an endpoint
Version Header
curl -H "X-Api-Version: 2026-02" \
     -H "Authorization: Bearer sk_live_xxxx" \
     https://your-convex-url.convex.site/api/v1/orgas

Security

Security is built into every layer of the swarmrise API.

Transport

  • --All API traffic must use HTTPS. HTTP requests are rejected with 301 redirect.
  • --TLS 1.2 minimum.

Authentication Methods

  • --Session tokens (Clerk JWT): Used by the frontend via Convex's real-time protocol
  • --API keys (Clerk M2M): Used by external integrations via the HTTP API
  • --Webhook signatures (Svix HMAC): Used for inbound webhooks from Clerk

Authorization Model

  • --Organization-scoped API keys can only access data within their bound organization
  • --All operations perform server-side membership verification via getMemberInOrga()
  • --Owner-only operations enforce orga.owner === member.personId
  • --Role-holder operations enforce role.memberId === member._id

Input Validation

  • --All inputs are validated through Convex validators (args definitions on every function)
  • --IDs are typed as Id<"tableName"> -- Convex rejects malformed IDs at the transport layer
  • --String inputs have length limits enforced server-side

Data Isolation

  • --Every org-scoped table has a by_orga index
  • --Queries always filter by orgaId to prevent cross-tenant data leakage
  • --deleteAllOrgaData() ensures complete cleanup when an organization is deleted

CORS

  • --For API key-authenticated endpoints, CORS is less relevant (server-to-server)
  • --For browser-accessible endpoints, only the swarmrise frontend origin is allowed

Outbound Webhook Guide

Subscribe to swarmrise events and receive real-time notifications via HTTPS webhooks.

Quick Setup

1.Create a webhook endpoint via the API
2.Save the signing secret (only shown once at creation)
3.Implement your endpoint handler
4.Verify the HMAC-SHA256 signature on every delivery

Supported Events

EventTriggerSource
organization.updatedOrg settings changedDecision polling
member.joinedInvitation acceptedDecision polling
member.leftMember removedDecision polling
decision.createdAny audited changeDecision polling
policy.createdNew policy publishedDecision polling
policy.updatedPolicy modifiedDecision polling
kanban.card.movedCard moved between columnsDirect hook

Cron job polls new decisions every 1 minute, maps them to event types, creates delivery records, and schedules HTTP delivery.

Each payload is signed with HMAC-SHA256. The signature header format is:

X-Swarmrise-Signature: t=<unix_timestamp>,v1=<hmac_hex>

10 second HTTP timeout per delivery attempt.

Exponential backoff at 1min, 5min, 30min, 2h (4 retries max). Failed deliveries are retried every 2 minutes via a separate cron job.

Endpoints are automatically disabled after 10 consecutive failures. Re-enabling via PATCH resets the failure count.

Maximum 10 webhook endpoints per organization. Webhook URLs must use HTTPS.

Create Webhook Request

POST /api/v1/orgas/:orgaId/webhooks
{
  "url": "https://example.com/webhooks/swarmrise",
  "events": [
    "decision.created",
    "member.joined",
    "kanban.card.moved"
  ]
}

Webhook Payload Format

Decision Event
{
  "event": "decision.created",
  "timestamp": "2026-02-26T14:30:00.000Z",
  "data": {
    "decisionId": "j572x...",
    "orgaId": "k473y...",
    "targetType": "policies",
    "targetId": "p291z...",
    "authorEmail": "alice@example.com",
    "diff": {
      "type": "Policy",
      "before": null,
      "after": { "title": "..." }
    }
  }
}
Kanban Card Move Event
{
  "event": "kanban.card.moved",
  "timestamp": "2026-02-26T14:30:00.000Z",
  "data": {
    "cardId": "c123...",
    "boardId": "b456...",
    "orgaId": "k473y...",
    "fromColumnId": "col_todo...",
    "toColumnId": "col_done..."
  }
}

Verifying Webhook Signatures

Use timing-safe comparison to verify the HMAC-SHA256 signature on every incoming webhook.

Node.js -- Verify HMAC-SHA256 Signature
const crypto = require("crypto");

function verifySignature(payload, header, secret) {
  const [tPart, v1Part] = header.split(",");
  const timestamp = tPart.replace("t=", "");
  const signature = v1Part.replace("v1=", "");

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${timestamp}.${payload}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature, "hex"),
    Buffer.from(expected, "hex")
  );
}

// Usage in Express:
app.post("/webhooks/swarmrise", (req, res) => {
  const signature = req.headers["x-swarmrise-signature"];
  const isValid = verifySignature(
    JSON.stringify(req.body),
    signature,
    process.env.SWARMRISE_WEBHOOK_SECRET
  );

  if (!isValid) return res.status(401).send("Invalid signature");

  // Process event...
  console.log(req.body.event, req.body.data);
  res.status(200).send("OK");
});