A documented REST API over your nursery’s data — children, attendance, invoices, rota, waiting list — plus outbound webhooks that fire the instant something happens. Scoped bearer tokens, every call recorded, fair rate limits, HMAC-signed events. It’s self-serve on the Pro plan — no sales call — and on the 14-day Pro trial (tokens stop working if the trial ends on Free).
pl_… secret. It’s shown once — store it securely.All requests use a bearer token, sent to ParentLink’s API at the address below (it is also shown in Settings → API tokens).
# Base URL
https://auiqiwcealsoluvwdjok.supabase.co/functions/v1/api-v1
# Authenticated request
curl https://auiqiwcealsoluvwdjok.supabase.co/functions/v1/api-v1/children \
-H "Authorization: Bearer pl_XXXXXXXX..."Hitting the root (/api-v1) with no resource returns a discovery document listing every endpoint and its required scope — handy for exploration.
v1 is read-only by design — the lower-risk surface that covers the integrations nurseries actually ask for (reading invoices into accounts, payroll, council reporting, website “spaces” widgets, CRM dashboards). Each endpoint requires its own scope, so an accounts tool you build only ever sees invoices and a payroll tool only ever sees the rota.
| Endpoint | Scope | Query params | Returns |
|---|---|---|---|
GET /children | children:read | limit, offset | Active + archived children: name, DOB, room, start date, allergies, dietary, SEND, funded entitlement. |
GET /attendance | attendance:read | limit, offset, from, to | Sign-in / sign-out sessions per child per day. Filter by date range. |
GET /invoices | invoices:read | limit, offset | Per-child billing roll-up: total due, total paid, outstanding, weeks billed / paid. |
GET /rota | rota:read | limit, offset, from, to | Staff rota rows per ISO week. Filter by week_start range. |
GET /waiting-list | waiting-list:read | limit, offset | Enquiry pipeline: child + parent name, email, phone, stage, status. |
List endpoints return a consistent envelope with the rows plus pagination metadata.
GET /api-v1/children?limit=2
{
"data": [
{
"id": "c1a2…",
"nursery_id": "n9f8…",
"room_id": "r3…",
"first_name": "Ava",
"last_name": "Walsh",
"dob": "2023-05-20",
"start_date": "2025-03-06",
"active": true,
"allergies": "Eggs",
"dietary": "Egg-free",
"send": false,
"funded_entitlement": { "hours": 15 }
}
],
"total": 27,
"limit": 2,
"offset": 0
}?limit= (1–500, default 100) and ?offset= (default 0). Responses include total so you can page through the full set.429 with a Retry-After header. Every call is recorded on our side, and Settings shows when each token was last used.401 missing / invalid / revoked / expired token · 403 token lacks the endpoint’s scope · 404 unknown resource · 429 rate limited. Errors return JSON: { "error": "…" }.Add an endpoint at Settings → Webhooks, choose the events you care about, and ParentLink POSTs a signed JSON payload the moment each one fires. Delivery is asynchronous with automatic retry and exponential backoff; an endpoint that fails repeatedly is auto-disabled so a dead URL never churns. Re-enable it once the receiver is fixed.
| Event | Fires when | Payload fields |
|---|---|---|
child.signed_in | A child is signed in (any source: kiosk, staff, parent). | attendance_id, child_id, date, session, signed_in_at |
child.signed_out | A child is signed out. | attendance_id, child_id, date, session, signed_out_at |
enquiry.created | A new waiting-list enquiry is created. | enquiry_id, child_name, child_dob, parent_name, parent_email, parent_phone, stage_id, status |
incident.logged | An accident / incident is recorded. | incident_id, child_id, record_type, date, severity, riddor_reportable |
invoice.paid | A weekly fee is paid in full (Direct Debit, card or manual). | child_id, week_mon, amount, paid_method, provider_payment_id, paid_at |
Every delivery is the same envelope. The event-specific fields live under data.
POST (your endpoint URL)
Content-Type: application/json
X-ParentLink-Event: invoice.paid
X-ParentLink-Delivery: 7c1e… # unique per delivery
X-ParentLink-Timestamp: 1759050842 # unix seconds, when this attempt was signed
X-ParentLink-Signature-V2: t=1759050842,sha256=<hex> # see "Verifying" below
X-ParentLink-Signature: sha256=<hex> # older, body only; sent until 1 January 2027
User-Agent: ParentLink-Webhooks/1
{
"id": "7c1e…",
"event": "invoice.paid",
"nursery_id": "n9f8…",
"created_at": "2026-06-07T09:14:02Z",
"data": {
"child_id": "c1a2…",
"week_mon": "2026-06-01",
"amount": 90,
"paid_method": "dd",
"provider_payment_id": "PM0001…",
"paid_at": "2026-06-07T09:14:02Z"
}
}Verify X-ParentLink-Signature-V2: compute HMAC-SHA256(secret, timestamp + "." + rawRequestBody) as lowercase hex, where the timestamp is the t= value, compare it (constant-time) to the sha256= value, and reject a delivery whose timestamp is more than five minutes old. The secret is shown once when you create or rotate the endpoint. Always verify against the raw body bytes — not a re-serialized object.
The older X-ParentLink-Signature header signs the body alone, so a captured delivery could be sent again. It is still sent until 1 January 2027 and then stops: move to V2 before then.
// Node.js (Express) — verify a ParentLink webhook
import express from 'express'
import crypto from 'crypto'
const WEBHOOK_SECRET = process.env.PARENTLINK_WEBHOOK_SECRET // whsec_…
const app = express()
// Capture the RAW body for signature verification.
app.use(express.raw({ type: 'application/json' }))
app.post('/hooks/parentlink', (req, res) => {
// t=<unix seconds>,sha256=<hex>
const v2 = req.header('X-ParentLink-Signature-V2') || ''
const t = (v2.match(/t=(\d+)/) || [])[1] || ''
const sig = (v2.match(/sha256=([0-9a-f]+)/) || [])[1] || ''
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(t + '.')
.update(req.body) // req.body is a Buffer (raw)
.digest('hex')
const fresh = Math.abs(Date.now() / 1000 - Number(t)) <= 300
const ok = fresh && sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
if (!ok) return res.status(401).send('bad signature')
const event = JSON.parse(req.body.toString('utf8'))
// … handle event.event / event.data …
res.sendStatus(200) // 2xx = delivered; anything else triggers retry
})Respond 2xx to acknowledge. Any non-2xx (or a timeout past 10s) is treated as a failure and retried with backoff. Deliveries are at-least-once — dedupe on X-ParentLink-Delivery if your handler isn’t idempotent.
Mint a scoped token, hit five endpoints, subscribe to five events. Part of the Pro plan — no Enterprise upsell, no sales call.