API reference
External endpoints, key permissions and request rules.
Use this API from your own server to list groups, record an enquiry or give a student access. 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/cohorts | cohort:read |
POST | /api/integrations/v1/leads | lead:write |
POST | /api/integrations/v1/cohort-students | student: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/cohorts?country=ES' \
-H 'x-api-key: YOUR_SERVER_KEY'
The response is { "cohorts": [...] }. Each item has cohortId, cohortKey, 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 cohortKey when granting access; use cohortId when linking an enquiry to a group. An empty cohorts array means no listed group matches your filters.
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 cohortId | No | Choose at most one. A group must be published 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, cohortId (string or null), courseId (string or null) and status (captured, converted or discarded). For example:
{ "leadId": "lead_id_from_response", "cohortId": 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 cohortId already linked to that lead can remain linked even if the group is no longer published; assigning a different group requires a published one.
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.
Grant student access
Use this call after you have decided that a student should join a published group. isPaid records your payment decision; this endpoint does not take a payment.
curl 'https://YOUR_DOMAIN/api/integrations/v1/cohort-students' \
-H 'x-api-key: YOUR_SERVER_KEY' -H 'Content-Type: application/json' \
-d '{"cohortKey":"autumn-2026","student":{"name":"Ana Ruiz","email":"ana@example.com"},"isPaid":false,"joinedAt":"2026-09-23T10:00:00Z"}'
| Field | Required | Rule |
|---|---|---|
cohortKey | Yes | Key of a published group in this organization. |
student.name, student.email | Yes | Nonempty name and valid email. |
student.phone | No | International phone number, at least five characters. |
joinedAt | Yes | ISO date-time. |
isPaid | No | Defaults to false; records payment status, does not charge a card. |
A new grant returns 201 and queues an access email. Repeating it for the same group and email with the same isPaid value returns 200 with the existing membership and does not queue another email. A different isPaid value returns 409 student_access_conflict; this call cannot change an existing membership's payment flag. Both successful responses have the same fields:
{
"studentId": "student_id_from_response",
"cohortStudentId": "membership_id_from_response",
"cohortId": "group_id_from_response",
"status": "active",
"loginPath": "/login"
}
If the response is lost, repeat the same request while the group is still published. A 200 response confirms that the membership exists. A missing, unpublished or foreign group returns 404, even when an earlier request may have created access; check the group in Taskpipe if its status changed. A full group returns 409 cohort_full for a new membership.
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 | cohort_not_found, lead_target_not_found | Check organization and target; only published groups accept new access or a new lead assignment. |
| 409 | cohort_full, student_access_conflict | Resolve capacity or payment-flag conflict. |
| 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, or retry access with the same group, email and isPaid. Do not change those values just to retry: changed lead data can update a contact, and changed isPaid can cause a conflict. See Integrations for key setup.