Workers & Credentials Tutorial
A complete, copy-paste flow against the REST API: create a worker, attach a credential, verify it, search, and move ownership around. Each step links to its full per-endpoint reference for every field and query parameter.
Read API Basics first — it covers base URLs, the Authorization header, IDs,
the response envelope, and error shapes that every step here relies on. The examples assume
$OHO_BASE and $OHO_TOKEN are set.
1. Create a worker
identity.displayName is the only required field. The request below adds the fields a typical
caller sends; everything else is optional. The server returns 201 with the generated wkr_
ID and a Location header. See Create worker for the full field list.
curl -sS -X POST "$OHO_BASE/workers" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"identity": { "displayName": "Jordan Avery", "firstName": "Jordan", "lastName": "Avery", "birthDate": "1992-03-14" },
"contact": { "workEmail": "jordan.avery@example.com" },
"employment": { "jobTitle": "Support Worker", "workerType": "EMPLOYEE", "status": "ACTIVE" },
"source": { "externalId": "WD-100482", "sourceSystem": "workday" }
}'
{
"data": {
"id": "wkr_V1StGXR8Z5jdHi6B",
"type": "worker",
"attributes": {
"identity": {
"displayName": "Jordan Avery",
"firstName": "Jordan",
"lastName": "Avery",
"birthDate": "1992-03-14"
},
"contact": { "workEmail": "jordan.avery@example.com" },
"employment": {
"jobTitle": "Support Worker",
"workerType": "EMPLOYEE",
"status": "ACTIVE"
},
"source": { "externalId": "WD-100482", "sourceSystem": "workday" }
},
"lastUpdated": "2026-06-29T02:14:07Z"
},
"meta": { "requestId": "f3c1a0e2-..." }
}
Save the ID for the steps that follow:
export WKR="wkr_V1StGXR8Z5jdHi6B"
Omitting identity.displayName returns 400 with { "error": "identity.displayName is required" }.
2. Fetch the worker
curl -sS "$OHO_BASE/workers/$WKR" -H "Authorization: Bearer $OHO_TOKEN"
Returns the same envelope as the create response. Add ?include=credentials to embed a summary
of the worker's credentials (capped at 50) — handy after step 5. See Get worker.
3. List and search workers
# Free-text search, newest first
curl -sS "$OHO_BASE/workers?query=Jordan&sort=lastUpdated:desc" -H "Authorization: Bearer $OHO_TOKEN"
# Look up by your upstream HRIS id
curl -sS "$OHO_BASE/workers?externalId=WD-100482" -H "Authorization: Bearer $OHO_TOKEN"
Filter by recency with updatedAfter (inclusive) and updatedBefore (exclusive); both accept
an ISO-8601 instant or a bare date. Full parameter list: List workers.
4. Update a worker
Use PATCH to change only the fields you send:
curl -sS -X PATCH "$OHO_BASE/workers/$WKR" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "employment": { "jobTitle": "Senior Support Worker" } }'
Use PUT to replace the worker wholesale — fields absent from the body
are cleared, and identity.displayName is required.
5. Create a credential
Required: identification.displayName, .credentialType, .category, .referenceNumber, and
verification.status. On create only, owners must carry at least one of worker or
applicant. The example adds the issuing details a typical caller sends. Full reference:
Create credential.
curl -sS -X POST "$OHO_BASE/credentials" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"identification": { "displayName": "WWCC — NSW", "credentialType": "WWCC_NSW", "category": "SCREENING", "referenceNumber": "WWC1234567E" },
"issuing": { "issuingAuthority": "NSW Office of the Children'\''s Guardian", "jurisdiction": "NSW", "issueDate": "2024-05-01", "expiryDate": "2029-05-01" },
"verification": { "status": "PENDING", "isVerifiable": true },
"owners": { "worker": "'"$WKR"'" }
}'
{
"data": {
"id": "cred_8x2Kf9aQ4mN7pLrT",
"type": "credential",
"attributes": {
"identification": {
"displayName": "WWCC — NSW",
"credentialType": "WWCC_NSW",
"category": "SCREENING",
"referenceNumber": "WWC1234567E"
},
"issuing": {
"issuingAuthority": "NSW Office of the Children's Guardian",
"jurisdiction": "NSW",
"issueDate": "2024-05-01",
"expiryDate": "2029-05-01"
},
"verification": { "status": "PENDING", "isVerifiable": true },
"owners": { "worker": "wkr_V1StGXR8Z5jdHi6B" }
},
"lastUpdated": "2026-06-29T02:20:11Z"
},
"meta": {
"requestId": "...",
"correlationId": "9b6d6c44-2a1e-4f0b-8b7a-3a2f1e0d9c8b"
}
}
export CRED="cred_8x2Kf9aQ4mN7pLrT"
credentialType codes are jurisdiction-specific (WWCC, Blue Card, Ochre Card, teacher
registration, AHPRA, NDIS, and more). Fetch the live list any time from
List supported credential types, and see the
Credentials concept pages for what each one verifies against.
category is one of SCREENING, GOVERNMENT_ID, LICENSE, CERTIFICATION.
- A missing required field →
400, e.g.{ "error": "identification.referenceNumber is required" } ownerswith neither worker nor applicant →400owners.workernot matching thewkr_<id>pattern →400
6. Verify the credential
Runs a registry-backed check and updates the credential's verification fields. The body is
optional; a correlationId you supply is echoed back and into the credential.verified webhook
so you can match the asynchronous delivery to this request. See Verify a credential.
curl -sS -X POST "$OHO_BASE/credentials/$CRED/verify" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "forceRefresh": true }'
{
"eligibility": "MAY_ENGAGE",
"statusDetail": "VALID",
"expiryDate": "2029-05-01",
"errorMessage": null,
"correlationId": "5b1c8b8a-2c3d-4e5f-9a0b-1c2d3e4f5a6b",
"requestId": "..."
}
7. Manual review
For a human decision instead of a registry check, use
approve (sets status=ACTIVE, eligibility=MAY_ENGAGE,
statusDetail=VALID) or reject (sets status=REVOKED,
eligibility=MAY_NOT_ENGAGE). Both take an optional reason/notes body.
curl -sS -X POST "$OHO_BASE/credentials/$CRED/approve" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "reason": "Sighted original certificate" }'
8. List and filter credentials
# All credentials for this worker
curl -sS "$OHO_BASE/credentials?ownerType=worker&ownerId=$WKR" -H "Authorization: Bearer $OHO_TOKEN"
# Filter by type, category, jurisdiction, status, eligibility
curl -sS "$OHO_BASE/credentials?category=SCREENING&jurisdiction=NSW&status=ACTIVE&sort=lastUpdated:desc" \
-H "Authorization: Bearer $OHO_TOKEN"
You can also filter by upstream owner id with ownerExternalId (narrow with ownerSourceSystem
if it could be ambiguous). Full parameter list: List credentials.
9. Ownership: link, unlink, transfer
owners is immutable on PATCH/PUT — attempting it returns 400. Change ownership through
the dedicated endpoints instead. Each owner reference is a type plus exactly one of id
or externalId. A credential holds at most one worker and one applicant slot at a time.
# Link an additional owner to one credential
curl -sS -X POST "$OHO_BASE/credentials/$CRED/link" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "to": { "type": "applicant", "id": "app_Qz7yT2mB" } }'
# Transfer an owner — the canonical applicant -> worker hire flow
curl -sS -X POST "$OHO_BASE/credentials/$CRED/transfer" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "from": { "type": "applicant", "id": "app_Qz7yT2mB" }, "to": { "type": "worker", "id": "'"$WKR"'" } }'
# Unlink an owner (refuses if it would leave the credential with no owners)
curl -sS -X POST "$OHO_BASE/credentials/$CRED/unlink" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "ownerType": "applicant" }'
References: Link · Transfer · Unlink.
Linking a slot that already holds a different id returns 409 with code ALREADY_LINKED — use
transfer to replace it. Transferring a credential not currently owned by from returns 409
with code OWNER_MISMATCH.
Bulk ownership
POST /credentials/link and POST /credentials/transfer (no
{id} in the path) operate on many credentials at once. Omit credentialIds to process every
credential currently linked to from; the response reports a per-item status. Best-effort batch,
capped at 500.
curl -sS -X POST "$OHO_BASE/credentials/transfer" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"from": { "type": "applicant", "externalId": "ATS-55021" },
"to": { "type": "worker", "externalId": "WD-100482", "sourceSystem": "workday" }
}'
{
"data": [
{ "credentialId": "cred_8x2Kf9aQ4mN7pLrT", "status": "transferred" },
{
"credentialId": "cred_bad1",
"status": "skipped",
"error": {
"code": "OWNER_MISMATCH",
"message": "not owned by applicant ..."
}
}
],
"meta": { "total": 2, "succeeded": 1, "failed": 1, "requestId": "..." }
}
10. Compliance summary
Aggregated roll-ups by status, category, jurisdiction, and eligibility — optionally scoped to one owner. See Compliance roll-ups.
curl -sS "$OHO_BASE/credentials/compliance-summary?ownerType=worker&ownerId=$WKR" \
-H "Authorization: Bearer $OHO_TOKEN"
{
"total": 3,
"byStatus": { "ACTIVE": 2, "PENDING": 1 },
"byCategory": { "SCREENING": 2, "LICENSE": 1 },
"byJurisdiction": { "NSW": 1, "VIC": 1, "QLD": 1 },
"byEligibility": { "MAY_ENGAGE": 2, "IN_PROGRESS": 1 },
"requestId": "..."
}
11. Soft delete
curl -sS -X DELETE "$OHO_BASE/credentials/$CRED" -H "Authorization: Bearer $OHO_TOKEN" -i # 204
curl -sS -X DELETE "$OHO_BASE/workers/$WKR" -H "Authorization: Bearer $OHO_TOKEN" -i # 204
Both set deleted: true and preserve the record. Re-query with includeDeleted=true to confirm
they still exist. References: Soft delete credential ·
Soft delete worker.
Where to go next
- API Reference — every endpoint, field, and query parameter
- Credentials concept pages — what each credential type verifies against
- Webhooks — receive
credential.verifiedand other events