Skip to content

Vacancies

Manage open positions and the hiring pipeline. Vacancies represent roles that need to be filled, with target dates, salary ranges, and team assignments. A vacancy can be filled by either a new employee or a new contractor — the Fill endpoint creates the filler atomically and transfers the vacancy's team/project assignments.

A vacancy is considered "filled" while its filler is currently active (i.e. their endDate is either unset or in the future). Once the filler's endDate passes, the vacancy is treated as open again and resumes contributing to the forecast.

New to Flowstate?

Read the Domain Model to understand the vacancy-to-employee/contractor fill workflow and how vacancies feed into headcount forecasting.

Vacancy Object

FieldTypeDescription
idstringUnique identifier (CUID format). System-generated. Read-only.
externalIdstring | nullYour external identifier for this vacancy. Unique within your organization. Use for lookups via path parameters.
rolestringVacancy title / role name (e.g. "Senior Backend Engineer").
descriptionstring | nullDetailed description of the role and responsibilities.
statusstringVacancy status: "open", "filled", "cancelled", or "on_hold".
ftenumberFull-Time Equivalent for this vacancy. 1.0 = full-time, 0.5 = half-time. Range: 0--1.
targetStartDatedate (YYYY-MM-DD) | nullDesired start date for the new hire. Used in headcount forecasting.
targetFillDatedate (YYYY-MM-DD) | nullTarget date to fill this vacancy by. Used for recruitment planning.
jobRoleIdstring | nullJob role classification. Use GET /job-roles for valid IDs.
workTypeIdstring | nullWork arrangement type. References a Work Type.
geographyIdstring | nullTarget hire location/region. References a Location. Affects salary range expectations.
salaryMinnumber | nullMinimum annual salary for the role. Decimal with 2 decimal places.
salaryMaxnumber | nullMaximum annual salary for the role. Decimal with 2 decimal places.
currencyCodestring | nullISO 4217 currency code for the salary range (e.g. "GBP").
filledByLiveEmployeeIdstring | nullID of the employee who filled this vacancy. Set by the Fill endpoint (employee branch) or manually via PATCH. References an Employee. Mutually exclusive with filledByLiveContractorId.
filledByLiveContractorIdstring | nullID of the contractor who filled this vacancy. Set by the Fill endpoint (contractor branch) or manually via PATCH. References a Contractor. Mutually exclusive with filledByLiveEmployeeId.
isFilledbooleanDerived. true when the vacancy is currently filled — i.e. one of the filledBy* FKs is set and the filler's endDate is unset or in the future. Once the filler's endDate passes, isFilled flips back to false and the vacancy resumes contributing to forecast cost.
hiringManagerIdstring | nullID of the employee responsible for hiring this role. References an Employee.
createdAtdatetime (ISO 8601)When the record was created. Read-only.
updatedAtdatetime (ISO 8601)When the record was last modified. Read-only.
customAttributesarrayCustom attribute values for this vacancy. Returned on GET-by-ID. See Custom Attributes.

Endpoints

MethodPathDescription
GET/org/:orgId/vacanciesList vacancies
GET/org/:orgId/vacancies/:idGet vacancy
POST/org/:orgId/vacanciesCreate vacancy
PATCH/org/:orgId/vacancies/:idUpdate vacancy
DELETE/org/:orgId/vacancies/:idDelete vacancy
POST/org/:orgId/vacancies/:id/fillFill a vacancy (convert to employee)

External ID Support

The :id path parameter accepts either a Flowstate CUID (e.g. clx1a2b3c4d5e6f7g8h9) or your externalId. The format is auto-detected.


List Vacancies

GET /org/:orgId/vacancies

Returns a paginated list of vacancies in the organization.

Query Parameters

ParameterTypeDefaultDescription
pageinteger1Page number (1-based).
limitinteger20Records per page. Min 1, max 100.
searchstring--Free-text search by role or description.
sortBystringroleField to sort by.
sortDirstringascSort direction: asc or desc.
scenarioIdstring--Scenario ID for what-if queries.

Example Request

bash
curl -X GET "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies?sortBy=targetStartDate&sortDir=asc" \
  -H "Authorization: Bearer private_..."

Example Response

json
{
  "data": [
    {
      "id": "clx5v6w7x8y9z0a1b2c3",
      "externalId": null,
      "role": "Senior Backend Engineer",
      "description": "Backend engineer for the payments team, focusing on billing infrastructure.",
      "status": "open",
      "fte": 1.0,
      "targetStartDate": "2026-06-01",
      "targetFillDate": "2026-05-15",
      "jobRoleId": "clx9r8q7w6e5t4r3",
      "workTypeId": "clx2w3x4y5z6a7b8",
      "geographyId": "clx3g2h1j0k9l8m7",
      "salaryMin": 120000,
      "salaryMax": 160000,
      "currencyCode": "USD",
      "filledByLiveEmployeeId": null,
      "hiringManagerId": "clx9m4n5o6p7q8r9",
      "createdAt": "2026-01-15T10:00:00Z",
      "updatedAt": "2026-03-01T16:45:00Z"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "total": 23,
    "hasNextPage": true
  }
}

Get Vacancy

GET /org/:orgId/vacancies/:id

Returns a single vacancy by ID.

Include KeyDescription
assignmentsAll team and project assignments for this vacancy.
filledByEmployeeThe employee record that filled this vacancy (if status is "filled").
GET /org/:orgId/vacancies/:id?include=assignments,filledByEmployee

Example Request

bash
curl -X GET "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/clx5v6w7x8y9z0a1b2c3?include=assignments" \
  -H "Authorization: Bearer private_..."

Example Response

json
{
  "data": {
    "id": "clx5v6w7x8y9z0a1b2c3",
    "role": "Senior Backend Engineer",
    "description": "Backend engineer for the payments team, focusing on billing infrastructure.",
    "status": "open",
    "fte": 1.0,
    "targetStartDate": "2026-06-01",
    "targetFillDate": "2026-05-15",
    "jobRoleId": "clx9r8q7w6e5t4r3",
    "workTypeId": "clx2w3x4y5z6a7b8",
    "geographyId": "clx3g2h1j0k9l8m7",
    "salaryMin": 120000,
    "salaryMax": 160000,
    "currencyCode": "USD",
    "filledByLiveEmployeeId": null,
    "hiringManagerId": "clx9m4n5o6p7q8r9",
    "createdAt": "2026-01-15T10:00:00Z",
    "updatedAt": "2026-03-01T16:45:00Z",
    "customAttributes": [
      {
        "id": "clx0a1b2c3d4e5f6g7h8",
        "definitionId": "clx2d3e4f5g6h7i8j9k0",
        "entityType": "VACANCY",
        "entityId": "clx5v6w7x8y9z0a1b2c3",
        "stringValue": "ENG-001",
        "numberValue": null,
        "dateValue": null,
        "dateRangeStart": null,
        "dateRangeEnd": null,
        "definition": {
          "id": "clx2d3e4f5g6h7i8j9k0",
          "name": "Cost Centre Code",
          "attributeKey": "cost_centre_code",
          "fieldType": "STRING"
        }
      }
    ],
    "assignments": [
      {
        "id": "clx8a9b0c1d2e3f4",
        "type": "team",
        "targetId": "clx6t7u8v9w0x1y2",
        "fte": 1.0,
        "startDate": "2026-06-01",
        "endDate": null,
        "createdAt": "2026-01-15T10:00:00Z",
        "updatedAt": "2026-01-15T10:00:00Z"
      }
    ]
  }
}

Create Vacancy

POST /org/:orgId/vacancies

Creates a new vacancy record.

Create Request Body

FieldTypeRequiredDescription
rolestringYesVacancy title or role name.
externalIdstring | nullNoYour external identifier. Must be unique within the organization. Cannot resemble a CUID format. Max 255 characters.
descriptionstring | nullNoDetailed role description.
statusstringNoVacancy status. Default: "open".
ftenumberNoFTE value (0--1). Default: 1.0.
targetStartDatedate (YYYY-MM-DD) | nullNoDesired start date for the new hire.
targetFillDatedate (YYYY-MM-DD) | nullNoTarget date to fill the vacancy.
jobRoleIdstring | nullNoJob role ID. Takes precedence over jobRole.
jobRoleobjectNoResolve-or-create a job role by externalId or title. Ignored when jobRoleId is supplied. See Employees → Nested jobRole Object for the shape and semantics — identical here.
workTypeIdstring | nullNoWork Type ID.
geographyIdstring | nullNoLocation ID.
salaryMinnumber | nullNoMinimum annual salary. Must be >= 0.
salaryMaxnumber | nullNoMaximum annual salary. Must be >= 0.
currencyCodestring | nullNoISO 4217 currency code (3 uppercase letters).
hiringManagerIdstring | nullNoID of the hiring manager (an employee).

Example Request

bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies" \
  -H "Authorization: Bearer private_..." \
  -H "Content-Type: application/json" \
  -d '{
    "role": "DevOps Engineer",
    "description": "Cloud infrastructure engineer to support the platform team.",
    "fte": 1.0,
    "targetStartDate": "2026-09-01",
    "targetFillDate": "2026-08-15",
    "salaryMin": 110000,
    "salaryMax": 145000,
    "currencyCode": "USD",
    "hiringManagerId": "clx9m4n5o6p7q8r9"
  }'

Example Response

json
{
  "data": {
    "id": "clx2b3c4d5e6f7g8h9i0",
    "externalId": null,
    "role": "DevOps Engineer",
    "description": "Cloud infrastructure engineer to support the platform team.",
    "status": "open",
    "fte": 1.0,
    "targetStartDate": "2026-09-01",
    "targetFillDate": "2026-08-15",
    "jobRoleId": null,
    "workTypeId": null,
    "geographyId": null,
    "salaryMin": 110000,
    "salaryMax": 145000,
    "currencyCode": "USD",
    "filledByLiveEmployeeId": null,
    "hiringManagerId": "clx9m4n5o6p7q8r9",
    "createdAt": "2026-04-08T09:30:00Z",
    "updatedAt": "2026-04-08T09:30:00Z"
  }
}

Status: 201 Created


Update Vacancy

PATCH /org/:orgId/vacancies/:id

Updates one or more fields on an existing vacancy. Only include the fields you want to change. You can set or update the externalId field.

Update Request Body

All fields from the Create Request Body are accepted, and all are optional.

Example Request

bash
curl -X PATCH "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/clx5v6w7x8y9z0a1b2c3" \
  -H "Authorization: Bearer private_..." \
  -H "Content-Type: application/json" \
  -d '{
    "status": "on_hold",
    "targetFillDate": "2026-07-01"
  }'

Delete Vacancy

DELETE /org/:orgId/vacancies/:id

Deletes a vacancy record.

Example Request

bash
curl -X DELETE "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/clx5v6w7x8y9z0a1b2c3" \
  -H "Authorization: Bearer private_..."

Status: 204 No Content


Fill a Vacancy

POST /org/:orgId/vacancies/:id/fill

Converts a vacancy into a new employee or contractor. This is an atomic operation that:

  1. Creates a new employee (or contractor) with the provided details.
  2. Creates an initial salary adjustment (or contractor rate adjustment) effective from startDate.
  3. Transfers all team/project assignments from the vacancy to the new filler.
  4. Sets the vacancy status to "filled" and links it via filledByLiveEmployeeId (employee branch) or filledByLiveContractorId (contractor branch).

Fields not provided in the fill request fall back to the vacancy's values (e.g. jobRoleId, geographyId, workTypeId, hiringManagerId).

Choosing the branch

Set fillerType to choose the branch (defaults to "employee" for backwards compatibility):

fillerTypeRequired fields
"employee" (default)firstName, lastName, salary, currencyCode, startDate
"contractor"name, rate, rateType, currencyCode, startDate

The "wrong" branch's fields must not be sent — e.g. name with fillerType: "employee" is rejected with a 400. The endpoint also rejects fills against vacancies whose isFilled is currently true.

Fill Request Body

FieldTypeRequiredDescription
fillerType"employee" | "contractor"NoDiscriminator. Defaults to "employee".
startDatedate (YYYY-MM-DD)YesFiller's start date. Vacancy assignments transfer from this date.
currencyCodestringYesISO 4217 currency code for the salary or rate (e.g. "GBP").
emailstring | nullNoWork email. If omitted on the employee branch, a placeholder is generated.
managerIdstring | nullNoEmployee ID of the new hire's manager. Falls back to the vacancy's hiringManagerId.
jobRoleIdstring | nullNoJob role ID. Falls back to the vacancy's jobRoleId. Takes precedence over jobRole. (Employee branch only.)
jobRoleobjectNoResolve-or-create a job role by externalId or title (same shape as on employees). Ignored when jobRoleId is supplied. (Employee branch only.)
workTypeIdstring | nullNoWork type ID. Falls back to the vacancy's workTypeId.
geographyIdstring | nullNoGeography ID. Falls back to the vacancy's geographyId.
Employee branch
firstNamestringYes (employee)New employee's first name.
lastNamestringYes (employee)New employee's last name.
salarynumberYes (employee)Annual base salary. Must be >= 0.
Contractor branch
namestringYes (contractor)Full name for the new contractor.
ratenumberYes (contractor)Rate per period (see rateType). Must be >= 0.
rateType"hourly" | "daily" | "monthly" | "annually"Yes (contractor)Billing period for rate.
contractorTypestringNoDefaults to "individual".

Example Request — Employee Branch

bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/clx5v6w7x8y9z0a1b2c3/fill" \
  -H "Authorization: Bearer private_..." \
  -H "Content-Type: application/json" \
  -d '{
    "fillerType": "employee",
    "firstName": "Sarah",
    "lastName": "Okonkwo",
    "email": "sarah.okonkwo@example.com",
    "startDate": "2026-06-01",
    "salary": 140000,
    "currencyCode": "USD"
  }'

Example Request — Contractor Branch

bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/clx5v6w7x8y9z0a1b2c3/fill" \
  -H "Authorization: Bearer private_..." \
  -H "Content-Type: application/json" \
  -d '{
    "fillerType": "contractor",
    "name": "Marco Bianchi",
    "startDate": "2026-06-01",
    "rate": 800,
    "rateType": "daily",
    "currencyCode": "EUR"
  }'

Example Response

The response wraps both possibilities — exactly one of employee / contractor is non-null depending on the branch taken:

json
{
  "data": {
    "employee": {
      "id": "clx7n8m9k0j1h2g3f4e5",
      "firstName": "Sarah",
      "lastName": "Okonkwo",
      "email": "sarah.okonkwo@example.com",
      "internalEmployeeId": null,
      "startDate": "2026-06-01",
      "endDate": null,
      "managerId": "clx9m4n5o6p7q8r9",
      "jobRoleId": "clx9r8q7w6e5t4r3",
      "workTypeId": "clx2w3x4y5z6a7b8",
      "geographyId": "clx3g2h1j0k9l8m7",
      "defaultCurrencyCode": "USD",
      "createdAt": "2026-04-08T10:00:00Z",
      "updatedAt": "2026-04-08T10:00:00Z"
    },
    "contractor": null,
    "vacancyId": "clx5v6w7x8y9z0a1b2c3",
    "teamAllocationsTransferred": 1,
    "projectAllocationsTransferred": 0
  }
}

Status: 200 OK

What happens behind the scenes

After a successful fill (employee branch):

  • The vacancy's status becomes "filled" and filledByLiveEmployeeId points to the new employee.
  • A salary adjustment is created with the provided salary, currencyCode, and the employee's startDate as the effectiveDate.
  • All vacancy team/project assignments are end-dated the day before startDate and re-created against the new employee.

After a successful fill (contractor branch):

  • The vacancy's status becomes "filled" and filledByLiveContractorId points to the new contractor.
  • An initial contractor rate adjustment is created with the provided rate, rateType, currencyCode, and effectiveDate = startDate.
  • All vacancy team/project assignments are end-dated and re-created against the new contractor.

In both cases the managerId defaults to the vacancy's hiringManagerId, and jobRoleId / workTypeId / geographyId default to the vacancy's values when not provided.


External IDs

You can assign your own externalId during creation or update. This allows you to reference vacancies by your system's identifier instead of the Flowstate CUID.

  • Setting: Pass externalId in the POST or PATCH request body.
  • Lookups: Use the external ID directly in path parameters (e.g. GET /vacancies/MY-EXT-ID).
  • Uniqueness: External IDs must be unique per entity type within your organization.
  • Format restriction: External IDs cannot match the CUID format (25 lowercase alphanumeric characters starting with a letter).

Error Responses

Validation Error (400)

json
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed.",
    "details": [
      { "field": "role", "message": "role is required" }
    ],
    "errorId": "err_clx9a8b7c6d5e4f3"
  }
}

Not Found (404)

json
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Vacancy not found.",
    "errorId": "err_clx9a8b7c6d5e4f3"
  }
}

Flowstate Documentation