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 APIAutomate 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_idare derived server-side from the key. A Pro-plan feature.
The Basics
Send Authorization: Bearer dwr_live_β¦ on every request
Each endpoint needs a specific scope β grant least privilege
Idempotency-Key on writes; undo via /operations/{id}/rollback
600 read / 60 write / 30 analytics / 10 AI per minute
Signed, real-time events for enrollments, bookings, certificates, payments
The queryable pull companion to webhooks β a 60-day window
Resources
Courses, modules, lessons, quizzes, and landing pages
Invite students, grant access (reversible), track progress
Sessions, session types, bookings, and attendance
Issue certificates after eligibility checks + public verify URL
Read-only headline metrics for the academy
Agent-native capabilities, constraints, and block schemas
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_idand grantedscopesare derived server-side from the key β you can never pass anacademy_idto 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.readnever allows writes. - Start with read-only keys, and add write scopes only when needed.
Available scopes
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
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.
Enrollments, bookings (before the session starts), certificates, and webhook endpoints
Sent notifications, and course / student edits (no snapshot is kept)
The API applies per-minute rate limits per key and request category.
Limits
| Category | Requests / minute |
|---|---|
| Reads | 600 |
| Writes | 60 |
| Analytics | 30 |
| AI generation | 10 |
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_β¦" }.
| Status | Code | Meaning |
|---|---|---|
| 401 | unauthorized | Missing or invalid key |
| 403 | missing_scope | Key lacks the required scope |
| 404 | not_found | Resource does not exist in your academy |
| 409 | idempotency_conflict | Same Idempotency-Key, different body |
| 422 | validation_error | Invalid request fields |
| 429 | rate_limit_exceeded | Rate 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
2xxto acknowledge. Reject anything you can't verify.
Common event types
access.granted, access.revoked
session.booked, session.booking.cancelled
certificate.issued, certificate.revoked
payment.succeededManage & 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
For clients that support a bearer token β send Authorization: Bearer dwr_live_β¦. The academy and scopes are derived from the key.
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
GET /capabilities β what this key can do
GET /constraints β field limits, enums, and data-model relationships
GET /overview β courses β modules β lessons β quizzes, plus pricing and landing pages
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 /constraintsandGET /overviewβ they tell an agent exactly which fields and enums are valid before it writes.
