Screening an Applicant Tutorial
The applicant-side flow, end to end, against the REST API: build a reusable screening package, create an applicant, send them a recruitment check, track it to completion, then hire the applicant — transferring the credentials they supplied onto a new worker. A final section picks up where the hire leaves off: putting the new worker under ongoing compliance checks.
This is the API counterpart to the Create a Screening Package and Send a Recruitment Check app guides. Each step links to its full per-endpoint reference.
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.
Two new ID shapes show up in this flow, alongside the app_ applicant ID from
API Basics:
| Resource | ID shape | Example |
|---|---|---|
| Screening package | a caller-chosen code | oho0001 |
| Recruitment check | chk_<id> | chk_2bX9pK4mN1qR8sT3 |
Unlike workers and credentials, a screening package's ID is the code you supply on create —
not a server-minted prefix. It must match ^[a-z0-9][a-z0-9_-]{0,63}$ and be unique in your tenant.
1. Build a screening package
A screening package is the bundle of credentials a role requires — you define it once and reuse it
for every applicant you screen for that role. POST /screening-packages with a code, a name,
and a requirements block listing the credential types. See
Create screening package for the full field list.
curl -sS -X POST "$OHO_BASE/screening-packages" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"code": "oho0001",
"name": "Disability Support — Standard",
"description": "Baseline checks for client-facing disability support roles.",
"requirements": {
"credentialTypes": ["WWCC_NSW", "FIRST_AID"]
},
"credentialGuidingTexts": {
"FIRST_AID": "Please upload the certificate, not the wallet card."
},
"active": false
}'
The server returns 201 with a Location header pointing at the new package:
{
"data": {
"id": "oho0001",
"type": "screeningPackage",
"attributes": {
"name": "Disability Support — Standard",
"description": "Baseline checks for client-facing disability support roles.",
"requirements": { "credentialTypes": ["WWCC_NSW", "FIRST_AID"] },
"credentialGuidingTexts": {
"FIRST_AID": "Please upload the certificate, not the wallet card."
},
"active": false,
"audit": { "createdAt": "2026-06-29T02:14:07Z", "createdBy": "..." }
},
"deleted": false
},
"meta": { "requestId": "f3c1a0e2-..." }
}
Save the code for the steps that follow:
export PKG="oho0001"
credentialTypes takes the same codes as the rest of the platform (WWCC, Blue Card, Ochre Card,
teacher registration, AHPRA, NDIS, First Aid, and more). Fetch the live list from
List supported credential types. For more complex packages — AND/OR
logic, nested groups, or per-credential attachmentRequired / skippable settings — the
requirements block also accepts an operator, child groups, and a credentialSettings map;
see Create screening package for the full grammar.
The code is the package's ID, so it must be unique — reusing one returns 409 with code
DUPLICATE_CODE. Build packages role-shaped rather than one giant "everything" package, so
applicants are only asked for what their role actually requires.
active defaults to trueOmit active and the package is created active and immediately usable. This example passes
active: false to stage the package first, so the next step can show the activate call — a useful
pattern when you're assembling a package over several requests.
2. Activate the package
Because we created the package with active: false, it can't be attached to a check until we turn
it on. POST /screening-packages/{code}/activate sets active = true, making it selectable for new
recruitment checks. See Activate a package.
curl -sS -X POST "$OHO_BASE/screening-packages/$PKG/activate" \
-H "Authorization: Bearer $OHO_TOKEN"
Returns 200 with the package now showing "active": true. To edit a package later use
PATCH or PUT; to take it out of circulation use
Deactivate. Browse or search every package with
List packages.
3. Create the applicant
An applicant is the pre-hire identity you screen. POST /applicants mints an opaque app_ ID you
can't supply yourself. Four fields are required: identity.displayName, identity.firstName,
identity.lastName, and contact.emailAddress. Optionally stamp the upstream ATS identifier into
source.externalId. See Create applicant.
curl -sS -X POST "$OHO_BASE/applicants" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"identity": { "displayName": "Riley Chen", "firstName": "Riley", "lastName": "Chen" },
"contact": { "emailAddress": "riley.chen@example.com" },
"source": { "externalId": "ATS-55021" }
}'
The server returns 201 with the generated app_ ID and a Location header:
{
"data": {
"id": "app_8d3c2e91",
"type": "applicant",
"attributes": {
"identity": {
"displayName": "Riley Chen",
"firstName": "Riley",
"lastName": "Chen"
},
"contact": { "emailAddress": "riley.chen@example.com" },
"source": { "externalId": "ATS-55021" }
},
"deleted": false
},
"meta": { "requestId": "..." }
}
export APP="app_8d3c2e91"
Stamp the applicant's upstream identifier into source.externalId. You can then address the
applicant by that external ID in the next step instead of the app_ ID — applicant.externalId,
narrowed with applicant.sourceSystem if it could be ambiguous — handy when your ATS is the system
of record.
4. Send the recruitment check
A recruitment check asks the applicant to supply the credentials a package requires. The
applicant gets an email with a secure upload link; the check tracks their progress to completion.
POST /recruitment-checks ties together the applicant and the package — Oho snapshots the
package's requirements and guiding texts onto the check at send time, so later edits to the package
don't change a check already in flight. See Create recruitment check.
Identify the applicant by exactly one of applicant.id (the app_ ID), applicant.externalId
(+ optional sourceSystem), or an inline applicant.details object. Provide exactly one of
screeningPackageCode or an inline requirements block — name the package with screeningPackageCode.
curl -sS -X POST "$OHO_BASE/recruitment-checks" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"applicant": { "id": "'"$APP"'" },
"screeningPackageCode": "'"$PKG"'",
"message": "Welcome aboard, Riley — please send through the checks below to finish onboarding.",
"expiryDays": 14
}'
The server returns 201 with the generated chk_ ID and a Location header:
{
"data": {
"id": "chk_2bX9pK4mN1qR8sT3",
"type": "recruitmentCheck",
"attributes": {
"displayName": "Recruitment Check — Riley Chen — 2026-06-29",
"applicant": { "id": "app_8d3c2e91" },
"screeningPackageCode": "oho0001",
"message": "Welcome aboard, Riley — please send through the checks below to finish onboarding.",
"requestedCredentialTypes": ["WWCC_NSW", "FIRST_AID"],
"credentialGuidingTexts": {
"FIRST_AID": "Please upload the certificate, not the wallet card."
},
"delivery": {
"emailAddress": "riley.chen@example.com",
"tokenExpiresAt": "2026-07-13T02:14:07Z",
"remindersSent": 0
},
"lifecycle": {
"status": "INITIATED",
"initiatedAt": "2026-06-29T02:14:07Z"
},
"submittedCredentialIds": []
},
"deleted": false
},
"meta": { "requestId": "..." }
}
export CHK="chk_2bX9pK4mN1qR8sT3"
Note requestedCredentialTypes and credentialGuidingTexts come back populated even though you
didn't send them — they're the snapshot taken from the package.
Referencing a missing or inactive screeningPackageCode is rejected with 400. Activate the
package (step 2) before sending checks against it.
A recruitment check screens a pre-hire applicant against a package. Its worker-side analogue
is the fetch request — same secure-upload mechanic, but it asks an existing worker for a
credential and takes a flat requestedCredentialTypes list instead of a package. See the
Fetch Requests tutorial when you need that flow.
5. Track the check
GET /recruitment-checks/{checkId} returns the same envelope, with lifecycle.status reflecting
where the applicant is up to and submittedCredentialIds filling in as they upload. See
Get recruitment check.
curl -sS "$OHO_BASE/recruitment-checks/$CHK" -H "Authorization: Bearer $OHO_TOKEN"
A check moves through the same capture lifecycle as a fetch request:
| Status | Meaning |
|---|---|
INITIATED | Created; email queued |
SENT | Email delivered to the applicant |
OPENED | Applicant opened the upload link |
PARTIALLY_COMPLETED | Some, but not all, requested credentials supplied |
SUBMITTED | Applicant submitted everything requested |
COMPLETED | All requested verifiables have been checked |
EXPIRED | Upload link lapsed before completion |
CANCELLED | Withdrawn by your team |
List and filter checks with GET /recruitment-checks — narrow by status, applicantId,
screeningPackageCode, or free-text query across applicant name and email; page with page and
pageSize. Full parameter list: List recruitment checks.
# Everything still awaiting a response
curl -sS "$OHO_BASE/recruitment-checks?status=SENT" -H "Authorization: Bearer $OHO_TOKEN"
# All checks for this applicant
curl -sS "$OHO_BASE/recruitment-checks?applicantId=$APP" -H "Authorization: Bearer $OHO_TOKEN"
When the applicant goes quiet, nudge them with Remind
(POST /recruitment-checks/{checkId}/remind); re-issue an expired link with
Resend. Rather than poll, you can subscribe to the recruitmentCheck.completed
webhook and react the moment a check finishes.
6. Hire: applicant → worker
Once the check reads COMPLETED, the applicant has a set of verified credentials — listed in
submittedCredentialIds — that you'll want to carry onto a permanent worker record. Hiring is
two moves: create the worker, then transfer the credentials from the applicant to that worker so
the verification history follows them rather than being re-keyed.
First, create the worker (full walkthrough in the Workers & Credentials tutorial):
curl -sS -X POST "$OHO_BASE/workers" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"identity": { "displayName": "Riley Chen", "firstName": "Riley", "lastName": "Chen" },
"contact": { "workEmail": "riley.chen@example.com" },
"employment": { "jobTitle": "Support Worker", "workerType": "EMPLOYEE", "status": "ACTIVE" },
"source": { "externalId": "WD-100482", "sourceSystem": "workday" }
}'
export WKR="wkr_V1StGXR8Z5jdHi6B"
Then bulk-transfer every credential the applicant supplied onto the new worker in one call.
Omitting credentialIds processes all credentials currently owned by the applicant — the
canonical hire move. See Transfer (bulk).
curl -sS -X POST "$OHO_BASE/credentials/transfer" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"from": { "type": "applicant", "id": "'"$APP"'" },
"to": { "type": "worker", "id": "'"$WKR"'" }
}'
{
"data": [
{ "credentialId": "cred_8x2Kf9aQ4mN7pLrT", "status": "transferred" }
],
"meta": { "total": 1, "succeeded": 1, "failed": 0, "requestId": "..." }
}
The applicant's verified WWCC and First Aid now belong to the worker, with their full history
intact. For the single-credential variant, link/unlink semantics, and conflict handling
(ALREADY_LINKED, OWNER_MISMATCH), see
Ownership: link, unlink, transfer.
7. After the hire: ongoing compliance
A recruitment check is a point-in-time gate. Once someone is a worker, you keep them compliant with
compliance checks — rules that evaluate continuously against your workforce and flag anyone who
falls out of line (an expired WWCC, a missing First Aid, and so on). This is the Continuous product,
and it's where evaluate and results live.
Start by fetching the condition catalogue — the fields and operators you can use to author a check's rule tree. Use it to drive form UIs and validate payloads. See Condition catalogue.
curl -sS "$OHO_BASE/compliance-checks/conditions" -H "Authorization: Bearer $OHO_TOKEN"
Author a check with Create compliance check (POST /compliance-checks; like
a package, its id is the caller-chosen code). Then evaluate it. Evaluation is synchronous —
it runs the rule against in-scope workers, persists the result, and returns a fresh summary:
# Re-evaluate one check now
curl -sS -X POST "$OHO_BASE/compliance-checks/$CHECK_ID/evaluate" \
-H "Authorization: Bearer $OHO_TOKEN"
{
"data": {
"checkId": "wwcc-current",
"totalInScope": 128,
"passing": 124,
"failing": 3,
"errors": 1,
"evaluatedAt": "2026-06-29T02:30:00Z",
"failingWorkerUrns": ["urn:li:worker:wkr_...", "urn:li:worker:wkr_..."]
},
"meta": { "requestId": "..." }
}
Read the latest summary any time without re-running, via
Get latest evaluation summary (GET /compliance-checks/{id}/results) — it
returns an empty summary if the check has never run. To sweep many checks in one pass, use
Bulk re-evaluate (POST /compliance-checks/evaluate): with no body it runs
every enabled check, or filter by ids and/or severity (CRITICAL / WARNING / INFO), which
AND together when both are supplied.
# Evaluate just the critical checks
curl -sS -X POST "$OHO_BASE/compliance-checks/evaluate" \
-H "Authorization: Bearer $OHO_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "severity": "CRITICAL" }'
Bulk evaluation returns a data array — one summary per evaluated check.
Where to go next
- API Reference: Screening Packages · Recruitment Checks · Applicants · Compliance Checks
- Workers & Credentials tutorial — the worker side of the hire, in full
- Fetch Requests tutorial — ask an existing worker for a credential
- Webhooks — receive
recruitmentCheck.completedand compliance events