Documentation menu

API reference

Four routes make up the Embed API — webinar create/list, webinar registration, course enrollment, and course embed-token minting. All are versioned under /api/v1, same as the Send API, and share its bearer-key auth.

Authentication

Authorization: Bearer mia_live_...

A missing or invalid key returns 401 { "error": "Missing or invalid Authorization: Bearer <apiKey>" } on every route below. Keys are managed under API & Integrations in the dashboard — see the quickstart.

POST /api/v1/webinars

Creates a webinar. accessMode is always forced to "registration" — the Embed API only ever deals in public-registration sessions, never subscriber-only live sessions.

| Field | Type | Required | Notes | | --- | --- | --- | --- | | title | string | yes | Trimmed; empty after trimming returns 400. | | scheduledAt | number | no | Unix ms timestamp. | | courseId | string | no | Ties the webinar to an existing course. | | capacity | number | no | Must be >= 0 if present. Registrations past capacity are waitlisted, not rejected. |

Returns 201 { "webinar": LiveSession }.

GET /api/v1/webinars

No body. Returns 200 { "webinars": LiveSession[] } — every session on the account with accessMode: "registration", subscriber-only sessions are filtered out.

POST /api/v1/webinars/[id]/register

Registers one end user for a webinar and sends the confirmation/waitlist email.

| Field | Type | Required | Notes | | --- | --- | --- | --- | | email | string | yes | Must match a basic email pattern, else 400 { "error": "A valid email is required" }. | | name | string | yes | Trimmed, max 200 characters, else 400 { "error": "A name is required" }. |

Errors: 404 if the webinar doesn't exist or isn't yours; 400 if it isn't accessMode: "registration"; 409 if it has already ended.

Response:

{ "status": "confirmed", "joinUrl": "https://mailinapp.com/webinar/<id>/join?token=..." }

or, once capacity is reached:

{ "status": "waitlisted", "joinUrl": null }

A confirmed registration fires the webinar's registered lifecycle email; a waitlisted one gets the plain waitlist notice regardless of any binding.

POST /api/v1/courses/[id]/enroll

Grants or revokes course access for an email address, keyed to your own entitlement decision rather than a MailInApp checkout.

| Field | Type | Required | Notes | | --- | --- | --- | --- | | email | string | yes | Same validation as registration; lowercased and trimmed before use. | | active | boolean | no | Defaults to true. false revokes access. |

404 if the course doesn't exist or isn't yours. Response: 200 { "subscriberId": "...", "active": true }.

A fresh grant (active: true on a subscriber who wasn't already entitled) fires the course's enrolled lifecycle email. Revoking access never emails the end user on your behalf.

POST /api/v1/courses/[id]/embed-token

Mints a short-lived (5-minute) signed token for embedding the course portal, after re-checking the subscriber is actually entitled.

| Field | Type | Required | Notes | | --- | --- | --- | --- | | email | string | yes | Same validation as above. |

Errors: 404 if the course doesn't exist, isn't yours, or isn't published; 409 if the account hasn't claimed a /learn/<slug> membership URL yet; 403 if this email isn't currently entitled (call enroll first).

Response: 200 { "portalUrl": "https://mailinapp.com/learn/<slug>/courses/<courseId>/embed?token=..." }. Redirect your app's iframe or a new window at portalUrl — it signs the visitor in and lands them in the ordinary course portal.

Lifecycle email bindings

A course or webinar can bind any of its lifecycle events to a studio project instead of the platform's plain confirmation copy:

| Resource | Events | | --- | --- | | Course | enrolled, completed (reminder accepted for parity, no automatic trigger — courses have no natural due date to fire one against) | | Webinar (LiveSession) | registered, reminder |

Bindings are set from the course/webinar's own Lifecycle emails panel in the dashboard, not through this API — they take effect on the next event regardless of whether it was triggered by the Embed API or the equivalent dashboard/public-form action. An event with no binding (or one pointing at a deleted/foreign project) falls straight back to today's plain email — this never breaks a send. A bound event renders through renderSingleRecipientEmail exactly like the Send API's template mode: full fallback engine, interactive blocks, a personal signed live-view link, and the project's own webhook/Responses tracking, attributed to that specific lifecycle event rather than a stored contact row.

Rate limits

Two independent caps apply, per route:

  • Per API key: 60 requests/minute. Exceeding it returns 429 { "error": "Rate limit exceeded" } for that key specifically.
  • Per account, Embed API quota: your plan includes a number of Embed API calls per calendar month, shared across all four routes above. 0 on Free/Starter returns 403 { "error": "The Embed API isn't included in your plan" }; exceeding a paid tier's quota returns 429 { "error": "Monthly Embed API quota for your plan exceeded" } until it resets on the 1st.

Webinar creation additionally re-checks your live-streaming quota (checkLiveSessionQuota) — the same broadcast-minutes/course gate the dashboard's own create flow enforces — and returns 403 with that check's own message if it fails.

Error codes

| Status | Meaning | | --- | --- | | 400 | Malformed JSON or a missing/invalid required field — see each route's own table above. | | 401 | Missing or invalid Authorization header, or the key has been revoked. | | 403 | Embed API not on your plan, quota check failed, live-session quota exceeded, or (embed-token) the email isn't currently entitled. | | 404 | The webinar/course doesn't exist or isn't owned by your account — deliberately the same response as "doesn't exist," same reasoning as the Send API's template-mode 404. | | 409 | (Webinar) the session has already ended. (Embed-token) the account hasn't claimed a membership URL yet. | | 429 | Per-key rate limit or monthly quota exceeded. |