Contractors
A contractor is a person or firm you engage outside payroll. Dated rate changes live in contractor rate adjustments. Team and project time lives in assignments.
- 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.
"800". Send them as numbers. - Scenarios. Live data only. See Scenarios.
The contractor object
| Field | Type | Description |
|---|---|---|
id | string | Flowstate ID. Read-only. |
externalId | string | null | Your identifier. Unique among your contractors, at most 255 characters, and can’t look like a Flowstate ID. |
name | string | Person’s or firm’s name. |
email | string | null | Contact email. |
contractorType | string | Free text, e.g. individual, agency, consultancy. |
companyId | string | null | ID of the contractor record for the firm they work through. |
startDate | datetime | null | First day of the engagement. |
endDate | datetime | null | Last day. null = open-ended. |
managerId | string | null | Managing employee’s ID. |
jobRoleId | string | null | Job role ID. |
workTypeId | string | null | Resource type ID. Read-only over this API. |
geographyId | string | null | Location ID. |
rateType | string | null | Period of rate: hourly, daily, monthly or annually. |
rate | decimal string | null | Rate on the contractor record. Dated changes are rate adjustments. |
currencyCode | string | null | ISO 4217 code for rate. |
organizationId, 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/contractors | View Contractors (team_contractors_view) |
GET | /api/v1/org/:orgId/contractors/:id | View Contractors (team_contractors_view) |
POST | /api/v1/org/:orgId/contractors | Create Contractors (team_contractors_create) |
PATCH | /api/v1/org/:orgId/contractors/:id | Update Contractors (team_contractors_update) |
DELETE | /api/v1/org/:orgId/contractors/:id | Delete Contractors (team_contractors_delete) |
Sub-resources: rate adjustments at /contractors/:contractorId/rate-adjustments and custom attribute values at /contractors/:id/custom-attributes.
List contractors
GET /api/v1/org/:orgId/contractors
Permission: View Contractors (team_contractors_view). With include=currentRate or include=rateHistory, you also need View Detailed Financials (financials_view_detailed).
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 name or email. | |
sortBy | string | name | name, email, contractorType or startDate. Any other value sorts by name. |
sortDir | string | asc | asc or desc. |
include | string | Comma-separated. See Includes. |
Includes
| Key | Adds | Needs |
|---|---|---|
currentRate | The rate adjustment with the latest effectiveDate, or null. | financials_view_detailed |
rateHistory | Every rate adjustment, latest effectiveDate first. | financials_view_detailed |
assignments | Every team and project assignment, past and current, earliest startDate first. Each is { id, type, targetId, fte, startDate, endDate, role, createdAt, updatedAt }, where type is team or project. |
Example request
curl "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/contractors?sortBy=startDate&sortDir=desc" \
-H "Authorization: Bearer private_..."
Example response
{
"data": [
{
"id": "cmf2k8x1q0060ab2cd3ef4gh5",
"organizationId": "cmf2k8x1q0000ab2cd3ef4gh5",
"name": "Priya Sharma",
"externalId": "CTR-311",
"email": "priya@sharmaconsulting.co.uk",
"contractorType": "individual",
"companyId": null,
"geographyId": "cmf2k8x1q0012ab2cd3ef4gh5",
"rateType": "daily",
"rate": "800",
"currencyCode": "GBP",
"startDate": "2025-01-15T00:00:00.000Z",
"endDate": "2026-12-31T00:00:00.000Z",
"managerId": "cmf2k8x1q0001ab2cd3ef4gh5",
"jobRoleId": "cmf2k8x1q0010ab2cd3ef4gh5",
"workTypeId": null,
"sourceSystem": "api",
"sourceSystemId": null,
"lastSyncedAt": "2025-01-10T08:00:00.000Z",
"metadata": {},
"createdAt": "2025-01-10T08:00:00.000Z",
"updatedAt": "2026-03-20T09:00:00.000Z"
}
],
"meta": { "page": 1, "limit": 20, "total": 1, "hasNextPage": false }
}
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | Invalid query parameter, or an include key not in the table. |
403 | FORBIDDEN | A rate include without financials_view_detailed. |
Retrieve a contractor
GET /api/v1/org/:orgId/contractors/:id
Permission: View Contractors (team_contractors_view). The rate includes also need financials_view_detailed.
Takes the same include parameter as List contractors. The response is the contractor object, with customAttributes and any includes.
Example request
curl "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/contractors/CTR-311?include=assignments" \
-H "Authorization: Bearer private_..."
Example response
{
"data": {
"id": "cmf2k8x1q0060ab2cd3ef4gh5",
"name": "Priya Sharma",
"externalId": "CTR-311",
"…": "other contractor fields",
"customAttributes": [],
"assignments": [
{
"id": "cmf2k8x1q0052ab2cd3ef4gh5",
"type": "team",
"targetId": "cmf2k8x1q0020ab2cd3ef4gh5",
"fte": 1,
"startDate": "2025-01-15T00:00:00.000Z",
"endDate": "2026-12-31T00:00:00.000Z",
"role": null,
"createdAt": "2025-01-10T08:00:00.000Z",
"updatedAt": "2025-01-10T08:00:00.000Z"
}
]
}
}
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | An include key not in the table. |
403 | FORBIDDEN | A rate include without financials_view_detailed. |
404 | NOT_FOUND | No contractor with that ID or externalId. |
Create a contractor
POST /api/v1/org/:orgId/contractors
Permission: Create Contractors (team_contractors_create).
The contractor is created in one transaction with any nested rateAdjustment, teamAssignment and projectAssignment you send.
Body parameters
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | |
contractorType | string | Yes | e.g. individual. |
externalId | string | null | No | At most 255 characters. Can’t look like a Flowstate ID. |
email | string | null | No | Valid email address. |
companyId | string | null | No | Flowstate ID of the firm’s contractor record. |
startDate | date | null | No | |
endDate | date | null | No | |
managerId | string | null | No | Employee ID or externalId. |
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. |
geographyId | string | null | No | Location ID. |
rateType | string | null | No | hourly, daily, monthly or annually. |
rate | number | null | No | ≥ 0. |
currencyCode | string | null | No | Three uppercase letters. |
rateAdjustment | object | No | First dated rate. See rateAdjustment. |
teamAssignment | object | No | Same shape as on employees. |
projectAssignment | object | No | Same shape as on employees. |
rateAdjustment
| Field | Type | Required | Description |
|---|---|---|---|
effectiveDate | date | Yes | |
rateType | string | Yes | hourly, daily, monthly or annually. |
rate | number | Yes | ≥ 0. |
currencyCode | string | Yes | Three uppercase letters. |
reason | string | null | No |
Example request
curl -X POST "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/contractors" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Marcus Johnson",
"email": "marcus@devshop.io",
"contractorType": "individual",
"externalId": "CTR-412",
"startDate": "2026-10-01",
"endDate": "2027-03-31",
"managerId": "EMP-1042",
"rateType": "daily",
"rate": 650,
"currencyCode": "GBP",
"rateAdjustment": {
"effectiveDate": "2026-10-01",
"rateType": "daily",
"rate": 650,
"currencyCode": "GBP",
"reason": "Initial rate"
},
"teamAssignment": {
"teamId": "cmf2k8x1q0020ab2cd3ef4gh5",
"fte": 1,
"startDate": "2026-10-01",
"endDate": "2027-03-31"
}
}'
Example response
201 Created. When you sent them, the created records come back under rateAdjustment, teamAssignment and projectAssignment.
{
"data": {
"id": "cmf2k8x1q0061ab2cd3ef4gh5",
"organizationId": "cmf2k8x1q0000ab2cd3ef4gh5",
"name": "Marcus Johnson",
"externalId": "CTR-412",
"email": "marcus@devshop.io",
"contractorType": "individual",
"companyId": null,
"geographyId": null,
"rateType": "daily",
"rate": "650",
"currencyCode": "GBP",
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2027-03-31T00:00:00.000Z",
"managerId": "cmf2k8x1q0001ab2cd3ef4gh5",
"jobRoleId": null,
"workTypeId": null,
"sourceSystem": "api",
"sourceSystemId": null,
"lastSyncedAt": "2026-09-14T09:00:00.000Z",
"metadata": {},
"createdAt": "2026-09-14T09:00:00.000Z",
"updatedAt": "2026-09-14T09:00:00.000Z",
"rateAdjustment": {
"id": "cmf2k8x1q0065ab2cd3ef4gh5",
"liveContractorId": "cmf2k8x1q0061ab2cd3ef4gh5",
"effectiveDate": "2026-10-01T00:00:00.000Z",
"rateType": "daily",
"rate": "650",
"currencyCode": "GBP",
"reason": "Initial rate",
"…": "other rate adjustment fields"
},
"teamAssignment": {
"id": "cmf2k8x1q0053ab2cd3ef4gh5",
"liveContractorId": "cmf2k8x1q0061ab2cd3ef4gh5",
"liveTeamId": "cmf2k8x1q0020ab2cd3ef4gh5",
"fte": 1,
"startDate": "2026-10-01T00:00:00.000Z",
"endDate": "2027-03-31T00:00:00.000Z",
"…": "other assignment fields"
}
}
}
Errors
| Status | Code | When |
|---|---|---|
400 | VALIDATION_ERROR | The body is invalid, teamAssignment.teamId or projectAssignment.projectId matches nothing, or externalId is already used. |
404 | NOT_FOUND | managerId matches no employee, or jobRole has only an externalId that matches no role. |
501 | NOT_IMPLEMENTED | scenarioId was sent. |
Update a contractor
PATCH /api/v1/org/:orgId/contractors/:id
Permission: Update Contractors (team_contractors_update).
Takes the create body with every field optional. Send only what changes. null clears a nullable field. Nested rateAdjustment, teamAssignment and projectAssignment always add a record and never change an existing one.
Example request
curl -X PATCH "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/contractors/CTR-311" \
-H "Authorization: Bearer private_..." \
-H "Content-Type: application/json" \
-d '{ "endDate": "2027-06-30" }'
Example response
200 OK. The response is the updated contractor, plus any nested records created.
Errors
As for Create a contractor, plus 404 NOT_FOUND when no contractor has that ID or externalId.
Delete a contractor
DELETE /api/v1/org/:orgId/contractors/:id
Permission: Delete Contractors (team_contractors_delete).
Permanent. The following are deleted with the contractor:
- rate adjustments
- assignments
- effort records
Where another record points to the contractor as its firm (companyId) or as a vacancy filler, that link is set to null. To end an engagement, set endDate instead.
Example request
curl -X DELETE "https://{tenant}.flowstate.inc/api/v1/org/{orgId}/contractors/CTR-311" \
-H "Authorization: Bearer private_..."
Example response
{ "data": { "id": "cmf2k8x1q0060ab2cd3ef4gh5", "deleted": true } }
Errors
| Status | Code | When |
|---|---|---|
404 | NOT_FOUND | No contractor with that ID or externalId. |
501 | NOT_IMPLEMENTED | scenarioId was sent. |
Tasks
End an engagement early
PATCH /contractors/CTR-311 with { "endDate": "2026-11-30" }. Assignments keep their own end dates, so end those through Assignments.
Change a rate from a date
Add a rate adjustment with the new effectiveDate. Earlier rates stay in the history.
Related
- Contractor rate adjustments · Assignments · Custom attributes · Errors
- Recipe: Add a contractor
- Webhooks: writes send
contractor,contractor_rate,contractor_team_allocationandcontractor_project_allocationevents. See Webhooks.