API reference
External endpoints, key permissions and request rules.
Use this API from your own server to list groups or record an enquiry. Create a key in Settings → API access and store it on that server, where visitors cannot see it. Each key belongs to one organization and needs permission for each task. Send it in the x-api-key header. For a JSON request, also send Content-Type: application/json.
| Method | Path | Permission |
|---|---|---|
GET | /api/integrations/v1/course-modalities | modality:read |
POST | /api/integrations/v1/leads | lead:write |
List groups
Use this call to show groups on your own website. It returns only published, listed groups in the key's organization. All filters are optional; when you send more than one, a group must match all of them.
| Query field | Format / effect |
|---|---|
courseId | ID of the original course. |
startDate | Date YYYY-MM-DD; matches the public start date in the group's time zone. |
country | Two-letter country code; converted to upper case. |
city, venue | Exact text matches. |
curl 'https://YOUR_DOMAIN/api/integrations/v1/course-modalities?country=ES' \
-H 'x-api-key: YOUR_SERVER_KEY'
The response is { "modalities": [...] }. Each item has modalityId, modalityKey, courseId, title, timezone, country, city, venue, hasStorefront, publicDates, seatAvailability, capacity and remainingSeats. Place fields, capacity and public dates may be null. seatAvailability is available or full. publicDates, when present, contains startsAt and endsAt, each null or { "instant": "..." }. Use modalityId when linking an enquiry to a group. An empty modalities array means no listed group matches your filters.
These endpoints do not enrol students or grant course access. Send buyers to the group's public offer, or follow Student access to add someone yourself.
Capture a lead
Use this call after someone sends an enquiry through your website. Give each form submission a stable externalLeadId so a lost response can be checked with the same request.
curl 'https://YOUR_DOMAIN/api/integrations/v1/leads' \
-H 'x-api-key: YOUR_SERVER_KEY' -H 'Content-Type: application/json' \
-d '{"externalLeadId":"form-1042","capturedAt":"2026-09-23T10:00:00Z","person":{"name":"Ana Ruiz","email":"ana@example.com"},"source":{"system":"website","form":"course-enquiry","pageUrl":"https://school.example/courses","locale":"es"}}'
| Field | Required | Rule |
|---|---|---|
externalLeadId | Yes | ID for this submission, 1–200 characters; reuse it for retries. |
capturedAt | Yes | ISO date-time; at most five minutes in the future. |
person | Yes | email or international phone required; name optional. |
source | Yes | system, form, pageUrl and locale; URL must use HTTP or HTTPS. |
courseId or modalityId | No | Choose at most one. A group must be published with enrolment open when first assigned. |
message | No | Up to 5,000 characters. |
customFields | No | Up to 20 fields with unique key, label and value. |
marketingConsent | No | Separate record of the person's marketing choice; see below. |
source.system and source.form must start with a lower-case letter and contain only lower-case letters, digits or hyphens (max 64 characters). pageUrl loses its query and fragment when stored. A new lead returns 201; an exact replay or a correction returns 200. Response fields are leadId, modalityId (string or null), courseId (string or null) and status (captured, contacted, converted or discarded). For example:
{ "leadId": "lead_id_from_response", "modalityId": null, "courseId": null, "status": "captured" }
Repeating the same data after normalization returns the saved lead without replacing staff edits. Changing data under the same external ID, or sending data for a contact already known to this organization, updates that lead; fields you send can replace earlier values. If you need to correct a submission, check what is already saved before sending changed data. A modalityId already linked to that lead can remain linked even if the group is no longer published; assigning a different group requires a published one with enrolment open.
marketingConsent is separate from the enquiry. Send it only if you recorded the person's choice. It needs granted (boolean), givenAt (ISO date-time), uiLocation (HTTP/S URL) and userAgent (1–1024 printable characters). Optional evidence includes IP address, country code, region code and language. A region code requires a country code.
Errors and retry rules
Errors use { "error": { "code": "...", "message": "..." } }; some include requestId or details. Do not depend on message text.
| Status | Typical code | Action |
|---|---|---|
| 400 | validation_error | Fix malformed JSON or invalid fields. |
| 401 | missing_api_key, invalid_api_key | Add a valid key with the required permission. |
| 404 | lead_target_not_found | Check the organization and target; new lead assignments need a published group with enrolment open. |
| 413 | payload_too_large | Lead body exceeds 32 KiB. |
| 429 | rate_limited | Wait the seconds in Retry-After. |
A 5xx response or a lost response does not tell you whether a write succeeded. Retry a lead with the same externalLeadId and data. Changed data can update a contact, so do not change it just to retry. See Integrations for key setup.