Appearance
Recipes
Task-oriented guides for common integration workflows. Each recipe shows the full sequence of API calls, request bodies, and responses.
Prerequisites
All recipes assume you have an API key and know your organization ID. See the API Overview for setup instructions. All URLs use the base https://{tenant}.flowstate.inc/api/v1.
Onboard a New Employee
Create an employee with their starting salary and team assignment in a single request using nested create.
Request
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "Kai",
"lastName": "Nakamura",
"email": "kai.nakamura@example.com",
"internalEmployeeId": "EMP-3042",
"startDate": "2026-05-01",
"geographyId": "clx3g2h1j0k9l8m7",
"workTypeId": "clx2w3x4y5z6a7b8",
"jobRoleId": "clx9r8q7w6e5t4r3",
"managerId": "clx9m4n5o6p7q8r9",
"salary": {
"effectiveDate": "2026-05-01",
"salary": 92000,
"currencyCode": "GBP",
"bonus": 8000,
"reason": "Starting salary"
},
"teamAssignment": {
"teamId": "clx6t7u8v9w0x1y2z3a4",
"fte": 1.0,
"startDate": "2026-05-01"
}
}'Response (201 Created)
json
{
"data": {
"id": "clx7n8m9k0j1h2g3f4e5",
"firstName": "Kai",
"lastName": "Nakamura",
"email": "kai.nakamura@example.com",
"internalEmployeeId": "EMP-3042",
"startDate": "2026-05-01",
"endDate": null,
"noticeDate": null,
"managerId": "clx9m4n5o6p7q8r9",
"jobRoleId": "clx9r8q7w6e5t4r3",
"workTypeId": "clx2w3x4y5z6a7b8",
"geographyId": "clx3g2h1j0k9l8m7",
"defaultCurrencyCode": null,
"createdAt": "2026-04-08T09:00:00Z",
"updatedAt": "2026-04-08T09:00:00Z"
}
}What happened
- The employee record was created.
- A salary adjustment of 92,000 GBP with an 8,000 GBP bonus was created with effective date 2026-05-01.
- A team assignment at 1.0 FTE was created linking the employee to the Platform Engineering team from their start date.
- All three records were created atomically -- if any part fails, nothing is created.
Fill a Vacancy
Convert an open vacancy into a hired employee. This transfers all vacancy assignments to the new employee.
Step 1: Fill the vacancy
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 '{
"firstName": "Sarah",
"lastName": "Okonkwo",
"email": "sarah.okonkwo@example.com",
"startDate": "2026-06-01",
"salary": 140000,
"currencyCode": "USD"
}'Response (201 Created)
json
{
"data": {
"id": "clx7n8m9k0j1h2g3f4e5",
"firstName": "Sarah",
"lastName": "Okonkwo",
"email": "sarah.okonkwo@example.com",
"internalEmployeeId": null,
"startDate": "2026-06-01",
"endDate": null,
"noticeDate": null,
"managerId": "clx9m4n5o6p7q8r9",
"jobRoleId": "clx9r8q7w6e5t4r3",
"workTypeId": "clx2w3x4y5z6a7b8",
"geographyId": "clx3g2h1j0k9l8m7",
"defaultCurrencyCode": null,
"createdAt": "2026-04-08T10:00:00Z",
"updatedAt": "2026-04-08T10:00:00Z"
}
}Step 2 (optional): Verify the vacancy is filled
bash
curl -X GET "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/vacancies/clx5v6w7x8y9z0a1b2c3" \
-H "Authorization: Bearer private_..."The vacancy now shows "status": "filled" and "filledByLiveEmployeeId": "clx7n8m9k0j1h2g3f4e5".
What happened
- A new employee was created with the provided name, email, and start date.
- The vacancy's
jobRoleId,workTypeId,geographyId, andhiringManagerIdwere inherited by the new employee (since they were not provided in the fill request). - A salary adjustment of 140,000 USD was created effective 2026-06-01.
- All team and project assignments that were on the vacancy were duplicated as employee assignments.
- The vacancy's status changed to
"filled".
Sync Employees from HRIS
A typical HRIS sync pattern: list existing employees, compare with your source system, then create or update as needed.
Step 1: List all employees with their internal IDs
Page through all employees to build a lookup map:
bash
curl -X GET "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees?limit=100&page=1" \
-H "Authorization: Bearer private_..."Continue incrementing page until hasNextPage is false. Use internalEmployeeId to match records against your HRIS.
Step 2: Create new employees
For each employee in your HRIS that has no match in Flowstate:
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "Aisha",
"lastName": "Patel",
"email": "aisha.patel@example.com",
"internalEmployeeId": "HR-5089",
"startDate": "2026-03-01",
"geographyId": "clx3g2h1j0k9l8m7",
"workTypeId": "clx2w3x4y5z6a7b8"
}'Step 3: Update changed employees
For each employee that exists in both systems but has changed fields:
bash
curl -X PATCH "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees/clx1a2b3c4d5e6f7g8h9" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"email": "aisha.patel-new@example.com",
"endDate": "2026-12-31"
}'Best practices
- Use
internalEmployeeIdas the stable join key between your HRIS and Flowstate. - Page through results with
limit=100(the maximum) to minimize API calls. - Use PATCH for updates -- it only modifies the fields you send.
- To handle salary changes from your HRIS, use the nested
salaryobject on the PATCH request or create salary adjustments directly.
Sync Employees Using External IDs
The recommended sync pattern for automated integrations. Use externalId to correlate records between your system and Flowstate -- no need to maintain a separate ID mapping table.
Step 1: Create employees with your external ID
Set externalId when creating each employee. This becomes the stable correlation key.
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"firstName": "Aisha",
"lastName": "Patel",
"email": "aisha.patel@example.com",
"externalId": "HR-5089",
"startDate": "2026-03-01",
"geographyId": "clx3g2h1j0k9l8m7"
}'Step 2: Update using the external ID directly in the path
No need to look up the Flowstate CUID -- use the external ID as the path parameter:
bash
curl -X PATCH "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees/HR-5089" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"email": "aisha.patel-new@example.com",
"endDate": "2026-12-31"
}'The :id path parameter auto-detects the format -- CUIDs (25-character lowercase alphanumeric) are looked up by id; everything else is treated as an externalId lookup.
Step 3: Fetch by external ID
bash
curl -X GET "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees/HR-5089" \
-H "Authorization: Bearer private_..."Best practices
- Set
externalIdon every record during initial sync. This avoids the need to page through all records for matching. - Use
externalIdin path parameters for GET, PATCH, and DELETE -- it works everywhere the:idparameter is accepted. - Foreign key fields like
managerIdalso accept external IDs (e.g.,"managerId": "HR-4001"). externalIdmust be unique within your organization. It cannot resemble a Flowstate CUID (25-character lowercase alphanumeric).- This pattern works for all core entities: employees, contractors, vacancies, teams, and projects.
Record a Salary Change
Record a salary increase for an employee. This creates a new salary adjustment without affecting existing history.
Request
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees/clx1a2b3c4d5e6f7g8h9/salary-adjustments" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"effectiveDate": "2026-07-01",
"salary": 110000,
"currencyCode": "GBP",
"bonus": 15000,
"reason": "Annual performance review - exceeds expectations"
}'Response (201 Created)
json
{
"data": {
"id": "clx9k0l1m2n3o4p5q6r7",
"employeeId": "clx1a2b3c4d5e6f7g8h9",
"effectiveDate": "2026-07-01",
"salary": 110000,
"bonus": 15000,
"currencyCode": "GBP",
"reason": "Annual performance review - exceeds expectations",
"createdAt": "2026-04-08T09:30:00Z",
"updatedAt": "2026-04-08T09:30:00Z"
}
}What happened
- A new salary adjustment was added to the employee's history.
- The employee's current salary is now 110,000 GBP effective 2026-07-01.
- The previous salary adjustment(s) remain unchanged in the history.
- Flowstate will use the old salary for cost calculations before 2026-07-01 and the new salary after.
Verify the salary history
bash
curl -X GET "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/employees/clx1a2b3c4d5e6f7g8h9?include=salaryHistory" \
-H "Authorization: Bearer private_..."Move an Employee Between Teams
To move an employee from one team to another, end the existing assignment and create a new one.
Step 1: Find the current team assignment
bash
curl -X GET "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/assignments/employees?employeeId=clx1a2b3c4d5e6f7g8h9&type=team" \
-H "Authorization: Bearer private_..."Response
json
{
"data": [
{
"id": "clx8a9b0c1d2e3f4g5h6",
"type": "team",
"targetId": "clx6t7u8v9w0x1y2z3a4",
"fte": 1.0,
"startDate": "2025-01-01",
"endDate": null,
"createdAt": "2024-12-15T10:00:00Z",
"updatedAt": "2025-06-01T14:00:00Z"
}
],
"meta": { "page": 1, "limit": 20, "total": 1, "hasNextPage": false }
}Step 2: End the current assignment
Set the endDate to the last day on the old team:
bash
curl -X PATCH "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/assignments/employees/clx8a9b0c1d2e3f4g5h6" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"endDate": "2026-06-30"
}'Step 3: Create the new team assignment
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/assignments/employees" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"employeeId": "clx1a2b3c4d5e6f7g8h9",
"teamId": "clx3d4e5f6g7h8i9j0k1",
"fte": 1.0,
"startDate": "2026-07-01"
}'What happened
- The old team assignment was end-dated to 2026-06-30.
- A new team assignment was created starting 2026-07-01 on the new team.
- Headcount reports will show the employee on the old team through June and on the new team from July.
- Cost attribution automatically follows the assignment dates.
Add a Contractor Engagement
Onboard a new contractor with their initial rate and team assignment.
Request
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/contractors" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Elena Vasquez",
"email": "elena@design-studio.io",
"contractorType": "individual",
"startDate": "2026-05-01",
"endDate": "2026-10-31",
"geographyId": "clx1e2n3g4r5o0t6u7v8",
"managerId": "clx9m4n5o6p7q8r9",
"rateAdjustment": {
"effectiveDate": "2026-05-01",
"rateType": "daily",
"rate": 950,
"currencyCode": "EUR",
"reason": "Initial engagement rate"
},
"teamAssignment": {
"teamId": "clx6t7u8v9w0x1y2z3a4",
"fte": 0.8,
"startDate": "2026-05-01",
"endDate": "2026-10-31"
}
}'Response (201 Created)
json
{
"data": {
"id": "clx8p9q0r1s2t3u4v5w6",
"name": "Elena Vasquez",
"email": "elena@design-studio.io",
"contractorType": "individual",
"companyId": null,
"startDate": "2026-05-01",
"endDate": "2026-10-31",
"managerId": "clx9m4n5o6p7q8r9",
"geographyId": "clx1e2n3g4r5o0t6u7v8",
"rateType": "daily",
"rate": 950.00,
"currencyCode": "EUR",
"createdAt": "2026-04-08T09:00:00Z",
"updatedAt": "2026-04-08T09:00:00Z"
}
}What happened
- The contractor record was created with a 6-month engagement window.
- A rate adjustment of 950 EUR/day was created effective 2026-05-01.
- A team assignment at 0.8 FTE was created matching the engagement dates.
- All three records were created atomically.
Set Up a Project with Team Allocations
Create a project and allocate teams to it.
Step 1: Create the project
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/projects" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Payment Gateway Migration",
"projectCode": "PAY-MIG",
"description": "Migrate from legacy payment processor to Stripe Connect.",
"startDate": "2026-05-01",
"endDate": "2026-11-30",
"lifecycleStageId": "clx5a6b7c8d9e0f1g2h3",
"estimatedCost": 380000,
"priority": 1
}'Response (201 Created)
json
{
"data": {
"id": "clx4m5n6o7p8q9r0s1t2",
"name": "Payment Gateway Migration",
"projectCode": "PAY-MIG",
"description": "Migrate from legacy payment processor to Stripe Connect.",
"startDate": "2026-05-01",
"endDate": "2026-11-30",
"ownerUserId": null,
"lifecycleStageId": "clx5a6b7c8d9e0f1g2h3",
"priority": 1,
"estimatedCost": 380000.00,
"createdAt": "2026-04-08T10:00:00Z",
"updatedAt": "2026-04-08T10:00:00Z"
}
}Step 2: Allocate the Payments team (primary)
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/assignments/teams" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"teamId": "clx7p8r9o0d1e2f3g4h5",
"projectId": "clx4m5n6o7p8q9r0s1t2",
"fte": 3.0,
"startDate": "2026-05-01",
"endDate": "2026-11-30",
"role": "Primary",
"costCategory": "CapEx"
}'Step 3: Allocate the Platform team (support)
bash
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/assignments/teams" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"teamId": "clx6t7u8v9w0x1y2z3a4",
"projectId": "clx4m5n6o7p8q9r0s1t2",
"fte": 1.0,
"startDate": "2026-05-01",
"endDate": "2026-08-31",
"role": "Support",
"costCategory": "CapEx"
}'What happened
- A project was created with a 7-month timeline, estimated cost, and lifecycle stage.
- The Payments team was allocated at 3.0 FTE for the full project duration as the primary team. This means 3 full-time equivalent people from that team are dedicated to the project.
- The Platform team was allocated at 1.0 FTE for the first 4 months as support.
- Both allocations are categorised as CapEx for financial reporting.
- Flowstate will use these allocations to calculate project cost attribution based on the team members' salaries.