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.
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.
/webhooks/clerkSvix signature verification (HMAC via CLERK_WEBHOOK_SECRET)
Syncs user data from Clerk to Convex on user.created and user.updated events
200 on success, 400 on missing headers or invalid signature, 500 on processing failure
/files/{storageId}Convex identity (session cookie) -- verifies org membership through storageFiles tracking table
Serves stored files (images inline, others as attachment download)
File blob with Content-Type and Content-Disposition. 400 missing storageId, 403 not a member, 404 file not found
/api/v1/ping/api/v1/auth/pingResponse Envelope
All API responses use a consistent JSON envelope.
{
"data": { ... },
"meta": {
"requestId": "req_abc123",
"timestamp": "2026-02-24T10:30:00Z"
}
}{
"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.
API Key Flow (External Integrations)
External systems authenticate using Clerk API keys scoped to an organization.
curl -H "Authorization: Bearer sk_live_xxxx" \
https://your-convex-url.convex.site/api/v1/orgasWhere 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.
Webhook Signature Flow (Inbound)
Inbound Clerk webhooks use Svix HMAC signature verification.
Public API Roadmap
The public REST API is organized into five tiers by integration value and implementation effort.
HTTP action that validates Clerk API keys and resolves them to an identity
Standardized JSON wrapper with data, meta, and error fields
Catches Convex errors and maps them to HTTP status codes
Token-bucket rate limiter scoped per API key
X-Api-Version header with date-based versioning (2026-02)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/orgas/:orgaId/webhooks | List all webhook endpoints (secret redacted) |
| POST | /api/v1/orgas/:orgaId/webhooks | Create a webhook endpoint (returns secret once) |
| GET | /api/v1/orgas/:orgaId/webhooks/:webhookId | Get a webhook endpoint (secret redacted) |
| PATCH | /api/v1/orgas/:orgaId/webhooks/:webhookId | Update url, events, or isActive |
| DELETE | /api/v1/orgas/:orgaId/webhooks/:webhookId | Delete endpoint + cascade deliveries |
Error Handling
All errors follow a consistent JSON shape.
{
"error": {
"code": "FORBIDDEN",
"message": "User is not a member of this organization",
"details": {},
"requestId": "req_abc123"
}
}Error Code Mapping
| Convex Error | HTTP Status | API Error Code |
|---|---|---|
| Not authenticated | 401 | UNAUTHENTICATED |
| User not found | 401 | UNAUTHENTICATED |
| User is not a member of this organization | 403 | FORBIDDEN |
| Only the organization owner can... | 403 | FORBIDDEN |
| Only the role holder can... | 403 | FORBIDDEN |
| Organization not found | 404 | NOT_FOUND |
| Team not found | 404 | NOT_FOUND |
| Role not found | 404 | NOT_FOUND |
| Cannot delete team with child teams | 409 | CONFLICT |
| Cannot delete organization with multiple members | 409 | CONFLICT |
| A pending invitation already exists for this email | 409 | CONFLICT |
| Rate limit exceeded | 429 | RATE_LIMITED |
| Webhook endpoint not found | 404 | NOT_FOUND |
| Webhook URL must use HTTPS | 422 | VALIDATION_ERROR |
| Maximum 10 webhook endpoints per organization | 422 | VALIDATION_ERROR |
| Team already has a leader | 422 | VALIDATION_ERROR |
| Invalid domain format | 422 | VALIDATION_ERROR |
| ConvexError (validation) | 422 | VALIDATION_ERROR |
| Unexpected errors | 500 | INTERNAL_ERROR |
Rate Limiting
Rate limiting uses a token-bucket algorithm scoped per API key.
Rate Limit Tiers
| Tier | Requests/minute | Burst |
|---|---|---|
| Free | 60 | 10 |
| Standard | 300 | 50 |
| Enterprise | 1,500 | 200 |
Rate Limit Headers
When exceeded, the API returns 429 Too Many Requests with a Retry-After header.
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 297
X-RateLimit-Reset: 1709290800
Retry-After: 42Versioning
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
curl -H "X-Api-Version: 2026-02" \
-H "Authorization: Bearer sk_live_xxxx" \
https://your-convex-url.convex.site/api/v1/orgasSecurity
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
Supported Events
| Event | Trigger | Source |
|---|---|---|
organization.updated | Org settings changed | Decision polling |
member.joined | Invitation accepted | Decision polling |
member.left | Member removed | Decision polling |
decision.created | Any audited change | Decision polling |
policy.created | New policy published | Decision polling |
policy.updated | Policy modified | Decision polling |
kanban.card.moved | Card moved between columns | Direct 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
{
"url": "https://example.com/webhooks/swarmrise",
"events": [
"decision.created",
"member.joined",
"kanban.card.moved"
]
}Webhook Payload Format
{
"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": "..." }
}
}
}{
"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.
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");
});