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
| Resource | Table | List and read | POST | PATCH | DELETE |
|---|---|---|---|---|---|
employments | employment | /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
| Action | Needs | What it does |
|---|---|---|
employees.activate | employees:manage | Finish onboarding |
employees.end_employment | employees:manage | End employment |
employees.hire | employees:manage | Hire |
employees.update | employees:manage | Edit employment details |
employees.activate
Finish onboarding — Moves an onboarding employee to active.
Needs employees:manage · changes data
https://app.getera.io/api/v1/employments/actions/activate- Canonical address:
POST /api/v1/actions/employees.activate
Input
| Field | Type | Required | Notes |
|---|---|---|---|
userId | uuid | yes | Member · id of app_user |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_employed | They must have an employment record. |
not_onboarding | Only 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"}'{
"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"{
"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
https://app.getera.io/api/v1/employments/actions/end_employment- Canonical address:
POST /api/v1/actions/employees.end_employment
Input
| Field | Type | Required | Notes |
|---|---|---|---|
userId | uuid | yes | Member · id of app_user |
endDate | date | yes | Last day |
reason | string (enum) | yes | Reason · Allowed values: resigned, terminated, retired, contract_ended, other |
note | string | Note · max 2000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
cannot_end_self | You cannot end your own employment. |
not_employed | They must have an employment record. |
already_ended | Their employment must not already have ended. |
end_before_hire | The 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"}'{
"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"{
"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
https://app.getera.io/api/v1/employments/actions/hire- Canonical address:
POST /api/v1/actions/employees.hire
Input
| Field | Type | Required | Notes |
|---|---|---|---|
userId | uuid | yes | Member · id of app_user |
hireDate | date | yes | Hire date |
employmentType | string (enum) | Employment type · Allowed values: full_time, part_time, contractor, intern, temporary | |
startActive | boolean | Skip onboarding | |
workPhone | string | Work phone · max 40 | |
emergencyContactName | string | Emergency contact · max 120 | |
emergencyContactPhone | string | Emergency phone · max 40 | |
notes | string | Notes · max 4000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_a_member | They must already be a member of this workspace. |
already_employed | They 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"}'{
"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"{
"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
https://app.getera.io/api/v1/employments/actions/update- Canonical address:
POST /api/v1/actions/employees.update
Input
| Field | Type | Required | Notes |
|---|---|---|---|
userId | uuid | yes | Member · id of app_user |
hireDate | date | Hire date | |
employmentType | string (enum) | Employment type · Allowed values: full_time, part_time, contractor, intern, temporary | |
workPhone | string | Work phone · max 40 | |
emergencyContactName | string | Emergency contact · max 120 | |
emergencyContactPhone | string | Emergency phone · max 40 | |
notes | string | Notes · max 4000 |
Preconditions
The action is refused with 409 and one of these codes:
| Code | Refused when |
|---|---|
not_employed | They 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"}'{
"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"{
"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/employmentsGET /api/v1/employments/{id}
| Column | Type | Nullable | Allowed values | References |
|---|---|---|---|---|
organization_id | string (uuid) · uuid | organization | ||
workspace_id | string (uuid) · uuid | workspace | ||
user_id | string (uuid) · uuid | app_user | ||
status | string · text | onboarding, active, terminated | ||
employment_type | string · text | full_time, part_time, contractor, intern, temporary | ||
hire_date | string (date) · date | |||
activated_at | string (date-time) · timestamptz | yes | ||
end_date | string (date) · date | yes | ||
end_reason | string · text | yes | ||
end_note | string · text | yes | ||
ended_by_user_id | string (uuid) · uuid | yes | app_user | |
work_phone | string · text | yes | ||
emergency_contact_name | string · text | yes | ||
emergency_contact_phone | string · text | yes | ||
notes | string · text | yes | ||
created_at | string (date-time) · timestamptz | |||
updated_at | string (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.
| Event | What happens | Payload |
|---|---|---|
employees.activated | Somebody finished onboarding. | user_id, recipientUserId |
employees.employment_ended | Somebody’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.hired | A personnel file was opened for somebody. | user_id, status, hireDate, recipientUserId |
employees.updated | Employment details were changed. | user_id |