DEVELOPERS · API & WEBHOOKS

Build on ParentLink. Self-serve API, no sales call.

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).

QuickstartWebhooksOpenAPI spec ↓
Read-only & scoped — least privilege per integration Per-token rate limit + each token’s last use shown Webhooks signed with HMAC-SHA256
QUICKSTART

Three steps to your first call

1
Mint a token
In ParentLink, go to Settings → API tokens (manager / admin only), click Mint new token, pick the scopes the integration needs, and copy the pl_… secret. It’s shown once — store it securely.
2
Call the API
Send the token as a bearer header against your project’s API base URL. Every response is scoped to the settings the token belongs to — it can never read anyone else’s data.
3
Subscribe to events
For push instead of poll, add a webhook in Settings → Webhooks, choose the events, and verify the signature on each delivery. Jump to webhooks ↓

Authentication

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.

ENDPOINTS

Five read-only resources

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.

EndpointScopeQuery paramsReturns
GET /childrenchildren:readlimit, offsetActive + archived children: name, DOB, room, start date, allergies, dietary, SEND, funded entitlement.
GET /attendanceattendance:readlimit, offset, from, toSign-in / sign-out sessions per child per day. Filter by date range.
GET /invoicesinvoices:readlimit, offsetPer-child billing roll-up: total due, total paid, outstanding, weeks billed / paid.
GET /rotarota:readlimit, offset, from, toStaff rota rows per ISO week. Filter by week_start range.
GET /waiting-listwaiting-list:readlimit, offsetEnquiry pipeline: child + parent name, email, phone, stage, status.

Sample response

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
}
CONVENTIONS

Pagination, limits & errors

Pagination
?limit= (1–500, default 100) and ?offset= (default 0). Responses include total so you can page through the full set.
Rate limits
Per-token, defaulting to 60 requests/minute (configurable when you mint). Over the limit returns 429 with a Retry-After header. Every call is recorded on our side, and Settings shows when each token was last used.
Tenant scoping
A token belongs to one setting, or to every setting in your group if you choose, when it is minted. Queries are always filtered to those settings — there is no cross-tenant access even with a valid token and the right scope.
Errors
401 missing / invalid / revoked / expired token · 403 token lacks the endpoint’s scope · 404 unknown resource · 429 rate limited. Errors return JSON: { "error": "…" }.
WEBHOOKS

Push, not poll

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.

Events

EventFires whenPayload fields
child.signed_inA child is signed in (any source: kiosk, staff, parent).attendance_id, child_id, date, session, signed_in_at
child.signed_outA child is signed out.attendance_id, child_id, date, session, signed_out_at
enquiry.createdA new waiting-list enquiry is created.enquiry_id, child_name, child_dob, parent_name, parent_email, parent_phone, stage_id, status
incident.loggedAn accident / incident is recorded.incident_id, child_id, record_type, date, severity, riddor_reportable
invoice.paidA weekly fee is paid in full (Direct Debit, card or manual).child_id, week_mon, amount, paid_method, provider_payment_id, paid_at

Delivery shape

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"
  }
}

Verifying the signature

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.

FAQ

Common questions

Is the API read-only?
v1 is read-only (GETs). Writes are higher-risk and deferred — tell us the workflow you need and we'll prioritise it. Webhooks already cover the common 'react to a change' use case without write access.
Who authenticates — the nursery or the parent?
The nursery. Tokens are minted by a manager / admin and scoped to that nursery's own data. This is not a parent-level API — it's for the nursery's own integrations (accounting, payroll, website, internal dashboards).
What can I build?
Your own accounts feed (read invoices into Xero / QuickBooks), payroll (attendance + rota → hours), a 'spaces available' widget on the nursery website, council / local-authority attendance feeds, a kitchen board that reacts to sign-ins with allergies, and multi-site dashboards across nurseries.
How do I rotate a leaked token?
Revoke it in Settings → API tokens (takes effect immediately — the token starts returning 401) and mint a new one. For webhooks, use Rotate secret on the endpoint; the old signing secret stops validating at once.
What are the rate limits?
Per-token, default 60 requests/minute, configurable 1–1000 when you mint. A 429 includes Retry-After. For high-volume sync, prefer webhooks over tight polling.
Do webhooks guarantee delivery?
At-least-once with retry + exponential backoff, then the delivery is marked dead after several attempts. Endpoints that fail many times in a row auto-disable. Watch deliveries (status, code, retry time) in Settings → Webhooks.

Ship your integration this afternoon.

Mint a scoped token, hit five endpoints, subscribe to five events. Part of the Pro plan — no Enterprise upsell, no sales call.

Start free trialOpenAPI spec