Vacancies
A vacancy is a planned hire: a role, dates, FTE and salary, with team and project assignments like a person’s. Fill a vacancy creates the employee or contractor who takes it.
- IDs. Paths take a Flowstate ID or your
externalId. A value of 24–25 lowercase letters and digits that starts with a letter is read as a Flowstate ID. Anything else is read as anexternalId. - Dates. Send
YYYY-MM-DD, which is read as 00:00 UTC, or a full ISO 8601 date-time. Dates come back as ISO 8601 date-times, e.g.2026-07-01T00:00:00.000Z. - Money. Amounts come back as decimal strings, e.g.
"90000". Send them as numbers. - Scenarios. Live data only. See Scenarios.
The vacancy object
| Field | Type | Description |
|---|---|---|
id | string | Flowstate ID. Read-only. |
externalId | string | null | Your identifier, e.g. a requisition number. Unique among your vacancies, at most 255 characters, and can’t look like a Flowstate ID. |
role | string | Title of the role. |
name | string | null | Display name, when set. Read-only. |
description | string | null | |
status | string | open, committed (approved, not yet filled), filled or cancelled. |
fte | number | 0–1. |
targetStartDate | datetime | null | Planned start. |
targetEndDate | datetime | null | Last day of a fixed-term role. null = permanent. |
jobRoleId | string | null | Job role ID. |
workTypeId | string | null | Resource type ID. |
geographyId | string | null | Location ID. |
salary | decimal string | null | Annual salary. |
currencyCode | string | null | ISO 4217 code for salary. |
hiringManagerId | string | null | Hiring manager’s employee ID. |
filledByLiveEmployeeId | string | null | Employee who filled it. Read-only; set by Fill a vacancy. |
filledByLiveContractorId | string | null | Contractor who filled it. Read-only; set by Fill a vacancy. At most one of the two filledBy fields is set. |
organizationId, correlationVacancyId, sourceSystem, sourceSystemId, lastSyncedAt, metadata | Record-keeping. Read-only. sourceSystem is api for records created here. | |
createdAt, updatedAt | datetime | Read-only. |
customAttributes | array | Custom attribute values. Returned by retrieve only. |
Endpoints
| Method | Path | Permission |
|---|---|---|
GET | /api/v1/org/:orgId/vacancies | View Vacancies (team_vacancies_view) |
GET | /api/v1/org/:orgId/vacancies/:id | View Vacancies (team_vacancies_view) |
POST | /api/v1/org/:orgId/vacancies | Create Vacancies (team_vacancies_create) |
PATCH | /api/v1/org/:orgId/vacancies/:id | Update Vacancies (team_vacancies_update) |
DELETE | /api/v1/org/:orgId/vacancies/:id | Delete Vacancies (team_vacancies_delete) |
POST | /api/v1/org/:orgId/vacancies/:id/fill | Create Employees (team_employees_create) |
Sub-resource: custom attribute values at /vacancies/:id/custom-attributes.
List vacancies
GET /api/v1/org/:orgId/vacancies
Permission: View Vacancies (team_vacancies_view).
Returns every vacancy, filled and cancelled ones included.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number, from 1. |
limit | integer | 20 | Page size, 1–100. |
search | string | Case-insensitive match on role or description. | |
sortBy | string | role | role, status, targetStartDate or targetEndDate. Any other value sorts by role. |
sortDir | string | asc | asc or desc. |
Example request
curl "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies?sortBy=targetStartDate" \
-H "Authorization: Bearer private_..."
Example response
{
"data": [
{
"id": "cmf2k8x1q0070ab2cd3ef4gh5",
"organizationId": "cmf2k8x1q0000ab2cd3ef4gh5",
"correlationVacancyId": null,
"externalId": "REQ-2207",
"role": "Senior Backend Engineer",
"name": "Senior Backend Engineer",
"description": "Payments platform.",
"status": "open",
"fte": 1,
"targetStartDate": "2026-11-01T00:00:00.000Z",
"targetEndDate": null,
"jobRoleId": "cmf2k8x1q0010ab2cd3ef4gh5",
"workTypeId": "cmf2k8x1q0011ab2cd3ef4gh5",
"geographyId": "cmf2k8x1q0012ab2cd3ef4gh5",
"salary": "90000",
"currencyCode": "GBP",
"filledByLiveEmployeeId": null,
"filledByLiveContractorId": null,
"hiringManagerId": "cmf2k8x1q0001ab2cd3ef4gh5",
"sourceSystem": "api",
"sourceSystemId": null,
"lastSyncedAt": "2026-08-03T10:00:00.000Z",
"metadata": {},
"createdAt": "2026-08-03T10:00:00.000Z",
"updatedAt": "2026-09-01T16:45:00.000Z"
}
],
"meta": { "page": 1, "limit": 20, "total": 1, "hasNextPage": false }
}
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | Invalid query parameter. |
Retrieve a vacancy
GET /api/v1/org/:orgId/vacancies/:id
Permission: View Vacancies (team_vacancies_view).
The response is the vacancy object, with customAttributes and any includes.
Includes
Pass include as a comma-separated list.
| Key | Adds |
|---|---|
assignments | Every team and project assignment. Each is { id, type, targetId, fte, startDate, endDate, createdAt, updatedAt }, where type is team or project. |
filledByEmployee | The employee in filledByLiveEmployeeId. Left out when that field is null. |
Example request
curl "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/REQ-2207?include=assignments" \
-H "Authorization: Bearer private_..."
Example response
{
"data": {
"id": "cmf2k8x1q0070ab2cd3ef4gh5",
"externalId": "REQ-2207",
"role": "Senior Backend Engineer",
"status": "open",
"…": "other vacancy fields",
"customAttributes": [],
"assignments": [
{
"id": "cmf2k8x1q0054ab2cd3ef4gh5",
"type": "team",
"targetId": "cmf2k8x1q0020ab2cd3ef4gh5",
"fte": 1,
"startDate": "2026-11-01T00:00:00.000Z",
"endDate": null,
"createdAt": "2026-08-03T10:00:00.000Z",
"updatedAt": "2026-08-03T10:00:00.000Z"
}
]
}
}
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | An include key not in the table. |
404 | NOT_FOUND | No vacancy with that ID or externalId. |
Create a vacancy
POST /api/v1/org/:orgId/vacancies
Permission: Create Vacancies (team_vacancies_create).
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
role | string | Yes | |
externalId | string | null | No | At most 255 characters. Can’t look like a Flowstate ID. |
description | string | null | No | |
status | string | No | Default open. |
fte | number | No | 0–1. Default 1. |
targetStartDate | date | null | No | |
targetEndDate | date | null | No | On or after targetStartDate. |
jobRoleId | string | null | No | Job role ID. Wins over jobRole. |
jobRole | object | No | Find or create a job role by externalId or title. Same shape as on employees. |
workTypeId | string | null | No | Resource type ID. |
geographyId | string | null | No | Location ID. |
salary | number | null | No | Annual salary, ≥ 0. |
currencyCode | string | null | No | Three uppercase letters. |
hiringManagerId | string | null | No | Employee ID or externalId. |
salaryMin, salaryMax | number | null | No | Deprecated. Use salary. When salary is absent, both bounds are stored as their midpoint, and one bound alone is stored as it is. |
A new vacancy has no assignments. Add them with POST /assignments/vacancies.
Example request
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"role": "Senior Backend Engineer",
"externalId": "REQ-2207",
"description": "Payments platform.",
"targetStartDate": "2026-11-01",
"salary": 90000,
"currencyCode": "GBP",
"hiringManagerId": "EMP-1042"
}'
Example response
201 Created. The response is the vacancy object.
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | The body is invalid, targetEndDate is before targetStartDate, or externalId is already used. |
404 | NOT_FOUND | hiringManagerId matches no employee, or jobRole has only an externalId that matches no role. |
501 | NOT_IMPLEMENTED | scenarioId was sent. |
Update a vacancy
PATCH /api/v1/org/:orgId/vacancies/:id
Permission: Update Vacancies (team_vacancies_update).
Takes the create body with every field optional. null clears a nullable field. An explicit salary wins over the deprecated range. The response is the updated vacancy, 200 OK.
Dates are checked against the stored record, so targetEndDate alone can’t go before the existing targetStartDate. After each update, the vacancy’s team and project assignments are moved to fit its targetStartDate and targetEndDate.
Example request
curl -X PATCH "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/REQ-2207" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{ "status": "committed", "targetStartDate": "2027-01-04" }'
Errors
As for Create a vacancy, plus 404 NOT_FOUND when no vacancy has that ID or externalId.
Delete a vacancy
DELETE /api/v1/org/:orgId/vacancies/:id
Permission: Delete Vacancies (team_vacancies_delete).
Permanent. The vacancy’s team and project assignments are deleted with it. To withdraw a role and keep its record, set status to cancelled.
Example response
{ "data": { "id": "cmf2k8x1q0070ab2cd3ef4gh5", "deleted": true } }
Errors
| Status | Code | When |
|---|---|---|
404 | NOT_FOUND | No vacancy with that ID or externalId. |
501 | NOT_IMPLEMENTED | scenarioId was sent. |
Fill a vacancy
POST /api/v1/org/:orgId/vacancies/:id/fill
Permission: Create Employees (team_employees_create), for both branches.
One transaction does four things:
- Creates the filler. This is an employee with a salary adjustment, or a contractor with a rate adjustment. The adjustment is effective from
startDate. - Ends the vacancy’s assignments. Each assignment with no end date ends the day before
startDate. - Starts new assignments. A copy of each one starts on
startDatefor the filler, on the same team or project at the same FTE. - Marks the vacancy filled. It sets
statustofilledand setsfilledByLiveEmployeeIdorfilledByLiveContractorId.
If you leave out managerId, workTypeId or geographyId, the vacancy’s hiringManagerId, workTypeId or geographyId is used. Employees also take the vacancy’s jobRoleId when you leave it out.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
fillerType | string | No | employee (default) or contractor. |
startDate | date | Yes | Filler’s first day. |
currencyCode | string | Yes | Three uppercase letters, for salary or rate. |
email | string | null | No | Work email. For an employee, send it: without it Flowstate sets a placeholder address. |
managerId | string | null | No | Employee ID or externalId. |
workTypeId | string | null | No | Resource type ID. |
geographyId | string | null | No | Location ID. |
firstName | string | Employee | |
lastName | string | Employee | |
salary | number | Employee | Annual base salary, ≥ 0. |
jobRoleId | string | null | No | Employee only. Wins over jobRole. |
jobRole | object | No | Employee only. Same shape as on employees. |
name | string | Contractor | |
rate | number | Contractor | ≥ 0. |
rateType | string | Contractor | hourly, daily, monthly or annually. |
contractorType | string | No | Contractor only. Default individual. |
Sending the other branch’s fields is a 400. For example, name with fillerType: "employee" is rejected.
Example request: employee
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/REQ-2207/fill" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "Sarah",
"lastName": "Okonkwo",
"email": "sarah.okonkwo@example.com",
"startDate": "2026-11-01",
"salary": 90000,
"currencyCode": "GBP"
}'
Example request: contractor
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/REQ-2207/fill" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"fillerType": "contractor",
"name": "Marco Bianchi",
"startDate": "2026-11-01",
"rate": 700,
"rateType": "daily",
"currencyCode": "GBP"
}'
Example response
200 OK. One of employee and contractor is set; the other is null.
{
"data": {
"employee": {
"id": "cmf2k8x1q0004ab2cd3ef4gh5",
"firstName": "Sarah",
"lastName": "Okonkwo",
"email": "sarah.okonkwo@example.com",
"startDate": "2026-11-01T00:00:00.000Z",
"managerId": "cmf2k8x1q0001ab2cd3ef4gh5",
"jobRoleId": "cmf2k8x1q0010ab2cd3ef4gh5",
"workTypeId": "cmf2k8x1q0011ab2cd3ef4gh5",
"geographyId": "cmf2k8x1q0012ab2cd3ef4gh5",
"defaultCurrencyCode": "GBP",
"…": "other employee fields"
},
"contractor": null,
"vacancyId": "cmf2k8x1q0070ab2cd3ef4gh5",
"teamAllocationsTransferred": 1,
"projectAllocationsTransferred": 0
}
}
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | A required field for the branch is missing, the other branch’s fields were sent, or email is already used. |
404 | NOT_FOUND | No vacancy with that ID or externalId, managerId matches no employee, or jobRole has only an externalId that matches no role. |
409 | CONFLICT | The vacancy’s status is filled, or a filler is already linked. |
501 | NOT_IMPLEMENTED | scenarioId was sent. |
Tasks
Move a start date
PATCH the vacancy’s targetStartDate. Its assignments move with it. There’s no need to update them separately.
Make a role fixed-term
PATCH with targetEndDate. Assignments that ran past it now end on it. Send "targetEndDate": null to make the role permanent again.
Related
- Employees · Contractors · Assignments · Custom attributes · Errors
- Recipe: Fill a vacancy
- Webhooks: writes send
vacancyevents, and a fill also sendsemployeeorcontractor. See Webhooks.