// developers
A REST + GraphQL + webhooks surface for the OpenShft 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.
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.openshft.io/api/v1/participants?limit=50' \
-H 'Authorization: Bearer osk_live_...'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.openshft.io/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.openshft.io/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.
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.openshft.io/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.
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.
/api/v1/participants?limit=50curl -X GET 'https://app.openshft.io/api/v1/participants?limit=50' \
-H 'Authorization: Bearer osk_live_...' \
-H 'Content-Type: application/json'/api/v1/participantscurl -X POST 'https://app.openshft.io/api/v1/participants' \
-H 'Authorization: Bearer osk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"legal_name": "Sam Taylor",
"plan_management_type": "agency",
"status": "onboarding"
}'/api/v1/workers/{worker_id}curl -X PATCH 'https://app.openshft.io/api/v1/workers/{worker_id}' \
-H 'Authorization: Bearer osk_live_...' \
-H 'Content-Type: application/json' \
-d '{
"status": "active"
}'/api/v1/shiftscurl -X POST 'https://app.openshft.io/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"
}'/api/v1/incidentscurl -X POST 'https://app.openshft.io/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
}'/api/v1/claim-linescurl -X POST 'https://app.openshft.io/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
}'/api/v1/progress-notescurl -X POST 'https://app.openshft.io/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
}'/api/v1/form-templatescurl -X POST 'https://app.openshft.io/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"
}
}
}'/api/v1/form-submissionscurl -X POST 'https://app.openshft.io/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."
}
}'/api/v1/form-submissions/{submission_id}/signcurl -X POST 'https://app.openshft.io/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..."
}'/api/v1/form-submissions/{submission_id}/lockcurl -X POST 'https://app.openshft.io/api/v1/form-submissions/{submission_id}/lock' \
-H 'Authorization: Bearer osk_live_...' \
-H 'Content-Type: application/json'Every NDIS form in OpenShft 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.
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.
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.
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.
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.
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.
ESM + CJS builds, typed against the OpenAPI spec. Edge-runtime + Node ≥18 + Bun + Deno.
npm install @openshft/sdk
import { OpenShftClient } from "@openshft/sdk";
const c = new OpenShftClient({
baseUrl: "https://app.openshft.io",
apiKey: process.env.OPENSHFT_API_KEY!,
});
for await (const p of c.participants.iterate()) {
console.log(p.legal_name, p.status);
}Sync + async clients on httpx, Pydantic v2 models. Python ≥ 3.10.
pip install openshft-sdk
from openshft_sdk import OpenShftClient, WorkerInput
with OpenShftClient(
base_url="https://app.openshft.io",
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.
openapi-typescript gives you fully typed fetch wrappers in a few lines.
npx openapi-typescript https://app.openshft.io/api/v1/openapi.json -o ./openshft-api.d.tsopenapi-python-client emits a typed client package.
pipx run openapi-python-client generate \
--url https://app.openshft.io/api/v1/openapi.jsonThe 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.
Subscribe to events from /settings/webhooks. Each delivery is signed via X-OpenShft-Signature (HMAC-SHA256 over the raw body) and carries an X-OpenShft-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.
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"
}Fired when a shift's fields change (PATCH /api/v1/shifts/{id}).
{
"id": "uuid",
"status": "scheduled"
}Fired when a shift is cancelled. Includes SNC eligibility decision.
{
"id": "uuid",
"cancellation_reason": "Participant unwell",
"snc_eligible": true
}Fired when a worker clocks out of a shift.
{
"id": "uuid"
}A participant moved into 'active' or 'onboarding' state.
{
"id": "uuid",
"legal_name": "Sam Taylor",
"status": "active"
}A participant's fields were edited.
{
"id": "uuid",
"status": "active"
}A worker was added to the workspace.
{
"id": "uuid",
"legal_name": "Alex Lee",
"employment_type": "casual",
"status": "applicant"
}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
}An incident was reported (any severity, including non-reportable).
{
"id": "uuid",
"severity": "minor",
"category": "fall",
"reportable": false
}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"
}An incident was updated (PATCH /api/v1/incidents/{id} or web admin).
{
"id": "uuid",
"status": "investigating"
}A claim line was generated.
{
"id": "uuid",
"participant_id": "uuid",
"line_total": 270.24
}A claim line was submitted to PACE in a batch.
{
"id": "uuid",
"claim_reference": "CL-2026-001",
"batch_id": "uuid"
}PACE remittance returned a rejection for this line.
{
"id": "uuid",
"claim_reference": "CL-2026-001",
"rejected_reason": "Plan exhausted"
}An NDIS plan was registered, activated, or suspended.
{
"id": "uuid",
"participant_id": "uuid",
"status": "active",
"action": "activated"
}A progress note was saved against a shift.
{
"id": "uuid",
"shift_id": "uuid",
"flags": {
"incident": false
}
}A new DRAFT form template was created.
{
"id": "uuid",
"code": "CM-FORM-INTAKE",
"audience": "staff",
"version": 1
}A form template transitioned to CURRENT.
{
"id": "uuid",
"code": "CM-FORM-INTAKE",
"version": 1,
"audience": "staff"
}A CURRENT template was superseded (newer version went CURRENT).
{
"id": "uuid",
"code": "CM-FORM-INTAKE",
"version": 1
}A draft form submission was started.
{
"id": "uuid",
"template_id": "uuid",
"template_code": "CM-FORM-INTAKE"
}A draft submission was submitted.
{
"id": "uuid",
"template_id": "uuid"
}A submission was locked (data_json is now immutable).
{
"id": "uuid",
"template_id": "uuid"
}A signature row was captured against a submission.
{
"id": "uuid",
"template_id": "uuid",
"signer_role": "witness"
}Questions? Email api@openshft.io or open an issue on GitHub.