Skip to main content

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.

Before you start

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:

ResourceID shapeExample
Screening packagea caller-chosen codeoho0001
Recruitment checkchk_<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"
Which credentialType?

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.

note

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 true

Omit 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"
Already in your ATS?

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.

Package must be active

Referencing a missing or inactive screeningPackageCode is rejected with 400. Activate the package (step 2) before sending checks against it.

Recruitment check vs fetch request

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:

StatusMeaning
INITIATEDCreated; email queued
SENTEmail delivered to the applicant
OPENEDApplicant opened the upload link
PARTIALLY_COMPLETEDSome, but not all, requested credentials supplied
SUBMITTEDApplicant submitted everything requested
COMPLETEDAll requested verifiables have been checked
EXPIREDUpload link lapsed before completion
CANCELLEDWithdrawn 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