GET/api/v1/organisation
Organisation profile (excludes billing and license details).
Required scope: organisation:read
Returns { data: object }. No query parameters.
Fields: name, slug, createdAt
Developer reference · v1
Read cakes, meeting types, members, and activity from your own tools. The REST API returns JSON and uses organisation-scoped API keys.
Use your deployed application origin as the base URL. Replace the example domain below. All endpoints start with /api/v1.
export SOS_BASE_URL="https://your-app.example.com"
# Set SOS_API_KEY using your secret manager, then run:
curl "$SOS_BASE_URL/api/v1/cakes?limit=50" \
--header "Authorization: Bearer $SOS_API_KEY"Example response (timestamps are Unix milliseconds; optional fields are omitted when unset):
{
"data": [
{
"id": "cake_123",
"organisationId": "org_123",
"cakeTypeId": "type_123",
"status": "active",
"totalSlices": 3,
"userSlices": {
"user_123": 3
},
"createdAt": 1788739200000
}
],
"pagination": {
"limit": 50,
"hasMore": false,
"nextCursor": null
}
}Authorization: Bearer sos_<key-id>_<secret>Every request requires an API key. The key determines the organisation automatically; do not send an organisation ID. Keys grant only the permissions selected at creation. Existing read keys cannot write; create a new key with cakes:write to modify cakes. Keys cannot create other keys or access another organisation.
Use HTTPS in production. Keep keys in server-side applications and jobs. Never put them in URLs, source control, logs, browser bundles, or mobile apps. Cross-origin browser requests are not enabled.
The members permission exposes names and emails; activities can contain free-text reasons. Only grant these permissions to integrations that need them. A member’s isActive field describes rotation participation, not API access.
The following read endpoints support GET. List results are ordered by document ID ascending. Unknown or repeated query parameters return 400. Responses include only the fields listed below, plus id.
Organisation profile (excludes billing and license details).
Required scope: organisation:read
Returns { data: object }. No query parameters.
Fields: name, slug, createdAt
Active and completed cakes, slice totals, assignments, and purchase status.
Required scope: cakes:read
Returns { data: array, pagination: object }. Accepts limit and after.
Fields: organisationId, cakeTypeId, status, totalSlices, userSlices, createdAt, completedAt, dueDate, winnerUserId, winnerUserIds, isPurchased, purchasedAt
Meeting types configured for the organisation.
Required scope: cake-types:read
Returns { data: array, pagination: object }. Accepts limit and after.
Fields: organisationId, name, isActive
Member names, emails, user IDs, roles, and rotation status. Includes personal data.
Required scope: members:read
Returns { data: array, pagination: object }. Accepts limit and after.
Fields: organisationId, authUid, displayName, email, role, isActive, joinedAt
Slice activity, including cake IDs, user IDs, timestamps, and reasons.
Required scope: activities:read
Returns { data: array, pagination: object }. Accepts limit and after.
Fields: organisationId, cakeId, userId, timestamp, reason
Cake status is active or completed. Member roles are owner, admin, or member. userSlices maps auth user IDs to slice counts; match these to member authUid values. dueDate is a date string. Billing, license, and key secrets are never returned.
Create a key with cakes:write. This permission is optional and unchecked by default. Every write takes a JSON body and requires Idempotency-Key: a unique 8–128 character value containing letters, numbers, underscores, or hyphens (a UUID works).
Reuse the same key and body when retrying an operation. A successful retry returns the original response and status with Idempotency-Replayed: true. Reusing a key for another operation returns 409. Keys are unique across all write endpoints within an organisation; receipts are retained indefinitely. Failed operations are not recorded. Retries still count toward the rate limit.
Start a tracker using an active meeting type from your organisation. Returns 201 with { data: cake }. A second active cake for the same type returns 409.
{
"cakeTypeId": "type_123",
"dueDate": "2026-10-15"
}Only cakeTypeId is required. The server sets the organisation, creation timestamp, active status, and zero slices. The optional dueDate schedules the purchase; it does not trigger an automatic action.
{
"userIds": [
"user_123"
],
"reason": "Late to weekly sync"
}Add one slice to each listed member. Supply 1–8 unique auth user IDs from your organisation; the optional reason allows up to 500 characters. The cake and activity records update together. Requests exceeding eight total slices return 409 without applying any slices. Reaching eight does not finalize the cake.
{
"dueDate": "2026-10-15",
"winnerUserIds": [
"user_123",
"user_456"
]
}Finalize an active cake and atomically start the next empty tracker. Returns 200 with { data: cake, nextCakeId }. The purchase date is required. Choose 1–8 unique winners from your organisation, or omit winners to use previously assigned winners, then all members tied for the most slices. An empty cake requires explicit winners. You may finalize before eight slices.
The server sets completedAt and marks the cake unpurchased. A different request to finalize an already completed cake returns 409. Explicit winners can differ from slice leaders; the dashboard and history display the selected winners.
{
"dueDate": "2026-10-22",
"winnerUserIds": [
"user_456"
]
}Set the scheduled purchase date, winners, or both, on an active or completed unpurchased cake. Returns 200 with the updated cake. Purchased cakes cannot be edited. Dates must be real calendar dates in YYYY-MM-DD form and are returned as midnight UTC ISO timestamps. Winner IDs are auth user IDs; winnerUserId contains the first winner for compatibility.
curl "$SOS_BASE_URL/api/v1/cakes" \
--request POST \
--header "Authorization: Bearer $SOS_API_KEY" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: create-weekly-sync-001" \
--data '{"cakeTypeId":"type_123"}'Unknown body fields and query parameters are rejected. Cross-organisation cakes or meeting types return 404. Invalid members return 400. Conflicts return 409 with a code such as active_cake_exists, slice_limit_exceeded, cake_not_active, cake_purchased, inactive_cake_type, winners_required, or idempotency_conflict.
List endpoints accept limit (1–100, default 50) and after. Pass the previous response’s pagination.nextCursor as after. Stop when hasMore is false. Cursors belong to the same endpoint and organisation. Pagination is not a snapshot: records can change while you read them.
// Server-side JavaScript (Node.js)
const base = process.env.SOS_BASE_URL;
const key = process.env.SOS_API_KEY;
let cursor = null;
do {
const url = new URL('/api/v1/cakes', base);
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('after', cursor);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${key}` },
});
if (!response.ok) throw new Error(`API returned ${response.status}`);
const page = await response.json();
// Send page.data to your reporting pipeline here.
cursor = page.pagination.nextCursor;
} while (cursor);Each key allows 60 authenticated requests per fixed minute, shared across endpoints. Invalid parameters on an authenticated request also consume capacity. On 429, wait the number of seconds in Retry-After before retrying. Use backoff for transient 500 errors.
Successful responses and 429 errors include X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (Unix seconds). API responses use Cache-Control: no-store.
| Status | Code | Action |
|---|---|---|
| 400 | invalid_request | Check the parameters, cursor, and page size. |
| 401 | invalid_api_key | Supply a valid key; expired and revoked keys are rejected. |
| 403 | insufficient_scope | Create a replacement key with the required permission. |
| 404 | not_found | Check the endpoint path. |
| 409 | Conflict | Resolve the cake state, capacity, or idempotency conflict before trying a new operation. |
| 429 | rate_limit_exceeded | Wait for Retry-After before retrying. |
| 500 | internal_error | Retry with backoff. Contact the app administrator if it persists. |
{
"error": {
"code": "insufficient_scope",
"message": "This endpoint requires members:read."
}
}Unsupported HTTP methods return 405 from the framework and may not use this JSON error format.
Keys expire after 1–365 days (default 90). To rotate, create a replacement key, update your integration, verify a successful request, and revoke the old key in Admin → API Keys. Revocation blocks subsequent authentication; requests already authorised may finish.
The full secret cannot be retrieved after creation. If it is lost, create a replacement. Keys belong to the organisation and remain valid after their creator leaves, until expiry or revocation. Review integrations when administrators change.
Import the OpenAPI 3.0 specification into Postman or your API tooling. Set the server URL to your deployment’s origin.