ReferenceReference by module

Team API

Every Team action (4) and data model (1): endpoints, scopes, input, preconditions, events and examples.

Your team directory and staff files — hiring, onboarding and leaving.

Module code employees · 4 actions · 1 data models · 4 events. A key or token never does more here than the person behind it can; row-level security applies exactly as in the app. Scopes: employees (everything), employees:read (read only) or employees:<capability>.

Resources

ResourceTableList and readPOSTPATCHDELETE
employmentsemployment/api/v1/employments

Lists take limit (up to 200), cursor, order (e.g. -created_at), fields and q; every other query parameter is a column filter: ?status=paid, ?total[gte]=100, ?id[in]=a,b, ?x=null.

Actions

ActionNeedsWhat it does
employees.activateemployees:manageFinish onboarding
employees.end_employmentemployees:manageEnd employment
employees.hireemployees:manageHire
employees.updateemployees:manageEdit employment details

employees.activate

Finish onboarding — Moves an onboarding employee to active.

Needs employees:manage · changes data

POSThttps://app.getera.io/api/v1/employments/actions/activate
API key or OAuth token · employees:manage
  • Canonical address: POST /api/v1/actions/employees.activate

Input

FieldTypeRequiredNotes
userIduuidyesMember · id of app_user

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_employedThey must have an employment record.
not_onboardingOnly an onboarding employee can be activated.

Events it publishes: employees.activated

What it writes: employment (update) — Sets the status to active

curl -X POST https://app.getera.io/api/v1/employments/actions/activate \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"userId":"APP_USER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "employees.activated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.employments.activate({
  userId: "APP_USER_ID"
})
era employments activate \
  --user-id "APP_USER_ID"
tools/call
{
  "name": "era__employees__activate",
  "arguments": {
    "userId": "APP_USER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold employees:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_employed, not_onboarding · 429 slow down

employees.end_employment

End employment — Records the last day and the reason, and names everything they still own so it can be handed over.

Needs employees:manage · reaches outside (sends, publishes or charges) · asks a person to confirm when Era runs it

POSThttps://app.getera.io/api/v1/employments/actions/end_employment
API key or OAuth token · employees:manage
  • Canonical address: POST /api/v1/actions/employees.end_employment

Input

FieldTypeRequiredNotes
userIduuidyesMember · id of app_user
endDatedateyesLast day
reasonstring (enum)yesReason · Allowed values: resigned, terminated, retired, contract_ended, other
notestringNote · max 2000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
cannot_end_selfYou cannot end your own employment.
not_employedThey must have an employment record.
already_endedTheir employment must not already have ended.
end_before_hireThe last day cannot be before the hire date.

Events it publishes: employees.employment_ended

What it writes: employment (update) — Closes the personnel file

curl -X POST https://app.getera.io/api/v1/employments/actions/end_employment \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"userId":"APP_USER_ID","endDate":"2026-11-02","reason":"resigned"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "employees.employment_ended"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.employments.endEmployment({
  userId: "APP_USER_ID",
  endDate: "2026-11-02",
  reason: "resigned"
})
era employments end_employment \
  --user-id "APP_USER_ID" \
  --end-date "2026-11-02" \
  --reason "resigned"
tools/call
{
  "name": "era__employees__end_employment",
  "arguments": {
    "userId": "APP_USER_ID",
    "endDate": "2026-11-02",
    "reason": "resigned"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold employees:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 cannot_end_self, not_employed, already_ended, end_before_hire · 429 slow down

employees.hire

Hire — Opens a personnel file for a workspace member — onboarding first, or straight to active.

Needs employees:manage · changes data

POSThttps://app.getera.io/api/v1/employments/actions/hire
API key or OAuth token · employees:manage
  • Canonical address: POST /api/v1/actions/employees.hire

Input

FieldTypeRequiredNotes
userIduuidyesMember · id of app_user
hireDatedateyesHire date
employmentTypestring (enum)Employment type · Allowed values: full_time, part_time, contractor, intern, temporary
startActivebooleanSkip onboarding
workPhonestringWork phone · max 40
emergencyContactNamestringEmergency contact · max 120
emergencyContactPhonestringEmergency phone · max 40
notesstringNotes · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_a_memberThey must already be a member of this workspace.
already_employedThey must not have an open employment record.

Events it publishes: employees.hired

What it writes: employment (upsert) — Opens (or re-opens) the personnel file

curl -X POST https://app.getera.io/api/v1/employments/actions/hire \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"userId":"APP_USER_ID","hireDate":"2026-11-02"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "employees.hired"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.employments.hire({
  userId: "APP_USER_ID",
  hireDate: "2026-11-02"
})
era employments hire \
  --user-id "APP_USER_ID" \
  --hire-date "2026-11-02"
tools/call
{
  "name": "era__employees__hire",
  "arguments": {
    "userId": "APP_USER_ID",
    "hireDate": "2026-11-02"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold employees:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_a_member, already_employed · 429 slow down

employees.update

Edit employment details — Hire date, employment type and work contact.

Needs employees:manage · changes data

POSThttps://app.getera.io/api/v1/employments/actions/update
API key or OAuth token · employees:manage
  • Canonical address: POST /api/v1/actions/employees.update

Input

FieldTypeRequiredNotes
userIduuidyesMember · id of app_user
hireDatedateHire date
employmentTypestring (enum)Employment type · Allowed values: full_time, part_time, contractor, intern, temporary
workPhonestringWork phone · max 40
emergencyContactNamestringEmergency contact · max 120
emergencyContactPhonestringEmergency phone · max 40
notesstringNotes · max 4000

Preconditions

The action is refused with 409 and one of these codes:

CodeRefused when
not_employedThey must have an employment record.

Events it publishes: employees.updated

What it writes: employment (update) — Rewrites the given fields

curl -X POST https://app.getera.io/api/v1/employments/actions/update \
  -H "Authorization: Bearer $ERA_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"userId":"APP_USER_ID"}'
Response
{
  "ok": true,
  "result": "…",
  "events": [
    "employees.updated"
  ]
}
import { createEra } from "@getera/sdk"

const era = createEra({ apiKey: process.env.ERA_API_KEY })
const res = await era.employments.update({
  userId: "APP_USER_ID"
})
era employments update \
  --user-id "APP_USER_ID"
tools/call
{
  "name": "era__employees__update",
  "arguments": {
    "userId": "APP_USER_ID"
  }
}

Errors: 401 no or expired credential · 403 the credential does not hold employees:manage · 404 the record or module is outside what it may reach · 422 invalid input (problems[]) · 409 not_employed · 429 slow down

Data models

Employment

Table employment · Resource employments

  • GET /api/v1/employments
  • GET /api/v1/employments/{id}
ColumnTypeNullableAllowed valuesReferences
organization_idstring (uuid) · uuidorganization
workspace_idstring (uuid) · uuidworkspace
user_idstring (uuid) · uuidapp_user
statusstring · textonboarding, active, terminated
employment_typestring · textfull_time, part_time, contractor, intern, temporary
hire_datestring (date) · date
activated_atstring (date-time) · timestamptzyes
end_datestring (date) · dateyes
end_reasonstring · textyes
end_notestring · textyes
ended_by_user_idstring (uuid) · uuidyesapp_user
work_phonestring · textyes
emergency_contact_namestring · textyes
emergency_contact_phonestring · textyes
notesstring · textyes
created_atstring (date-time) · timestamptz
updated_atstring (date-time) · timestamptz

Primary key: workspace_id, user_id

Checks:

  • CHECK (((end_date IS NULL) OR (end_date >= hire_date)))
  • CHECK (((status = 'terminated'::text) = ((end_date IS NOT NULL) AND (end_reason IS NOT NULL))))
  • CHECK (((end_reason IS NULL) OR (end_reason = ANY (ARRAY['resigned'::text, 'terminated'::text, 'retired'::text, 'contract_ended'::text, 'other'::text]))))

Events

Workflows and webhooks react to these events.

EventWhat happensPayload
employees.activatedSomebody finished onboarding.user_id, recipientUserId
employees.employment_endedSomebody’s employment ended. Addressed to whoever hands over what they still own (the Hand-over tab on their file).user_id, name, endDate, reason, ownerUserId, recipientUserId
employees.hiredA personnel file was opened for somebody.user_id, status, hireDate, recipientUserId
employees.updatedEmployment details were changed.user_id
Was this page helpful?