Welcome to Dwrrah

Use the Dwrrah API to connect your academy with external systems and manage courses, students, payments, and learning content through secure and flexible integrations.

πŸ“˜

Dwrrah Public API

Automate your academy end to end β€” courses, students, enrollments, live sessions, certificates, analytics, and outbound webhooks. Every key belongs to a single academy; scopes and academy_id are derived server-side from the key. A Pro-plan feature.


The Basics


Resources

The Dwrrah Public API lets your academy automate its own data β€” courses, students, enrollments, live sessions, certificates, analytics, and outbound webhooks. It's a Pro-plan feature.

Base URL: https://api.dwrrah.com/v1

1. Create an API key

In your academy dashboard, go to Settings β†’ Developers β†’ API Keys and create a key. The key is shown once at creation β€” store it securely. Each key belongs to exactly one academy, and its scopes are derived from the key.

πŸ”‘

Start with a read-only key and add write scopes only when you need them.

2. Make your first request (read)

curl https://api.dwrrah.com/v1/academy -H "Authorization: Bearer dwr_live_…"

3. Make a write request

Writes affect your academy directly. Send an Idempotency-Key to make retries safe:

curl -X POST https://api.dwrrah.com/v1/webhooks/endpoints -H "Authorization: Bearer dwr_live_…" -H "Content-Type: application/json" -H "Idempotency-Key: 3f1c2b7a-…" -d '{"url":"https://example.com/hook","events":["session.booked"]}'

Using n8n / Make

Use an HTTP Request node: set the URL to the endpoint, add a header Authorization: Bearer dwr_live_…, and an Idempotency-Key for writes. On 429, wait the Retry-After seconds, then retry.

πŸ’¬

Every response carries a request_id β€” include it when contacting support.

Every request must send your academy API key as a Bearer token: Authorization: Bearer dwr_live_xxxxxxxxxxxx

How keys work

  • Keys are created in Settings β†’ Developers β†’ API Keys in your dashboard.
  • The key is shown once at creation β€” store it securely.
  • Each key belongs to exactly one academy. The academy_id and granted scopes are derived server-side from the key β€” you can never pass an academy_id to act on another academy.
  • Keys are managed only from the dashboard (create, rotate, revoke). There is no key-management endpoint, so a key can never mint or revoke another key.

Missing or invalid key

Returns HTTP 401 with { "error": { "code": "unauthorized" } }.

Test keys

Test keys help you label and organize integrations, but they do not run in an isolated sandbox β€” every key acts on your own academy data, and writes affect your account directly. Responses to a test key include the header X-Dwrrah-Environment-Warning: test_keys_act_on_live_account, so you can spot test traffic in your logs.

πŸ›‘οΈ

Grant each key only the scopes it needs, and review write payloads before sending them.

Every endpoint requires a specific scope. A key only carries the scopes you granted it β€” always grant the least privilege an integration needs. A request missing a required scope returns HTTP 403 with missing_scope.

How scopes work

  • Each operation declares its required scope (shown as Required scopes on every reference page).
  • Read and write are separate β€” e.g. courses.read never allows writes.
  • Start with read-only keys, and add write scopes only when needed.

Available scopes

Read

academy.read, courses.read, lessons.read, quizzes.read, sessions.read, bookings.read, attendance.read, students.read, enrollments.read, certificates.read, memberships.read, assignments.read, analytics.read, events.read, webhooks.read

Write

academy.write, theme.write, seo.write, courses.write, lessons.write, quizzes.write, pages.write, sessions.write, bookings.write, students.write, enrollments.write, certificates.write, memberships.write, assignments.write, coupons.write, banners.write, notifications.write, webhooks.write, media.generate

⚠️

Some capabilities are never issuable in v1 β€” payments, hard deletes of revenue/progress-bearing data, domains, team/roles, and AI execution.

Idempotency

Send an Idempotency-Key header on write requests (POST / PATCH) to make them safely retryable β€” a network blip won't create a duplicate.

curl -X POST https://api.dwrrah.com/v1/enrollments -H "Authorization: Bearer dwr_live_…" -H "Idempotency-Key: 3f1c2b7a-…" -d '{"student_id":"stu_77aa","course_id":"crs_9a8b"}'

  • Repeating the same key returns the original result β€” no duplicate.
  • Reusing a key with a different body returns 409 idempotency_conflict.
  • Use a fresh unique key (e.g. a UUID) per logical operation.

Reversible writes & rollback

Every write response includes an operation object. When reversible is true, undo the write:

curl -X POST https://api.dwrrah.com/v1/operations/{operation_id}/rollback -H "Authorization: Bearer dwr_live_…"

Rollback is idempotent and scoped to your academy.

Reversible

Enrollments, bookings (before the session starts), certificates, and webhook endpoints

Not reversible

Sent notifications, and course / student edits (no snapshot is kept)

The API applies per-minute rate limits per key and request category.

Limits

CategoryRequests / minute
Reads600
Writes60
Analytics30
AI generation10

Writes and generation are intentionally stricter.

Rate-limit headers

Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset.

When you hit the limit

The API returns 429 with a Retry-After header. Wait that many seconds before retrying, and use exponential backoff for long-running automations.

Error shape

All errors return { "error": { "code": "...", "message": "..." }, "request_id": "req_…" }.

StatusCodeMeaning
401unauthorizedMissing or invalid key
403missing_scopeKey lacks the required scope
404not_foundResource does not exist in your academy
409idempotency_conflictSame Idempotency-Key, different body
422validation_errorInvalid request fields
429rate_limit_exceededRate limit hit β€” see Retry-After

Webhooks deliver signed, real-time events to an HTTPS endpoint you register β€” enrollments, bookings, certificates, payments, and more. They're the push companion to the queryable Event Stream.

1. Register an endpoint

curl -X POST https://api.dwrrah.com/v1/webhooks/endpoints -H "Authorization: Bearer dwr_live_…" -H "Content-Type: application/json" -d '{"url":"https://example.com/dwrrah/webhook","events":["session.booked","payment.succeeded"]}'

The response includes the signing secret (whsec_…) once β€” store it immediately. The URL must be public HTTPS.

2. Verify every delivery

Before trusting a payload, verify the Dwrrah-Signature header. It's a comma-separated list of v1=<hex> HMAC-SHA256 signatures over Dwrrah-Timestamp + "." + raw_request_body, keyed by your signing secret. Compute the same HMAC and compare. During a secret rotation's 24-hour grace window, the list carries both the new and previous secret's signature.

⚠️

Always verify the signature and reply with any 2xx to acknowledge. Reject anything you can't verify.

Common event types

Access

access.granted, access.revoked

Sessions

session.booked, session.booking.cancelled

Certificates

certificate.issued, certificate.revoked

Commerce
payment.succeeded

Manage & debug

  • Rotate secret β€” a new secret is returned once; the previous one stays valid for a 24h grace window.
  • Test β€” send a sample event to your endpoint.
  • Deliveries β€” inspect attempts and statuses, and retry failed deliveries.
πŸ”’

The signing secret is never returned by list endpoints β€” only secret_last4.

The Dwrrah MCP server exposes the same capabilities as the REST API β€” as tools for AI agents β€” under the same scopes and the same single-academy isolation. Read tools mirror the GET endpoints; write tools are guarded (drafts / explicit confirm) and idempotent.

Server URL: https://mcp.dwrrah.com

πŸ”’

Every tool runs under the scopes of the connection. payments, hard deletes, and AI execution are not exposed as tools.

Two ways to connect

API key (Bearer)

For clients that support a bearer token β€” send Authorization: Bearer dwr_live_…. The academy and scopes are derived from the key.

OAuth 2.1 (PKCE)

For connector-style clients β€” add the server URL as a connector, approve one academy and its scopes on the consent screen, and the token is bound to that academy.

API key

Create an MCP-enabled key in your dashboard, then point your MCP client at the server with the key as a bearer token. The academy and its scopes come from the key β€” you never pass an academy_id.

OAuth

Add https://mcp.dwrrah.com as a connector in a client that supports OAuth connectors. You'll be sent to a Dwrrah consent screen to pick one academy and approve scopes. Connections can be reviewed and revoked from your dashboard at any time.


The event stream is the queryable, pull-based companion to Webhooks. Instead of receiving events pushed to your server, you fetch them on demand β€” ideal for backfills, reconciliation, and audits.

Endpoints: GET /v1/events (list, newest first, cursor-paginated) and GET /v1/events/{id} (single event). Requires the events.read scope. Events are retained for a 60-day window.

What's in the stream

Activity across your academy β€” courses, lessons, quizzes, sessions, enrollments, certificates, memberships, and payments β€” plus every internal notification and email sent to students and instructors, each recorded as its specific cause event notification.<type>.

Filtering

Filter by event type. Subscribe to a specific cause, or use notification.* for all notifications. The full list is available from GET /constraints β†’ notification_event_types (see Discovery).

curl "https://api.dwrrah.com/v1/events?type=session.booked&limit=50" -H "Authorization: Bearer dwr_live_…"

πŸ”

Prefer webhooks for real-time reactions and the event stream for polling, backfills, and reconciliation. They share the same event shape.

Discovery endpoints are agent-native: they let an AI agent (or your own code) learn the shape of an academy β€” its capabilities, field limits, live structure, and content-block schemas β€” before making any write. All are read-only.

Endpoints

Capabilities

GET /capabilities β€” what this key can do

Constraints

GET /constraints β€” field limits, enums, and data-model relationships

Overview

GET /overview β€” courses ↔ modules ↔ lessons ↔ quizzes, plus pricing and landing pages

Block schemas

GET /schemas/lesson-blocks and GET /schemas/page-blocks β€” per-block config and styling

Readiness

Before publishing, check whether a course is complete: GET /v1/courses/{course_id}/readiness

Block shape

Lesson and page blocks use the canonical shape { "id": "blk_1", "block_type": "text", "sort_order": 0, "config": {} }. Text blocks accept rich HTML.

πŸ€–

Start any automation with GET /constraints and GET /overview β€” they tell an agent exactly which fields and enums are valid before it writes.