Workers
A worker is a person record in your organisation — a staff member, volunteer, or contractor whose compliance you track. Workers hold the person's identifying details and link to every credential, Recruitment Check, ban, and check that's ever been run for them.
A person whose journey started via a Recruitment Check (and hasn't been formally onboarded yet) is called an applicant instead. The two are closely related — see Applicants vs. workers below.
What a worker represents
One person. Examples:
- A nurse you employ, with an active AHPRA registration and a current WWC.
- A teacher at your school, with a registration plus a police check.
- A contractor with NDIS worker screening and right-to-work documentation.
A worker always belongs to exactly one organisation, chosen when the record is created and not editable afterwards — see Moving a worker to another organisation for what to do when someone transfers. If two organisations engage the same physical person, each keeps its own worker record — same human, different records. Records are never shared across organisations. And because every customer runs on its own single-tenant Oho deployment, reached at its own <tenant>.weareoho.com subdomain, records are never shared across tenants either.
Applicants vs. workers
The platform separates people you're considering from people you've onboarded:
| Applicant | Worker | |
|---|---|---|
| When created | Automatically by a Recruitment Check | Imported from your HRIS, created directly, or created when an applicant is hired |
| Lifecycle stage | Onboarding / pre-employment | Active / employed |
| Public ID | app_<id> | wkr_<id> |
| Counts toward active headcount | No | Yes |
| Eligible for renewal flows | No (still being onboarded) | Yes |
Hiring an applicant isn't a single rename — you create a worker record and move the applicant's credentials onto it (a credential ownership transfer; see Credentials). The applicant record is retired and the worker carries on with the same verified history. Most of this guide talks about workers; everything carries over to applicants unless noted.
Core fields
Worker attributes are grouped into nested objects — identity, contact, employment, and source — rather than a flat list. The fields you'll use most:
| Field | Required | Notes |
|---|---|---|
identity.displayName | ✓ | The only field required on create. How the person is shown across Oho. |
identity.firstName, identity.lastName | — | Used for matching during credential verification. A person with one legal name puts it in lastName — see People with one name. |
contact.workEmail | — | The destination for Recruitment Check emails. (contact.personalEmail is also available.) |
contact.workPhone / contact.personalPhone | — | Recommended for SMS-based flows and identity verification. |
identity.birthDate | conditional | ISO-8601 date (YYYY-MM-DD). Required for some credential types (NSW WWC, SA WWC, NDIS Worker Screening, etc.). |
source.externalId | — | Strongly recommended. Your internal employee/contractor ID. Becomes the join key with your HRIS. Queryable via GET /workers?externalId=.... |
employment.status | — | HR lifecycle status, e.g. ACTIVE, INACTIVE, ON_LEAVE, TERMINATED. Recorded and filterable; it does not start or stop checking. |
isActive | — | Top level, not inside employment. Oho's monitoring switch. false pauses scheduled verification. Defaults to true. |
employment.manager | — | The worker's direct manager (another worker), set by id or externalId. Drives the manager copy of credential expiry notifications. |
id | auto | Synthetic wkr_ identifier assigned by Oho on creation. The internal URN is never exposed. |
If you don't have a birth date at create time, submit without it — you can update later, and Oho can also auto-enrich the worker's identity.birthDate from data the person provides during a Recruitment Check or a successful registry check.
The required set above is the API minimum — a POST /workers needs only identity.displayName. The app's Add Worker wizard asks for a little more: Legal Last Name and Organisation. (Legal First Name is optional there, so a person with one legal name can be recorded as they are.) That's a deliberate UI guardrail to capture clean data up front, not an API constraint. Date of Birth and Work Email are prompted for but not enforced there either — the wizard only validates them when you fill them in (it won't accept a date of birth less than 12 years ago, and a work email must be unique). The 12-year floor is the wizard's alone — the API applies no age floor at all, so a POST /workers (and a spreadsheet import, which takes the same path) accepts any valid ISO-8601 date. If you're following the Add a Worker guide in the app, use its required set; if you're integrating directly against the API, displayName alone will create the record.
People with one name
Not everyone has a given name and a family name. Record a person who holds one legal name by
putting that name in Legal Last Name (identity.lastName) and leaving Legal First Name
blank. The app says as much under the field — "Leave blank if this worker goes by a single name" —
and the wizard saves without it.
The last-name field is the one to use because it is what the registers are searched on. VEVO, the VIC and WA Working With Children registers and the QLD Blue Card portal all match on the family name; several of them take nothing else but a card number.
A -, a ., N/A or a repeat of the given name is submitted to the register literally, so the
check errors or comes back not found, and it goes on failing on every re-check. One worker loaded
with the surname - had her VEVO passport check fail for exactly this reason, while the credential
that happened to hold her real single name as its surname verified first time.
If a worker already carries a placeholder, edit the worker, move the real name into Legal Last Name, clear Legal First Name, and then correct the same fields on each of their credentials.
Credentials carry their own holder name — the name as printed on the card — and that, not the worker record, is what a check is run under. So set the credential's Last name / Surname to the single name as well.
A few credential forms still require a given name: AHPRA, QLD Blue Card, NT Ochre Card, NSW teacher registration and NDIS Worker Screening. If the holder has only one name, email support@weareoho.com rather than inventing a given name — an invented name is what the register would then be searched under, which is the problem this section exists to avoid. SA Working With Children already accepts a blank first name.
A worker with no surname on record at all is skipped by some checks outright, so the one field that must never be empty is the last name.
Active vs. inactive
A worker carries two independent states, and they answer different questions:
| Field | Question it answers | Where it comes from | What it changes |
|---|---|---|---|
employment.status | Does this person still work here? | Your HR system, a spreadsheet import, or an edit | Nothing on its own. It is recorded, shown on the profile, and available as a Status filter. |
isActive (top level) | Is Oho still checking this person? | An admin, in Oho or over the API | Everything. false takes every credential they hold out of scheduled verification and out of compliance triage. |
employment.status does not stop Oho checking anyone. A worker whose HR record says
TERMINATED but whose isActive is still true goes on being scanned against the registers, goes
on raising expiry alerts, and goes on counting in the compliance dashboards. Setting the status is
how you record the fact; setting isActive is how you act on it.
The reverse is just as true: isActive: false on its own doesn't record that anyone left. Most
offboarding sets both — the status for your records, the switch for Oho.
This is about writing the fields directly. A connector maps your HR
system's own status values to Oho's, and derives the monitoring switch from that mapping, so a
leaver your HR feed marks terminated does stop being checked. A
spreadsheet import works the same way — its Status column sets
both. It's the direct API write that has to set isActive itself.
isActive is not inside employmentIt sits at the top level of the worker body, alongside identity, employment and source.
Send it anywhere else — nested under employment, or inside the attributes wrapper that only
appears in responses — and it is ignored, with no error. The request succeeds, the status
changes, and the worker carries on being scanned.
{
"employment": { "status": "INACTIVE", "endDate": "2026-10-09" },
"isActive": false
}
See Offboard a worker for the full
offboarding path, in the app and over the API, including why a PUT that omits isActive quietly
switches scanning back on.
Relationships
Organisation
└── Worker (one person)
├── Credentials (their checks, licences, clearances)
├── Recruitment Checks (information requests sent to them)
├── Bans (any disqualifying register entries)
└── Checks history (police checks, etc.)
- Worker → Organisation (N:1) — every worker belongs to one organisation. There's no way to move one in the app; see Moving a worker to another organisation.
- Worker → Credentials (1:N) — a person can hold many credentials, in any combination, with their own statuses and expiries.
- Worker → Recruitment Checks (1:N) — a person can have any number of Recruitment Checks sent to them over time. Each one is a distinct event.
- Worker → Bans (0:N) — bans are surfaced as flags but stored separately so they preserve their public-register provenance.
Lifecycle
Onboard
│ Create applicant via Check ─┐
│ OR ├── First compliance picture
│ Create worker directly ─┘
▼
Active
│ Receive webhooks as credential statuses change
│ Periodically re-check or send renewal Recruitment Checks
▼
Offboard
Pause monitoring (isActive = false) — stops the checking, history preserved
AND/OR record the HR fact (employment.status = INACTIVE)
OR Delete — only for genuine errors (e.g. wrong person created)
Common operations
These are the conceptual operations — the exact REST/GraphQL syntax is in the API Reference.
Create
Create a worker before doing anything else with them. Even minimal details (name + email) are enough — you can enrich later.
create_worker(identity.displayName, [identity.firstName], [identity.lastName],
[contact.workEmail], [source.externalId], [identity.birthDate])
→ returns Worker with synthetic wkr_ id
Only identity.displayName is required; everything else can be filled in later. To avoid duplicates, look the person up first by their HRIS identifier (GET /workers?externalId=...) before creating a new record — source.externalId is the strongest match key, with contact.workEmail (unique within your organisation) as a fallback.
Read
Fetch a worker by their public wkr_ id. The response includes their fields plus links to their credentials, Recruitment Checks, and any bans.
Update
Patch any of the editable fields. Sensitive updates (DOB after creation) require admin role.
Deactivate
Patch top-level isActive to false. This is the one that stops the checking, and it's what you
want on offboarding. Patch employment.status alongside it if you also want the HR lifecycle
recorded — on its own it changes nothing. Use PATCH, not PUT: a replace that omits isActive
resets it to true.
Delete
Hard-delete a worker. Reserved for genuine errors. Removes the person record but preserves audit log entries. Prefer deactivation in almost every case.
Linking to your HR system
Every customer eventually needs to join Oho's worker records back to their HRIS. Two reliable joins exist:
| Field | Strength | Notes |
|---|---|---|
source.externalId | Strongest | Your authoritative employee ID. Set at create time and never reuse it. |
contact.workEmail | Strong | Unique within an organisation. Watch for email changes when staff change surname. |
Avoid relying on first/last name combinations — they're not unique and they change.
Auto-enrichment
When the platform receives new identifying data about a worker (from a successful registry check, from a Recruitment Check submission, from a state-government webhook), it can update the worker record. Examples:
- A WWC verification returns a different DOB than you supplied → the worker's DOB is corrected.
- A Recruitment Check submission supplies a phone number you didn't have → it's added.
Enrichment is non-destructive by default: missing fields are filled in, existing fields aren't overwritten unless the new value comes from a higher-trust source (e.g. a government registry overrides a self-asserted value).
Enrichment is auditable — every change is recorded.
Validation rules
- Email uniqueness —
contact.workEmailis unique within your organisation only. The same email can exist in another organisation. - Birth date format —
identity.birthDateis an ISO-8601 date (YYYY-MM-DD). Required for state checks that match on date of birth. - External ID —
source.externalIdis an opaque string, your choice. Recommended to be globally unique within your HR system.
Privacy & access
Worker records store personally identifiable information (PII). Your responsibilities under the Australian Privacy Principles (and any other applicable law) apply.
- Only users assigned to a worker's organisation can see that worker — access is scoped by organisation for every role except Super Admin. Separately, single-tenant isolation walls your whole deployment off from every other customer's.
- Roles (Super Admin / Admin / Manager / Observer) further restrict who can read PII, edit it, or delete records. See Roles & access.
- Subject-access requests are served by
GETon the worker. - Correction requests are served by
PATCH. - Right-to-erasure requests can be served by deactivation; full deletion may have audit implications — check with
support@weareoho.combefore hard-deleting for compliance reasons.
Best practice
- Create the record first. Always create the worker (or let a Recruitment Check create the applicant) before submitting their first credential. It costs you nothing and gives you a clean history.
- Always set
source.externalId. Future-you, debugging a webhook 18 months from now, will thank you. - Deactivate, don't delete. Set
isActivetofalse— andemployment.statustoINACTIVEas well if you want the lifecycle on record. Hard deletion is for genuine mistakes. - Re-use, don't recreate. Look the person up by
source.externalIdbefore creating — duplicates are the most common avoidable error.
Identifiers
Workers and applicants are addressed by a synthetic, prefixed public ID minted by the server at create time:
- Worker:
wkr_<id>(e.g.wkr_V1StGXR8Z5jdHi6B) - Applicant:
app_<id>(e.g.app_Qz7yT2mB)
You never supply these — the server returns them in the create response and the Location header. To find a worker by your own upstream HRIS identifier, use GET /workers?externalId=.... See API Basics for the full identifier convention.
Internally each record has a URN (urn:li:worker:wkr_..., urn:li:applicant:app_...) built from the public ID. This is an implementation detail — it is never returned by the API and must not be used as a resource identifier.
Next
- Credentials → — the credentials that hang off a worker.
- Applicants → — people in recruitment, before they become workers.
- Linking & ownership → — who a credential belongs to, and what happens at hire.
- API Reference → — the exact endpoints for managing workers.