Public API (v1)

Base URL: https://krunc.com/api/v1 · Spec: /api/v1/openapi.json

The API is tenant-scoped: a key only ever sees the workspace it was created in. Create keys under Dashboard → API keys (admin role). The full key (cos_…) is shown once; we store a SHA-256 hash and a prefix.

Authentication

Authorization: Bearer cos_xxxxxxxxxxxxxxxx

Every endpoint requires a scope. A key without the scope gets 403 {"error":"forbidden","message":"This key lacks the [scope] scope."}.

ScopeGrants
read:profileGET /profiles
read:storeGET /products
write:storePOST /products
read:ordersGET /orders, GET /orders/{id}
read:serversGET /servers, GET /servers/{id}/status
read:membersGET /members (display name + role only; users who hide their username appear as "Anonymous member")
write:postsPOST /posts (creates a draft)
read:analyticsGET /analytics/summary
webhooksGET/POST /webhooks, DELETE /webhooks/{id}, POST /webhooks/{id}/enable

Rate limit: 300 requests / minute per key (X-RateLimit-Remaining header, 429 with Retry-After when exceeded).

Conventions

  • Lists are paginated: ?page=1&per_page=25 (max 100) → {"data":[…],"meta":{"page":1,"per_page":25,"total":7,"pages":1}}
  • Errors: {"error":"not_found","message":"…"} with the matching HTTP status. Validation errors: 422 {"error":"validation_failed","errors":{"field":["msg"]}}
  • Money is in integer cents with a 3-letter currency.
  • Timestamps are UTC YYYY-MM-DD HH:MM:SS.

Examples

curl -H "Authorization: Bearer $KEY" https://krunc.com/api/v1/me
curl -H "Authorization: Bearer $KEY" "https://krunc.com/api/v1/products?per_page=50"
curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"name":"VIP","kind":"recurring","interval":"month","price_cents":999,"deliverables":[{"type":"discord_role","guild_id":"123","role_id":"456"}]}' \
  https://krunc.com/api/v1/products
curl -H "Authorization: Bearer $KEY" https://krunc.com/api/v1/servers/48/status

GET /servers/{id}/status only returns public information: counts, status and zone aggregates that meet the server's minimum-count threshold. Individual player positions are never exposed through the API.

Outbound webhooks

Register a URL and the events you want:

curl -X POST -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"url":"https://example.com/hooks/creatoros","events":["order.paid","entitlement.granted"]}' \
  https://krunc.com/api/v1/webhooks

The response includes secret (whsec_…) once. Events: order.paid, order.refunded, subscription.created, subscription.canceled, entitlement.granted, entitlement.revoked, member.joined, stream.started, stream.ended, server.online, server.offline, post.published, automation.run, report.created, or *.

Each delivery is an HTTP POST with JSON body {id, event, created_at, tenant_id, data} and headers:

HeaderMeaning
X-CreatorOS-Eventevent name
X-CreatorOS-Deliveryunique delivery id (use for idempotency)
X-CreatorOS-Signaturesha256= + HMAC-SHA256 of the raw body with your secret

Verify before trusting (PHP):

$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $_SERVER['HTTP_X_CREATOROS_SIGNATURE'] ?? '')) { http_response_code(400); exit; }

Respond with any 2xx within 10 seconds. Non-2xx responses are retried with exponential backoff (up to 5 attempts per delivery). After 10 consecutive failures the webhook is disabled and the workspace owners are notified; re-enable it with POST /webhooks/{id}/enable.

Internally any module can emit: $app->make('outbound')->emit($tenantId, 'order.paid', [...]).