Server API & webhooks
The preview operator API of the community server — authentication, conventions, resources — and the signed webhook events modules emit.
This page describes the preview interface of the Server API and webhooks. Paths, fields and event names may change before general availability.
The Server API lets operators integrate their community with their own systems: manage members and approvals, schedule rooms, curate the directory, read usage, and receive events. It is served by the community server in the operator's deployment, so requests and data never leave the operator's environment.
Conventions
Base URL. https://<deployment.domain>/api/v1. Examples below use https://community.orbit.example/api/v1.
Authentication. Operator API keys, created in the admin console and sent as a bearer token. Each key has scopes — for example members:read, members:write, rooms:write, directory:write, usage:read — and is tied to the console user who created it for auditing.
curl https://community.orbit.example/api/v1/community \
-H "Authorization: Bearer $SYMORVA_API_KEY"Idempotency. Every POST accepts an Idempotency-Key header. Repeating a request with the same key and body returns the original result; the same key with a different body is rejected with idempotency_conflict.
Pagination. List endpoints take limit (1 to 100, default 20) and cursor, and return nextCursor until the last page.
Errors. Non-2xx responses share one envelope:
{
"error": {
"code": "forbidden",
"message": "The API key lacks the members:write scope.",
"requestId": "req_01JBX7Q4M9W8ZK2N5T3F6RVY0D"
}
}| Status | Codes |
|---|---|
| 400 | invalid_request, idempotency_conflict |
| 401 | unauthenticated |
| 403 | forbidden, module_disabled |
| 404 | not_found |
| 409 | conflict, stale_revision |
| 429 | rate_limited — see the Retry-After header |
| 5xx | internal, unavailable |
Concurrency. Resources edited by more than one operator carry a revision. Updates must send the revision they read; a stale revision returns 409 stale_revision instead of overwriting someone else's change.
Resources
| Method and path | Purpose | Module |
|---|---|---|
GET /community | Community identity, enabled modules and options | core |
GET /members | List members; filter by role or status | chat |
GET /members/{memberId} | Read a member | chat |
PATCH /members/{memberId} | Change a member's role | chat |
POST /members/{memberId}/restrictions | Mute, suspend or ban a member | chat |
GET /join-requests | Pending join requests | chat |
POST /join-requests/{requestId}/approve | Approve a join request | chat |
POST /join-requests/{requestId}/reject | Reject a join request | chat |
GET /groups | List groups | chat |
POST /groups | Create a group | chat |
POST /announcements | Send a system announcement | chat |
GET /rooms | List scheduled and live rooms | meetings |
POST /rooms | Schedule a room | meetings |
POST /rooms/{roomId}/end | End a live room | meetings |
GET /rooms/{roomId}/recordings | List a room's recordings | meetings |
GET /ai/grants | Grant metadata for audit — never content | aiStudio |
GET /wallet/transfers | List transfers | wallet |
GET /wallet/transfers/{transferId} | Read a transfer | wallet |
GET /directory/listings | List directory listings | dapps |
POST /directory/listings | Create a listing | dapps |
PATCH /directory/listings/{listingId} | Update a listing | dapps |
GET /usage | Usage records by unit and time window | metering |
GET /webhook-endpoints | List webhook endpoints | core |
POST /webhook-endpoints | Register an endpoint | core |
DELETE /webhook-endpoints/{endpointId} | Remove an endpoint | core |
Endpoints of a disabled module return 403 module_disabled.
There is deliberately no endpoint that reads message content, creates an AI grant on a member's behalf, or moves a member's funds. Announcements are system messages: they originate on the server, are not end-to-end encrypted, and are labelled as system messages in apps.
Example: schedule a room
curl -X POST https://community.orbit.example/api/v1/rooms \
-H "Authorization: Bearer $SYMORVA_API_KEY" \
-H "Idempotency-Key: 6f1c2a3e-governance-2026-11-04" \
-H "Content-Type: application/json" \
-d '{
"title": "Governance call",
"hostId": "mem_8Q2K",
"startsAt": "2026-11-04T10:00:00Z",
"durationMinutes": 60,
"broadcast": { "enabled": true, "maxAudience": 2000 }
}'{
"id": "room_3VJ9",
"title": "Governance call",
"status": "scheduled",
"mode": "large-room",
"hostId": "mem_8Q2K",
"startsAt": "2026-11-04T10:00:00Z",
"durationMinutes": 60,
"broadcast": { "enabled": true, "maxAudience": 2000 },
"revision": "1"
}maxAudience cannot exceed the manifest's modules.meetings.broadcast.maxAudience.
Example: read usage
curl "https://community.orbit.example/api/v1/usage?unit=participant-minutes&from=2026-10-01&to=2026-11-01" \
-H "Authorization: Bearer $SYMORVA_API_KEY"{
"data": [
{
"recordId": "use_01JBX8",
"module": "meetings",
"unit": "participant-minutes",
"quantity": 1840,
"windowStart": "2026-10-01T00:00:00Z",
"windowEnd": "2026-10-02T00:00:00Z"
}
],
"nextCursor": "c_02"
}Webhooks
Modules emit events to the endpoints you register. Delivery is at least once: the same event can arrive more than once, and events can arrive out of order. Use the event id to deduplicate and the occurredAt timestamp, not arrival order, to sequence.
Delivery
Each delivery is an HTTPS POST with a JSON body and these headers:
| Header | Value |
|---|---|
Symorva-Event-Id | The event's unique id |
Symorva-Event-Type | The event type, for example chat.join_request.created |
Symorva-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
Respond with any 2xx status within 10 seconds to acknowledge. Other responses and timeouts are retried with exponential backoff for up to 24 hours; after that the event is kept in the endpoint's failed-delivery list in the admin console and can be replayed.
Event envelope
Prop
Type
{
"id": "evt_01JBX9R2C4",
"type": "chat.join_request.created",
"community": "orbit-collective",
"occurredAt": "2026-10-28T09:41:12Z",
"data": {
"requestId": "jr_5TQ1",
"memberId": "mem_2B7X",
"groupId": "grp_governance"
}
}Verifying signatures
The signature is an HMAC-SHA256 over the timestamp, a period and the raw request body, keyed with the endpoint's signing secret. Verify it before parsing the body, and reject timestamps outside a short tolerance window to prevent replay.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifySymorvaSignature(
rawBody: string,
header: string,
secret: string,
toleranceSeconds = 300,
): boolean {
const parts = Object.fromEntries(header.split(",").map((p) => p.split("=", 2)));
const timestamp = Number(parts.t);
if (!Number.isFinite(timestamp)) return false;
if (Math.abs(Date.now() / 1000 - timestamp) > toleranceSeconds) return false;
const expected = createHmac("sha256", secret).update(`${timestamp}.${rawBody}`).digest();
const received = Buffer.from(parts.v1 ?? "", "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}Event catalog
| Module | Events |
|---|---|
| Chat | chat.member.joined, chat.member.left, chat.join_request.created, chat.join_request.resolved, chat.role.changed, chat.group.created, chat.message.created, chat.report.created |
| Meetings | meetings.room.scheduled, meetings.room.started, meetings.room.ended, meetings.participant.joined, meetings.participant.left, meetings.recording.started, meetings.recording.ready |
| AI studio | ai.grant.created, ai.grant.revoked, ai.job.created, ai.job.completed, ai.job.failed, ai.asset.published |
| Wallet | wallet.transfer.created, wallet.transfer.settled, wallet.transfer.failed, wallet.deposit.confirmed, wallet.withdrawal.requested, wallet.withdrawal.reviewed |
| DApps | dapps.listing.published, dapps.listing.unpublished, dapps.request.approved, dapps.request.denied |
| Creator economy | creator.tip.sent, creator.remix.attributed, creator.payout.settled |
Field lists for each event are on the module pages. Events never contain message content, prompts, generated content, data released to partner apps, or key material. Subscribing an endpoint to an event of a disabled module is rejected.
Client SDKs
The preview interface for building community apps — Swift and SwiftUI for Apple platforms, Kotlin and Jetpack Compose for Android, and the shared contract desktop shells bind to.
Deployment guide
Self-hosted and managed deployments, environments, regions, first deployment, updates, backups and release hygiene.