CursivaDocs
Developer

API keys & webhooks

The Developer area holds the credentials and callbacks that let external systems talk to your organization: API keys for the /api/v1 REST API and webhooks that push signed events to your endpoints. Managing either one requires an organization owner or admin role.

API keys

API keys grant programmatic access to the organization's /api/v1 REST API. Each key is scoped to the organization that owns it.

  • cs_live_ prefix — the full token has the form cs_live_<secret>. The server stores only a SHA-256 hash and a short 12-character prefix; a database leak never returns a working key.
  • Shown only once — the plaintext token exists only at creation time and cannot be recovered afterward. Copy it right away.
  • Optional expiration — a key can have an expiresAt or never expire; an expired key authenticates as invalid.
  • Revocation (soft) — revoking marks the key as revoked but keeps the row for auditing; a revoked key stops authenticating immediately.
  • Last used — each successful authentication stamps lastUsedAt, useful for spotting idle keys.

The API uses the token as a Bearer; it resolves the organization that owns the key and operates within it. Older keys with the ff_live_ prefix, generated before the rebrand, remain valid because what matters is the hash.

Keys are managed at /dashboard/settings/developer/api-keys.

Webhooks

Webhooks deliver signed events to your HTTPS endpoints, with a delivery history (the ‘calls’). Endpoints are managed at /dashboard/settings/developer/webhooks.

Available events

EventTrigger
enrollment.createdA new enrollment is created
content.completedA learner completes the content
purchase.createdA purchase is recorded
purchase.refundedA purchase is refunded
enrollment.requestedA learner requests enrollment, or a pay-first checkout creates a pending request
enrollment.approvedA pending enrollment request is approved
enrollment.rejectedA pending enrollment request is rejected (a pay-first buyer is refunded)
learner.unenrolledA learner is removed from a content
content.publishedA content is published
review.createdA learner posts or updates a review
community.joinedA learner joins or is assigned to a community
discussion.post.createdA member posts a question or reply in a community

How delivery works

  • Signing secret — each webhook receives a whsec_… secret, generated on the server and shown only once at creation.
  • HMAC signature — the body is sent via POST as JSON with the headers x-cursiva-event (the event name) and x-cursiva-signature (HMAC-SHA256 of the body with the secret). Validate the signature on your receiver before trusting the payload.
  • Timeout and resilience — each delivery has a 5-second time limit; a failed delivery never interrupts the action that triggered it.
  • Delivery history — each attempt records the response status, body (truncated), error, success, and duration, queryable as the endpoint’s history of ‘calls’.
  • Manual test (ping) — you can send a test event (webhook.test) to an endpoint and see the attempt in the history.
  • Enable/disable and delete — inactive endpoints don’t receive the fan-out; only those that are active and subscribed to the event are triggered.

A webhook can be assigned to a team within the organization, via teamId, to group endpoints by owner (personal member or team) in the listing.

REST API

The /api/v1 API manages almost everything the dashboard does. The exact request and response schemas are generated from the same contracts that run the API — see the interactive API reference (in production on its own host, docs.cursiva.io/api). This section is the map: how the API behaves and which resources it covers.

Conventions

Every call shares the same rules:

  • AuthenticationAuthorization: Bearer cs_live_…. A key resolves to one organization and acts with org-admin authority scoped to it; reaching another organization's data is impossible.
  • Rate limits240 reads/min and 60 writes/min per key. Every response carries X-RateLimit-Limit, -Remaining, and -Reset; a 429 adds Retry-After.
  • Pagination — list endpoints take page and pageSize (capped). A page past the end returns an empty array, so you can loop page++ until it's empty.
  • Money — amounts are integer minor units (e.g. cents) paired with a currency. Amounts in different currencies are never summed into one number.
  • Errors400 validation, 401 bad key, 403 not permitted, 404 not found (a resource in another org reads as not-found), 429 rate-limited. The body is { "error": "…" }.
  • Request bodies are JSON, capped at 4 MB.

What you can manage

ResourceEndpoints (prefix /api/v1)What you can do
OrganizationGET,PATCH /organization · GET /members · GET /organization/integrationsRead/update the org profile (name, bio, logo), list staff with their roles, check payment/video connection status
Custom domainGET,PUT,DELETE /organization/custom-domain · POST …/verifyConnect a domain, fetch the DNS records, run the ownership check, disconnect
ContentsGET,POST /contents · GET,PATCH,DELETE /contents/{id} · POST /contents/{id}/publish · POST /contents/{id}/duplicateList (paginated + filters), create, edit pricing/theme/access/format/ownership, publish, duplicate the packaging
DocumentGET,PUT /contents/{id}/documentRead the draft (and published) course document, or replace the draft — headless authoring
LearnersGET /learners · GET,POST /contents/{id}/learners · DELETE,POST /contents/{id}/learners/{userId} (reset)List/search learners, enroll by email (optionally into a cohort), unenroll, reset progress
Enrollment requestsGET /contents/{id}/enrollment-requests · POST /enrollment-requests/{id}/approve · …/rejectRead the approval queue with intake answers, approve, or reject (a rejected pay-first buyer is refunded automatically)
CommunitiesGET,POST /contents/{id}/communities · PATCH,DELETE /communities/{id} · members sub-routesCreate/manage cohorts (dates, seats, tutor), add/move/remove members, set the star badge
Intake formsGET,POST /intake-forms · GET,PATCH,DELETE /intake-forms/{id}Manage reusable enrollment-question sets
TagsGET,POST /tags · PATCH,DELETE /tags/{id}The org tag registry
CouponsGET,POST /contents/{id}/coupons · PATCH,DELETE /coupons/{id}Per-content discount codes
TeamsGET,POST /teams · GET,PATCH,DELETE /teams/{id} · members sub-routesTeams and their memberships
Sales & purchasesGET /sales · GET /purchases · GET /purchases/{id} · GET /contents/{id}/purchases · POST /purchases/{id}/refundThe order ledger (filter by content, buyer, status, date) and refunds
InsightsGET /insights · GET /insights/series · GET /contents/{id}/insights · …/salesHeadline numbers, time series, and per-content academic + sales figures
ReviewsGET /contents/{id}/reviewsRead ratings and the review feed (read-only)
WebhooksGET,POST /webhooks · PATCH,DELETE /webhooks/{id} · …/deliveries · …/testRegister and manage the endpoints described above, inspect the delivery log, send a test

MCP server

Cursiva ships an MCP server (Model Context Protocol) so AI clients — claude.ai, Claude Code, and any MCP-compatible agent — can operate your organization directly. The endpoint is https://mcp.cursiva.io. To connect from Claude Code with a browser login:

claude mcp add --transport http cursiva https://mcp.cursiva.io

Two auth modes are available: OAuth (the server acts as the signed-in user, with their real team roles — editor, finance, community) or an org API key (cs_live_… from Settings → Developer → API keys, org-admin authority, headless). The full guide — all 68 tools, connection steps per client, authoring workflow, and protocol details — lives at MCP server.

Best practices

  • Record who created each key or endpoint, which environment it uses, and how to revoke the credential.
  • Treat every key and every webhook secret as sensitive: shown once, stored as a hash/cipher, never in the frontend or screenshots.
  • Test in the appropriate environment whenever you rotate a key or point a webhook at a new endpoint.