AladiaDocs

Enrollments API, developer guide

Enroll a person in a course after a sale outside Aladia (CRM, your Stripe, a management system): POST /v2/integrations/enrollments with the academy API key, idempotency, errors, curl, Node.js and Python examples, the enrollment.created webhook and enrollments from SSO.

07/10/2026

When a course is sold outside Aladia (your CRM closes the deal, payment goes through your Stripe or a bank transfer), you grant access with one call: Aladia creates the account (or finds the existing one), enrolls the person in the course and edition and sends the You're enrolled email. The full flow for academy admins is in How you sell a course.

The enrollment is an external payment: no subscription, instalment or transaction on Aladia, no Aladia revenue in reports. Staff can remove the person from the course like any student (refunds are up to you).

Authentication

An academy API key: Settings › Developers › API keys, in the academy context (API keys and webhooks). Send it in the apikey header of every request, from your server only (never in the browser).

  • The key works in the workspace where it was created: it only enrolls in that academy's courses; another academy's course answers 404.
  • The key has the permissions of whoever created it: it needs Requests: manage (leads.update), which Owner and Admin have.

The call

POST https://api.aladia.io/v2/integrations/enrollments
apikey: <the academy key>
Content-Type: application/json
FieldRequiredDescription
emailyesthe person's email: key of the account and of idempotency
courseIdyesthe course id (it is in the /courses/<id> URL); the course must be published
cycleIdfor live courses with several editionsthe edition; with a single edition it can be omitted
namenofull name in one field (or firstName and lastName)
externalIdnothe customer id in your CRM (letters, digits and . _ : @ -, max 120): returned in the response and the webhook. external_id is accepted too
languagenoit or en: language of the email and of a new account
notifynofalse = no You're enrolled email (you send your own). Default true

Response

201 Created the first time (or when it adds a new edition of a live course), 200 OK if the person was already enrolled in that course and edition:

{
  "id": "6703f1c2a4b5c6d7e8f90123",
  "status": "created",
  "courseId": "6ab516ab857fed8289e3f276",
  "cycleId": "6ab516ab857fed8289e3f27a",
  "profileId": "6703f1c2a4b5c6d7e8f90001",
  "email": "mario.rossi@example.com",
  "accountCreated": true,
  "payment": "external",
  "externalId": "hubspot-deal-4242"
}

status: created (new enrollment, or a pending invitation turned into one), cycle-added (already enrolled, edition added), existing (nothing to do). accountCreated: true = the account was just created: the email includes Set your password.

Idempotency

The key is email + course + edition. Calling again with the same data creates no duplicates, sends no second email and fires no second webhook: retry safely after a timeout. Two simultaneous calls for the same email and course go through one at a time.

Errors

StatuserrorCodeWhen
400enrollment-invalidinvalid email, externalId with characters not allowed, edition not in the course, missing cycleId on a live course with several editions (the field is in details)
401missing, expired or deleted key
403the key lacks Requests: manage
403workspace-sso-requiredsign-in from your product set to Product only and the person has no account yet (see below)
404the course does not exist or is not a course of the key's academy
409lead-course-not-availablethe course is not published
409enrollment-account-blockedthe account with that email is blocked

Examples

curl

curl -X POST https://api.aladia.io/v2/integrations/enrollments \
  -H "apikey: $ALADIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "mario.rossi@example.com",
    "name": "Mario Rossi",
    "courseId": "6ab516ab857fed8289e3f276",
    "cycleId": "6ab516ab857fed8289e3f27a",
    "externalId": "hubspot-deal-4242"
  }'

Node.js (18+)

async function enroll({ email, name, courseId, cycleId, dealId }) {
  const response = await fetch('https://api.aladia.io/v2/integrations/enrollments', {
    method: 'POST',
    headers: { apikey: process.env.ALADIA_API_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ email, name, courseId, cycleId, externalId: dealId }),
  })
  const body = await response.json()
  if (!response.ok) throw new Error(`Aladia ${response.status}: ${body.errorCode ?? ''} ${body.message ?? ''}`)
  /* 201 = enrolled now, 200 = already enrolled: done either way. */
  return body
}

Python (requests)

import os
import requests

def enroll(email, name, course_id, cycle_id=None, deal_id=None):
    response = requests.post(
        "https://api.aladia.io/v2/integrations/enrollments",
        headers={"apikey": os.environ["ALADIA_API_KEY"]},
        json={"email": email, "name": name, "courseId": course_id, "cycleId": cycle_id, "externalId": deal_id},
        timeout=30,
    )
    if response.status_code not in (200, 201):
        raise RuntimeError(f"Aladia {response.status_code}: {response.text}")
    return response.json()

With Make or Zapier: an HTTP › Make a request module with the same method, headers and body, after the "payment received" or "deal won" step.

Webhooks

In the same academy, Settings › Webhooks:

  • lead.created: a request for information from the site (where a CRM sale starts);
  • lead.updated: the request changes status or assignee (also when Enroll moves it to Enrolled);
  • enrollment.created: a new enrollment (or an added edition) from the API or from Enroll. It does not fire when the response is existing.

Each delivery is a JSON POST to the webhook URL, with this body:

{
  "event": "enrollment",
  "data": {
    "event": "enrollment.created",
    "enrollment": {
      "id": "6703f1c2a4b5c6d7e8f90123",
      "status": "created",
      "course": { "id": "6ab516ab857fed8289e3f276", "title": "Master in Law" },
      "cycleId": "6ab516ab857fed8289e3f27a",
      "profileId": "6703f1c2a4b5c6d7e8f90001",
      "email": "mario.rossi@example.com",
      "name": "Mario Rossi",
      "externalId": "hubspot-deal-4242",
      "payment": "external",
      "via": "api",
      "accountCreated": true,
      "createdAt": "2026-10-07T09:30:00.000Z"
    }
  }
}

via: api or lead (Enroll in Requests).

Enrollments from SSO

If the academy uses sign-in from your product, the JWT courses claim enrolls too: at every sign-in the user joins the listed courses. Like API enrollments they involve no Aladia purchase, but they are marked from your product (SSO), not external payment: the product manages them (replace_courses can remove them), while API enrollments stay until staff removes them. No claim is needed to say "paid": neither counts as Aladia revenue.

Which to use:

SituationWay
The product knows at every sign-in which courses the user is entitled tocourses claim (and replace_courses)
A CRM or an external checkout closes the sale, onceenrollments API
Academy in Product only and the person never signed incourses claim: the API answers 403 workspace-sso-required because it creates no accounts outside the product

In Product only the API enrolls people who already have an account (they signed in from the product at least once). In Mixed or with SSO off it creates the account as usual.

On this page