Skip to main content
unicare.ioUnicare home
  • Product
  • Workflow
  • Pricing
  • Developers
  • Contact
Sign inStart free trial
unicare.io

The operating system for NDIS providers. Built in Australia, hosted in Sydney, audit-ready by default.

Product
  • Features
  • Workflow
  • Pricing
  • Roadmap
  • Public API
Company
  • Contact
  • Status
Legal
  • Privacy
  • Terms
  • Data residency
System operationalABN 51 824 753 556
© 2026 Unicare · Built in Australia

// developers

Unicare Developer API

A REST + GraphQL + webhooks surface for the Unicare NDIS provider platform. Integrate scheduling, claims, incidents, and progress notes with your rostering, finance, or BI stack. Available on every paid tier — no Enterprise gate.

Browse API reference →OpenAPI spec (JSON)GraphQL playgroundPostman collectionCreate an API keyGitHubStatusChangelogRoadmap

Authentication

All requests require an osk_live_* Bearer token. Keys are scoped to a single Provider Organisation (workspace). Issue and revoke keys from /settings/api-keys.

curl 'https://app.tryunicare.com/api/v1/participants?limit=50' \
  -H 'Authorization: Bearer osk_live_...'

GraphQL

The GraphQL endpoint at /api/graphql covers the same resources as REST v1 — same Bearer API key, same workspace scoping. Queries are read-only with cursor pagination; mutations cover create*, update*, and delete* for participants, workers, shifts, incidents and progress notes. Open the endpoint in a browser for an interactive playground.

# Query
curl 'https://app.tryunicare.com/api/graphql' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{"query": "{ incidents(reportable: true, limit: 25) { nodes { id severity reportable24hDueAt status } pageInfo { nextCursor } } }"}'

# Mutation (requires a key with the "write" scope)
curl 'https://app.tryunicare.com/api/graphql' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{"query": "mutation($input: ParticipantInput!) { createParticipant(input: $input) { id legalName status } }",
       "variables": {"input": {"legalName": "Sam Reed", "status": "onboarding", "planManagementType": "agency"}}}'

Available types: Participant, Worker, Shift, Incident, ClaimLine, ProgressNote, FormTemplate, FormSubmission, plus a mequery for your API key's scope.

OAuth 2.0

For partner integrations that mint per-request tokens rather than carry a long-lived API key, the /api/v1/oauth/token endpoint implements the client_credentials grant. Register a client at /settings/oauth; the secret is shown once at creation. The returned access_token is a normal Bearer token authenticated through the same path as long-lived keys.

# Mint a short-lived token
curl -X POST 'https://app.tryunicare.com/api/v1/oauth/token' \
  -H 'Content-Type: application/json' \
  -d '{
    "grant_type": "client_credentials",
    "client_id":  "osc_live_...",
    "client_secret": "oss_live_...",
    "scope":      "read"
  }'

# Response
# {
#   "access_token": "osk_live_...",
#   "token_type":   "Bearer",
#   "expires_in":   3600,
#   "scope":        "read"
# }

The endpoint metadata document is at GET /api/v1/oauth/token.

Resources

Every resource supports list (GET), single get (GET /:id), create (POST), update (PATCH /:id), and soft-delete (DELETE /:id). See the OpenAPI spec for the complete field list.

GET

List participants

/api/v1/participants?limit=50
curl -X GET 'https://app.tryunicare.com/api/v1/participants?limit=50' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json'
POST

Create a participant

/api/v1/participants
curl -X POST 'https://app.tryunicare.com/api/v1/participants' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "legal_name": "Sam Taylor",
       "plan_management_type": "agency",
       "status": "onboarding"
     }'
PATCH

Update a worker

/api/v1/workers/{worker_id}
curl -X PATCH 'https://app.tryunicare.com/api/v1/workers/{worker_id}' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "status": "active"
     }'
POST

Schedule a shift

/api/v1/shifts
curl -X POST 'https://app.tryunicare.com/api/v1/shifts' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "worker_id": "00000000-0000-0000-0000-000000000000",
       "participant_id": "00000000-0000-0000-0000-000000000000",
       "scheduled_start": "2026-05-08T09:00:00+10:00",
       "scheduled_end": "2026-05-08T13:00:00+10:00",
       "support_item_code": "01_011_0107_1_1",
       "support_item_name": "Assistance With Self-Care Activities — Standard — Weekday Daytime",
       "rate_category": "weekday_day"
     }'
POST

Report an incident

/api/v1/incidents
curl -X POST 'https://app.tryunicare.com/api/v1/incidents' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "occurred_at": "2026-05-06T14:32:00+10:00",
       "participant_id": "00000000-0000-0000-0000-000000000000",
       "description": "Participant slipped in the bathroom. No injury observed. Bathroom mat replaced and risk assessment refreshed.",
       "severity": "minor",
       "category": "fall",
       "reportable": false
     }'
POST

Create a claim line

/api/v1/claim-lines
curl -X POST 'https://app.tryunicare.com/api/v1/claim-lines' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "participant_id": "00000000-0000-0000-0000-000000000000",
       "service_date": "2026-05-08",
       "support_item_code": "01_011_0107_1_1",
       "support_item_name": "Assistance With Self-Care Activities — Standard — Weekday Daytime",
       "quantity": 4,
       "unit_price": 67.56
     }'
POST

Save a progress note

/api/v1/progress-notes
curl -X POST 'https://app.tryunicare.com/api/v1/progress-notes' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "shift_id": "00000000-0000-0000-0000-000000000000",
       "body": "Visited the participant for their morning routine. Showered with prompts, prepared breakfast, took medication as prescribed. No incidents.",
       "flags": {
         "incident": false,
         "fall": false,
         "medication_issue": false,
         "boc": false,
         "restrictive_practice": false
       },
       "ai_assisted": false
     }'
POST

Create a form template (DRAFT)

/api/v1/form-templates
curl -X POST 'https://app.tryunicare.com/api/v1/form-templates' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "code": "CM-FORM-INTAKE",
       "name": "Participant intake interview",
       "category": "intake",
       "audience": "staff",
       "is_easy_read": false,
       "practice_standards": [
         "1.1",
         "1.2"
       ],
       "schema_json": {
         "type": "object",
         "required": [
           "full_name"
         ],
         "properties": {
           "full_name": {
             "type": "string",
             "title": "Participant full name"
           },
           "communication_preferences": {
             "type": "string",
             "title": "Communication preferences"
           }
         }
       },
       "ui_json": {
         "ui:order": [
           "full_name",
           "communication_preferences"
         ],
         "communication_preferences": {
           "ui:widget": "textarea"
         }
       }
     }'
POST

Submit a form

/api/v1/form-submissions
curl -X POST 'https://app.tryunicare.com/api/v1/form-submissions' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "template_id": "00000000-0000-0000-0000-000000000000",
       "participant_id": "00000000-0000-0000-0000-000000000000",
       "data": {
         "full_name": "Sam Taylor",
         "communication_preferences": "Prefers SMS over phone calls."
       }
     }'
POST

Sign a submission

/api/v1/form-submissions/{submission_id}/sign
curl -X POST 'https://app.tryunicare.com/api/v1/form-submissions/{submission_id}/sign' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json' \
  -d '{
       "signer_role": "submitter",
       "signer_name": "Alex Lee",
       "signature_data": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."
     }'
POST

Lock a submission

/api/v1/form-submissions/{submission_id}/lock
curl -X POST 'https://app.tryunicare.com/api/v1/form-submissions/{submission_id}/lock' \
  -H 'Authorization: Bearer osk_live_...' \
  -H 'Content-Type: application/json'

Form Engine

Every NDIS form in Unicare is a row in form_templates rendered by a single React engine (ADR-008 — the wedge architecture). Build new forms via the API and instances will appear in the customer's admin UI, worker app, and participant app simultaneously.

1. Create a template

POST /form-templates with a JSON Schema 2020-12 fragment + an RJSF-style UI hint hash. The template starts in DRAFT; advance through APPROVED → CURRENT via the admin UI to begin accepting submissions.

2. Submit data

POST /form-submissions with the template_id and a data object whose keys match schema_json.properties. Submissions begin as draft and can be PATCHed until lock.

3. Sign & lock

POST /form-submissions/{id}/sign with a base64 PNG signature dataURL and signer role; idempotent on the (submission, signer_user_id, signer_role) tuple. Then POST /form-submissions/{id}/lock to freeze data_json — only soft-delete is permitted afterwards.

4. Easy-Read pairing

Participant-audience templates require an Easy-Read sibling under ADR-012 before they can transition to APPROVED or CURRENT. Create the sibling with is_easy_read: true, then call the form_pair_easy_read_siblingRPC (or use the “Pair Easy-Read” button in the admin UI). The renderer surfaces a toggle so participants can opt into the simpler form.

Official SDKs

First-party clients for TypeScript and Python. Both ship full CRUD on the eight v1 resources, automatic OAuth token caching, cursor pagination iterators, and typed error mapping.

@unicare/sdk (TypeScript)

ESM + CJS builds, typed against the OpenAPI spec. Edge-runtime + Node ≥18 + Bun + Deno.

npm install @unicare/sdk

import { UnicareClient } from "@unicare/sdk";

const c = new UnicareClient({
  baseUrl: "https://app.tryunicare.com",
  apiKey: process.env.UNICARE_API_KEY!,
});

for await (const p of c.participants.iterate()) {
  console.log(p.legal_name, p.status);
}

unicare-sdk (Python)

Sync + async clients on httpx, Pydantic v2 models. Python ≥ 3.10.

pip install unicare-sdk

from unicare_sdk import UnicareClient, WorkerInput

with UnicareClient(
    base_url="https://app.tryunicare.com",
    api_key="osk_live_...",
) as c:
    w = c.workers.create(WorkerInput(
        legal_name="Sam Reed",
        employment_type="casual",
    ))
    for s in c.shifts.iterate():
        ...

Need a language we don't ship? The OpenAPI spec generates a typed client with openapi-typescript, openapi-python-client, Kiota, or any compatible generator.

TypeScript

openapi-typescript gives you fully typed fetch wrappers in a few lines.

npx openapi-typescript https://app.tryunicare.com/api/v1/openapi.json -o ./unicare-api.d.ts

Python

openapi-python-client emits a typed client package.

pipx run openapi-python-client generate \
  --url https://app.tryunicare.com/api/v1/openapi.json

Postman

The collection at /api/v1/postman.json is generated from the OpenAPI spec on the fly. Set the api_key and base_url variables once and every request authorises automatically.

Webhooks

Subscribe to events from /settings/webhooks. Each delivery is signed via X-Unicare-Signature (HMAC-SHA256 over the raw body) and carries an X-Unicare-Event-Id idempotency key. Subscribers must respond with a 2XX within 10 seconds; failures retry up to 3 times and the subscription auto-deactivates after 10 consecutive failures.

shift.created

Fired when a shift is scheduled (web admin or POST /api/v1/shifts).

{
  "id": "uuid",
  "worker_id": "uuid",
  "participant_id": "uuid",
  "scheduled_start": "ISO",
  "scheduled_end": "ISO"
}
shift.updated

Fired when a shift's fields change (PATCH /api/v1/shifts/{id}).

{
  "id": "uuid",
  "status": "scheduled"
}
shift.cancelled

Fired when a shift is cancelled. Includes SNC eligibility decision.

{
  "id": "uuid",
  "cancellation_reason": "Participant unwell",
  "snc_eligible": true
}
shift.completed

Fired when a worker clocks out of a shift.

{
  "id": "uuid"
}
participant.onboarded

A participant moved into 'active' or 'onboarding' state.

{
  "id": "uuid",
  "legal_name": "Sam Taylor",
  "status": "active"
}
participant.updated

A participant's fields were edited.

{
  "id": "uuid",
  "status": "active"
}
worker.created

A worker was added to the workspace.

{
  "id": "uuid",
  "legal_name": "Alex Lee",
  "employment_type": "casual",
  "status": "applicant"
}
worker.deleted

A worker was soft-deleted from the workspace (deleted_at set).

{
  "id": "uuid",
  "deleted": true
}
worker.screening.expiring

A worker's NDIS Worker Screening, WWCC, First Aid, or CPR expires within 30 days. Emitted hourly per worker.

{
  "id": "uuid",
  "ndis_screening_expires_at": "2026-06-01",
  "wwcc_expires_at": null,
  "horizon_days": 30
}
incident.created

An incident was reported (any severity, including non-reportable).

{
  "id": "uuid",
  "severity": "minor",
  "category": "fall",
  "reportable": false
}
incident.classified_reportable

An incident was classified as a Reportable Incident under the NDIS Act.

{
  "id": "uuid",
  "reportable_type": "serious_injury",
  "reportable_24h_due_at": "ISO",
  "severity": "major"
}
incident.updated

An incident was updated (PATCH /api/v1/incidents/{id} or web admin).

{
  "id": "uuid",
  "status": "investigating"
}
claim_line.created

A claim line was generated.

{
  "id": "uuid",
  "participant_id": "uuid",
  "line_total": 270.24
}
claim_line.submitted

A claim line was submitted to PACE in a batch.

{
  "id": "uuid",
  "claim_reference": "CL-2026-001",
  "batch_id": "uuid"
}
claim_line.rejected

PACE remittance returned a rejection for this line.

{
  "id": "uuid",
  "claim_reference": "CL-2026-001",
  "rejected_reason": "Plan exhausted"
}
plan.updated

An NDIS plan was registered, activated, or suspended.

{
  "id": "uuid",
  "participant_id": "uuid",
  "status": "active",
  "action": "activated"
}
progress_note.created

A progress note was saved against a shift.

{
  "id": "uuid",
  "shift_id": "uuid",
  "flags": {
    "incident": false
  }
}
form_template.created

A new DRAFT form template was created.

{
  "id": "uuid",
  "code": "CM-FORM-INTAKE",
  "audience": "staff",
  "version": 1
}
form_template.published

A form template transitioned to CURRENT.

{
  "id": "uuid",
  "code": "CM-FORM-INTAKE",
  "version": 1,
  "audience": "staff"
}
form_template.superseded

A CURRENT template was superseded (newer version went CURRENT).

{
  "id": "uuid",
  "code": "CM-FORM-INTAKE",
  "version": 1
}
form_submission.created

A draft form submission was started.

{
  "id": "uuid",
  "template_id": "uuid",
  "template_code": "CM-FORM-INTAKE"
}
form_submission.submitted

A draft submission was submitted.

{
  "id": "uuid",
  "template_id": "uuid"
}
form_submission.locked

A submission was locked (data_json is now immutable).

{
  "id": "uuid",
  "template_id": "uuid"
}
form_submission.signed

A signature row was captured against a submission.

{
  "id": "uuid",
  "template_id": "uuid",
  "signer_role": "witness"
}

Questions? Email api@tryunicare.com or open an issue on GitHub.