Scolah Partner API

Send what you know about a ward to their family

Clinics, health and device makers, and school systems can send appointments, measurements and timeline entries straight into a family's Scolah. A ward is the person a family looks after in Scolah: a child, or an older parent. A family member links you to one ward at a time, and can unlink you whenever they choose.

Write-onlyYou send data. You can never read a ward's record.
One ward per linkEvery link starts with a family member's one-time code.
Revocable at onceWhen a family member unlinks you, your tokens stop working immediately.
Standard OAuth 2.0Client credentials, bearer tokens and refresh tokens.

What you can send

Each partner is approved for one or more scopes. A link to a ward carries the scopes you were approved for, and every request is checked against them.

ScopeEndpointWhat the family sees
appointments:writePOST /appointmentsA read-only event in the calendar of everyone in the family, with a reminder an hour before.
measurements:writePOST /measurementsWeight, height or temperature on the ward's timeline and health charts.
activities:writePOST /activitiesA timeline entry of any kind: care, health, learning, play, a milestone, an observation.

The family gets one notification per batch you send, labelled with your name. School staff never see what partners send; it stays with the family.

How linking works

A family member decides which ward you can send data for. There is no way to link to a ward without them.

  1. A family member makes a code in ScolahIn the app, under Connected apps, they pick your organisation and the ward. Scolah shows a code like K7QM-3XPD. It works once, for you only, for 10 minutes.
  2. They give you the codeRead out at reception, typed into your app, or entered on your website: whatever suits your service.
  3. You swap it for tokensYour server sends the code with your client credentials to the token endpoint and gets an access token and a refresh token for that one ward.
  4. You send dataUse the access token on the data endpoints. Refresh it when it expires, after an hour.
  5. The family can unlink youFrom the same screen, at any time. Your next request gets 401 invalid_token; stop sending and delete the tokens for that link.

Linking the same ward again replaces the earlier link, so you only ever hold one live link per ward. The token response tells you its link_id; keep it alongside your own patient or customer record.

Quick start

All endpoints live under one base URL. We give you test credentials for our test environment first, and live credentials once your integration works.

EnvironmentBase URL
Livehttps://api.scolah.com/api/v1/core/partner-api
Testhttps://api.uat.scolah.com/api/v1/core/partner-api

1. Swap the family member's code for tokens

curl -X POST https://api.scolah.com/api/v1/core/partner-api/oauth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=link_code \
  -d code=K7QM-3XPD
{
  "access_token": "Hk3v…Q9wA",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "pX7d…2mLc",
  "scope": "appointments:write measurements:write",
  "link_id": "5b0f6a7e-1c2d-4e3f-9a8b-7c6d5e4f3a2b"
}

2. Send a measurement

curl -X POST https://api.scolah.com/api/v1/core/partner-api/measurements \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "items": [
      { "externalId": "visit-8812-weight", "type": "WEIGHT",
        "value": 14.2, "unit": "kg", "measuredAt": "2026-10-13T09:40:00+00:00" }
    ]
  }'
{
  "accepted": 1,
  "rejected": 0,
  "results": [ { "externalId": "visit-8812-weight", "status": "ACCEPTED", "error": null } ]
}

The weight appears on the ward's timeline and health chart within seconds.

Tokens

POST/oauth/token

A standard OAuth 2.0 token endpoint. Send the body as application/x-www-form-urlencoded and your client credentials by HTTP Basic. If your client cannot send Basic auth, put client_id and client_secret in the form instead.

grant_typeAlso sendUse it to
link_codecodeTurn a family member's code into a new link and its first tokens.
refresh_tokenrefresh_tokenGet a new access token for a link you already hold.
  • Access tokens last one hour. Send them as Authorization: Bearer ….
  • Refresh tokens rotate: every refresh returns a new access token and a new refresh token, and both old ones stop working at once. Store the new refresh token before you use the access token.
  • Each link has one live token pair. If several workers send for the same link, share one token store and refresh in one place.
  • Tokens are opaque strings. Don't parse them, and keep them server-side, as you would a password.
  • Your client secret is shown to you once. If it leaks, email partners@scolah.com at once: we suspend the credentials, which stops every token issued with them.
GET/link

Tells you which link a token belongs to and what it allows. Useful as a health check. It never returns anything about the ward.

{ "linkId": "5b0f6a7e-…", "partnerId": "0e9d…", "scope": "appointments:write measurements:write" }

Appointments

POST/appointments appointments:write

Up to 100 appointments per request. Each one becomes a read-only event for everyone in the ward's family, with a reminder an hour before. Send the same externalId again to change an appointment, or with "status": "CANCELLED" to remove it.

FieldRules
externalIdRequired. Your own id for the appointment, up to 255 characters.
titleRequired unless cancelling. Shown to the family as is, so write it for them: "Vaccination at Ridge Clinic". Longer than 100 characters is shortened with "…".
startAtRequired unless cancelling. ISO 8601 with an offset. From 30 days ago to 2 years ahead.
endAtOptional. After startAt.
timeZoneOptional. An IANA zone such as Africa/Accra.
location, notesOptional. Shown on the event; the location opens in maps. Shortened after 255 and 2,000 characters.
statusCONFIRMED (the default) or CANCELLED.
{
  "items": [
    { "externalId": "appt-20931", "title": "Vaccination at Ridge Clinic",
      "startAt": "2026-11-04T10:30:00+00:00", "endAt": "2026-11-04T11:00:00+00:00",
      "timeZone": "Africa/Accra", "location": "Ridge Clinic, Accra" }
  ]
}

Each item's status is CREATED, UPDATED, CANCELLED or REJECTED.

Measurements

POST/measurements measurements:write

Up to 100 readings per request. Scolah converts units, checks that each value is plausible for a ward, and adds it to the timeline and health charts.

typeUnitsAccepted range
WEIGHTkg (default), g, lb0.3 to 200 kg
HEIGHTcm (default), m, in20 to 250 cm
TEMPERATUREC (default), F30 to 45 °C

Every reading needs an externalId, a type, a value and a measuredAt from up to a year ago to no more than a day ahead. Sending the same externalId again corrects the reading instead of adding a second one.

Timeline entries

POST/activities activities:write

Up to 100 entries per request, for anything else worth a place on the ward's timeline: a check-up summary, a therapy session, a reading level, a first step.

FieldRules
externalIdRequired, up to 255 characters. The same id updates the entry.
typeRequired. CARE, HEALTH, LEARNING, PLAY, MILESTONE, OBSERVATION or CUSTOM.
subtypeOptional. Lower-case letters, digits and underscores, such as doctor_visit.
titleRequired, up to 200 characters.
descriptionOptional, up to 5,000 characters.
occurredAtRequired. ISO 8601 with an offset, from up to a year ago to no more than a day ahead.
dataOptional. Extra details as a JSON object, up to 16 KB.
{
  "items": [
    { "externalId": "session-4471", "type": "HEALTH", "subtype": "speech_therapy",
      "title": "Speech therapy session",
      "description": "Worked on 's' and 'sh' sounds. Practise the picture cards at home.",
      "occurredAt": "2026-10-12T15:00:00+00:00", "data": { "durationMinutes": 45 } }
  ]
}

Responses

The data endpoints check each item on its own. One bad item never fails the batch: you get 200 with a result per item, in the order you sent them.

{
  "accepted": 2,
  "rejected": 1,
  "results": [
    { "externalId": "r-1", "status": "ACCEPTED", "error": null },
    { "externalId": "r-2", "status": "ACCEPTED", "error": null },
    { "externalId": "r-3", "status": "REJECTED",
      "error": "value is out of the plausible range for a ward" }
  ]
}

A rejected item is not stored. Fix it and send it again with the same externalId; resending accepted items is safe and never creates duplicates.

Errors

Token and authorisation errors use the OAuth 2.0 shape, so most client libraries read them as they are: {"error": "…", "error_description": "…"}. A malformed request gets a validation body instead: {"title": "Validation Failed", "status": 400, "errors": {…}}.

StatuserrorWhat to do
400invalid_grantThe code is wrong, used or expired, or the refresh token was revoked or already used. Ask the family member for a new code.
400unsupported_grant_typeUse link_code or refresh_token.
400The request itself is malformed, for example an empty list or more than 100 items.
401invalid_clientCheck your client id and secret.
401invalid_tokenThe access token expired: refresh it. If refreshing fails with invalid_grant, the family unlinked you: stop sending for that ward and delete its tokens.
403insufficient_scopeYour link doesn't allow this kind of data. The needed scope is in the WWW-Authenticate header.
429too_many_requestsOver a rate limit: 30 token requests a minute per client id, 120 data requests a minute per access token. Wait for the Retry-After seconds.
503temporarily_unavailableScolah could not take the batch just now. Send the whole batch again after Retry-After seconds (30 if there is none); items that did get through are updated, not duplicated.

Good practice

  • Batch your data. Send up to 100 items per request rather than one at a time.
  • Use stable ids. Your externalId is how Scolah recognises an item. Never reuse one for something else. Ids are kept per ward, so the same id for two wards makes two separate items.
  • Retry safely. On a network error or a 503, send the same batch again. Accepted items are updated in place, not duplicated.
  • Write for families. Titles and descriptions appear in the family's app as you send them. Avoid internal codes and jargon.
  • Respect unlinking. A revoked link is final. Don't ask the family to relink you just to keep sending.
  • Send only what the family should keep. Everything you send becomes part of the ward's record, which their family can see, download and delete.

Scolah handles data about children, older adults and other wards. Partners must follow the Scolah terms and privacy policy, and the data protection law that applies to them.

Request access

We review every partner before issuing credentials. Tell us who you are and what you'd like to send, and we'll reply with test credentials or any questions, usually within a few working days.

What would you send?

This opens an email to partners@scolah.com with your answers filled in.