Create and manage learners¶
A learner is the core record in Cognassist: a person you enrol so they can take the cognitive assessment and receive support tailored to how they think and learn. This guide covers creating learners, keeping them reconciled with your own system of record, and the tutor-assignment model that decides who can see each learner.
All calls use the base URL https://api.uk.cognassist.com and a bearer token (see Getting started). Your credentials are scoped to your organisation, so every endpoint here only ever returns your organisation's learners.
The model in one paragraph¶
Store one stable id per learner on your side (clientReference) and create each learner once. A learner's email is unique within your organisation, so a second create for the same person is rejected, not duplicated; you still reconcile before creating so you handle that cleanly and keep your own id mapping. When you reconcile, page the learner list and match rows on email, the human key the list returns, and fetch a full record only when you need to confirm or repair the id you stored. Everything below is the detail on top of that.
For the join-key model (which identifier does which job, and why you match on email), see Core concepts. This guide uses those keys; it does not re-explain them.
Create a learner¶
POST /v1/learners creates a single learner and returns 201 Created with two Cognassist-side GUIDs, learnerId and learnerUserId. Store learnerId against your own record. It is the id you pass on every later call for that learner (invite, retrieve results, read evidence).
curl -X POST "https://api.uk.cognassist.com/v1/learners" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Jane",
"lastName": "Doe",
"email": "jane.doe@example.com",
"clientReference": "12345ABCDE",
"primaryTutorEmail": "tutor.name@example.com"
}'
The example above is trimmed to the fields that carry the model. The create body has more required fields than that, plus several optional ones.
Full create body: required and optional fields
Required: firstName, lastName, email, primaryTutorEmail, gender, postcode, dateOfBirth.
Strongly recommended: clientReference, your own id for the learner and your durable join key. Always send it; without it you have no stable key of your own to reconcile against. (The API reference lists it among the required create fields.)
Optional: programmeLevel (an integer 1 to 7), learnerUniqueReference, mobileNumber, endDate.
programmeLevel is optional, even though Getting started may show a leaner body that omits it. learnerUniqueReference is the optional slot for a ULN: it is validated and must be unique within your organisation when supplied, but it is optional, so it is not your primary join (see Core concepts).
For exact types, formats, and validation rules, see the create endpoint in the API reference.
Two things about create cause silent failures if you miss them, so internalise both before you write the loop.
Create does not update; unknown primary tutor is silently skipped; unknown secondary tutor rejects
POST /v1/learners creates; it does not update an existing learner. If primaryTutorEmail does not resolve to a known tutor in Cognassist, the learner is still created but with no primary tutor assigned — there is no validation error for an unknown primary tutor. By contrast, if any email in secondaryTutorEmails does not resolve to a known tutor, the entire create request is rejected (400).
A learner's email is unique within your organisation, so calling create again for the same person is rejected (User is already a Learner), not silently duplicated. Still look the learner up before every create and funnel all enrolment through a single check keyed on your clientReference, so two source systems handle an existing learner cleanly rather than tripping that error.
Because there is no API to list or create tutors, provision them in the Cognassist app (or via your Cognassist contact) up front, then map each learner to a tutor email your source system already knows. If a learner is created with no primary tutor because the email was not found, use PUT /v1/learners/{learnerId} to assign the tutor once their account is provisioned. See Troubleshooting for diagnosing tutor assignment issues.
Batch SFTP as a limited fallback for getting learners in
To get learners in from a file-only source, a one-way-in SFTP upload is a limited fallback. Prefer POST /v1/learners wherever the source system can make an HTTPS call, so you get the learnerId back to react to and the reconcile step to avoid duplicates.
Reconcile against your system¶
Reconciling means: pull the learners Cognassist already holds, match them against your own records, then create only the ones that are missing (and PUT any that changed). The load-bearing detail is which field you match on, and that is decided by what the list returns.
GET /v1/learners returns a paged list with a top-level totalRecords. The list carries only email as a shared human key: it does not return clientReference and offers no server-side filter on it, so the reconcile loop matches on email, not clientReference. For the full list projection and why the match key is email, see Core concepts. clientReference stays your stable primary key on your own side; the point here is only that the API's list cannot be filtered or matched by it today.
When you have matched on email and need to confirm or repair the stored clientReference (an email changed, or you are backfilling a join you never captured), fetch the full record: GET /v1/learners/{learnerId}, or GET /v1/learners/{email} for a known email (which returns 404 on no match). Both include clientReference and learnerUniqueReference. Reserve this per-learner fetch for the rows that actually need it; running it for every row is roughly one call per learner, which is exactly the tight polling to avoid against an unquantified rate limit.
The loop reads totalRecords, pages the list, matches each row on email, then branches: PUT a changed match or POST a missing learner, stopping once it has collected totalRecords.
flowchart TD
A[Read totalRecords<br/>from the first page] --> B[Page GET /v1/learners]
B --> C[Match each row on email]
C --> D{Email in your<br/>source records?}
D -- "Found" --> E[PUT only if the record changed]
D -- "Not found" --> F[POST to create the learner]
E --> G{Collected<br/>totalRecords?}
F --> G
G -- "No" --> B
G -- "Yes" --> H[Done]
Paging and sizing the run
The list is paginated with pageSize, pageIndex, and includeOffProgrammeLearners. Because the response includes totalRecords, the stop condition is knowable without guessing the page count: stop once you have collected totalRecords items, or when a page returns fewer than pageSize rows.
The spec does not state whether pageIndex starts at 0 or 1, so you do not need that value to write the loop: start from your first page, keep requesting until you have collected totalRecords items (or a page returns fewer than pageSize rows), and the stop condition holds on either base. Confirm the base index in the API reference if you want to log or assert absolute page numbers. totalRecords also lets you size the run up front: at a page size of N, expect about totalRecords / N list calls, far fewer than a per-row fetch.
The Power Automate worked example and Keep in sync both build on this loop; it is the canonical picture, so they link here rather than redraw it.
Update and delete¶
PUT /v1/learners/{learnerId} returns 204 No Content. Every field is optional: omit a field to leave it unchanged, supply it to replace the value. Send only what changed, such as a new tutor or a corrected reference.
curl -X PUT "https://api.uk.cognassist.com/v1/learners/00000000-0000-0000-0000-000000000000" \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{ "primaryTutorEmail": "new.tutor@example.com", "endDate": "2030-06-30" }'
DELETE /v1/learners/{learnerId} also returns 204. Reserve it for records created in error; use PUT to move a learner between tutors or programmes.
Tutor assignment governs visibility¶
Assignment is not just an attribute; it decides who in your organisation can see a learner and their results:
- Each learner has one primary tutor plus any number of secondary tutors.
- A tutor sees the learners assigned to them; a tutor manager sees their tutors' learners; an admin sees all learners. Leadership dashboards (Insights) are admin-only.
- Assigning a tutor requires a manager or admin.
- Primary versus secondary is a professional convention (who owns the plan and records evidence), not an app-enforced read-only lock.
Set the primary tutor at creation via primaryTutorEmail, or change it later with PUT. The tutor must already exist in Cognassist (see the create warning above).
Set secondary tutors on create with secondaryTutorEmails, or replace the whole secondary set later with PUT /v1/learners/{learnerId}/secondarytutors. Planned: endpoints to create or list tutor accounts themselves, and Tags and Location as first-class filterable learner fields, are on the roadmap.
Related¶
- Getting started: authenticate and make your first create call.
- Core concepts: join keys and the data model in depth.
- Send the assessment: invite the learner or embed the assessment.
- Keep in sync: webhooks first, polling as the fallback, once a learner is enrolled.
- Power Automate worked example: the reconcile loop as a low-code flow.
- Troubleshooting: diagnosing a rejected create, including the tutor dependency.
- Full request and response schemas: the API reference.