A Phase Code is a cost code under a Job or Meet Point. A worker picks one when they clock in. A Work Order has no Phase Codes.
| To do this | Use |
|---|---|
| Set the whole list of one activity | phaseCodes on PATCH /activities/{activityId} |
| Add one Phase Code | POST /activities/{activityId}/phase-codes |
| Change or retire one Phase Code | PATCH /activities/{activityId}/phase-codes/{phaseCodeId} |
| Change one Phase Code on many Jobs | POST /phase-codes/apply |
Scopes: activity:read to read, activity:write to write. Only the owner of an activity
can change its Phase Codes. See Ownership.
Back to the index · Conventions · Errors
Every endpoint can also return 401 (no key, or a key that is not valid, revoked or
expired) and 503 (service_unavailable or instance_mismatch). The tables below list
only the other statuses.
The Phase Code object
| Field | Type | Description |
|---|---|---|
phaseCodeId | integer | The Phase Code's id |
activityId | integer | The Job or Meet Point that it belongs to |
code | string | Trimmed only. Case and inner spaces are kept. It never changes. |
description | string or null | The effective description: an administrator's override where one is set, else the owner's |
isActive | boolean | False when the Phase Code is retired |
sourceSystem | string | The owner |
isLocallyOverridden | boolean | True when an administrator overrides a field |
overridden | array of strings | The fields that an override covers |
GET /activities/{activityId}/phase-codes
Lists every Phase Code of one Job or Meet Point, active and inactive, ordered by code.
GET /activities shows only the active ones.
Scope: activity:read
Path parameters
| Name | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
activityId | integer | Yes | 1 or more | The activity's id |
Example request
curl "$TIMECARD_BASE_URL/activities/48213/phase-codes" \
-H "X-API-Key: $TIMECARD_API_KEY"
Example response 200 OK
The list is in records at the top level, with no total and no paging.
{
"status": 200,
"message": "Success: phase codes retrieved.",
"records": [
{
"phaseCodeId": 48214,
"activityId": 48213,
"code": "01-100",
"description": "Mobilization",
"isActive": true,
"sourceSystem": "acme-erp",
"isLocallyOverridden": false,
"overridden": []
},
{
"phaseCodeId": 48230,
"activityId": 48213,
"code": "03-300",
"description": "Concrete",
"isActive": false,
"sourceSystem": "acme-erp",
"isLocallyOverridden": false,
"overridden": []
}
]
}
Errors
| Status | code | When |
|---|---|---|
| 400 | validation_failed | activityId is not a positive integer |
| 403 | insufficient_scope | The key does not have activity:read |
| 404 | not_found | No activity has this id within the key's companies |
| 500 | internal_error | The Phase Codes could not be read |
POST /activities/{activityId}/phase-codes
Adds one Phase Code to a Job or Meet Point. You do not send the whole list again. The new Phase Code is owned by your key's source system and copies the activity's division.
Scope: activity:write
Path parameters
| Name | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
activityId | integer | Yes | 1 or more | The Job or Meet Point |
Request body
| Name | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
code | string | Yes | 1 to 50 characters | The Phase Code. Trimmed only. Case and inner spaces are kept. | |
description | string or null | No | Max 200 characters | The description. Blank is stored as null. |
Example request
curl -X POST "$TIMECARD_BASE_URL/activities/48213/phase-codes" \
-H "X-API-Key: $TIMECARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "code": "03-300", "description": "Concrete" }'
Example response 201 Created
{
"status": 201,
"message": "Success: phase code added.",
"record": {
"phaseCodeId": 48230,
"activityId": 48213,
"code": "03-300",
"description": "Concrete",
"isActive": true,
"sourceSystem": "acme-erp",
"isLocallyOverridden": false,
"overridden": []
}
}
Errors
| Status | code | When |
|---|---|---|
| 400 | validation_failed | activityId is not a positive integer, the body is not valid, or the activity is a Work Order |
| 403 | insufficient_scope | The key does not have activity:write |
| 403 | forbidden | The key has no source system |
| 404 | not_found | No activity has this id within the key's companies |
| 409 | conflict | The activity belongs to another source system. Or the activity already has this code, active or not. To bring back an inactive code, use PATCH with "isActive": true. |
| 500 | internal_error | The Phase Code could not be added |
PATCH /activities/{activityId}/phase-codes/{phaseCodeId}
Changes one Phase Code and leaves the others alone. Send description, isActive, or
both. The code cannot change.
Scope: activity:write
Path parameters
| Name | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
activityId | integer | Yes | 1 or more | The Job or Meet Point | |
phaseCodeId | integer | Yes | 1 or more | One of the activity's Phase Codes |
Request body
Send at least one field. An empty body gets 400.
| Name | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
description | string or null | No | Max 200 characters | The new description. Blank or null clears it. | |
isActive | boolean | No | false retires the Phase Code. true brings it back. |
The body can also hold revert, but only an administrator can use it. A key that sends
revert gets 400.
Example request
curl -X PATCH "$TIMECARD_BASE_URL/activities/48213/phase-codes/48230" \
-H "X-API-Key: $TIMECARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "description": "Concrete flatwork", "isActive": true }'
Example response 200 OK
{
"status": 200,
"message": "Success: phase code updated.",
"record": {
"phaseCodeId": 48230,
"activityId": 48213,
"code": "03-300",
"description": "Concrete flatwork",
"isActive": true,
"sourceSystem": "acme-erp",
"isLocallyOverridden": false,
"overridden": []
}
}
When the values you send are already stored, nothing changes and the message is
Success: nothing changed.
Errors
| Status | code | When |
|---|---|---|
| 400 | validation_failed | An id is not a positive integer, the body is empty or not valid, or the body sends code |
| 403 | insufficient_scope | The key does not have activity:write |
| 403 | forbidden | The key has no source system |
| 404 | not_found | No activity has this id within the key's companies, or phaseCodeId is not one of its Phase Codes |
| 409 | conflict | The Phase Code belongs to another source system. Or it is the last active Phase Code of a Meet Point, which must keep one. |
| 500 | internal_error | The Phase Code could not be updated |
GET /phase-codes
Lists each distinct Phase Code across one company's Jobs, with the number of Jobs that use it. Use it to keep one cost-code list in step across every Job. Codes are grouped without case. Ordered by code.
Scope: activity:read
Query parameters
| Name | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
companyCode | string | When the key covers more than one company | Max 10 characters | The company | |
q | string | No | Max 100 characters | Keeps a code when the code, or a Job's description of it, contains this text | |
isActive | string | No | Both | true, false | true keeps codes that are active on at least one active Job. false keeps codes that are active on none. |
limit | integer | No | 25 | 1 to 100 | Page size |
offset | integer | No | 0 | 0 or more | Codes to skip |
Example request
curl "$TIMECARD_BASE_URL/phase-codes?companyCode=ACME&q=01-&limit=100" \
-H "X-API-Key: $TIMECARD_API_KEY"
Example response 200 OK
{
"status": 200,
"message": "Success: phase codes retrieved.",
"record": {
"total": 1,
"records": [
{
"code": "01-100",
"description": "Mobilization",
"descriptions": ["Mobilization", "Mobilisation"],
"jobCount": 14,
"activeJobCount": 9
}
]
}
}
Response record fields
| Field | Type | Description |
|---|---|---|
code | string | The Phase Code |
description | string or null | The description that most Jobs use. Null when no Job gives one. |
descriptions | array of strings | Every distinct description, most used first |
jobCount | integer | Jobs that have the code, active or not |
activeJobCount | integer | Jobs where the Job and the code are both active |
Errors
| Status | code | When |
|---|---|---|
| 400 | validation_failed | A query parameter is not valid, or companyCode is missing when the key covers several companies |
| 403 | insufficient_scope | The key does not have activity:read |
| 403 | forbidden | A key bound to one company names another |
| 404 | not_found | companyCode names no active company that the key can reach |
| 500 | internal_error | The Phase Codes could not be read |
POST /phase-codes/apply
Adds, renames, deactivates or reactivates one Phase Code on many Jobs at once. Name the
Jobs in activityIds, or send "allActiveJobs": true. Send exactly one of the two.
Scope: activity:write
| A Job | Result |
|---|---|
| Does not have the code | The code is added, active. If you send "isActive": false, the Job is left unchanged. |
| Has the code | It takes the description and isActive that you send, and keeps what you do not send |
| Belongs to another source system | Refused |
| Is not a Job of the company | Refused |
Each Job is saved on its own and gets its own result. One refused Job does not stop the
others. Send "dryRun": true to see the results first. A dry run saves nothing.
Request body
| Name | Type | Required | Default | Allowed values | Description |
|---|---|---|---|---|---|
companyCode | string | When the key covers more than one company | Max 10 characters | The company | |
code | string | Yes | 1 to 50 characters | The Phase Code. Trimmed only. | |
description | string or null | No | Max 200 characters | Written where the code is added or present. Leave it out to keep each Job's description. | |
isActive | boolean | No | true reactivates, false deactivates. Leave it out to add a missing code as active and keep a present one's state. | ||
activityIds | array of integers | One of activityIds or allActiveJobs | 1 to 1,000 items | The Jobs. Repeats are dropped. | |
allActiveJobs | boolean | One of activityIds or allActiveJobs | true | Apply to every active Job of the company | |
dryRun | boolean | No | false | true reports what would happen and saves nothing |
Example request
curl -X POST "$TIMECARD_BASE_URL/phase-codes/apply" \
-H "X-API-Key: $TIMECARD_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"companyCode": "ACME",
"code": "99-900",
"description": "Safety meeting",
"isActive": true,
"allActiveJobs": true,
"dryRun": true
}'
Example response 200 OK
{
"status": 200,
"message": "Success: dry run, 1 of 2 jobs would change. Nothing was saved.",
"record": {
"dryRun": true,
"results": [
{ "activityId": 48213, "jobCode": "J-2026-104", "outcome": "added" },
{
"activityId": 48400,
"jobCode": "J-2026-131",
"outcome": "refused",
"message": "J-2026-131 is owned by timecard, and its Phase Codes belong to it."
}
]
}
}
Without dryRun, the message reads Success: <n> of <total> jobs changed. Do not parse
the message text of a result. It can change.
Response fields
| Field | Type | Description |
|---|---|---|
record.dryRun | boolean | True when nothing was saved |
record.results[].activityId | integer | The Job |
record.results[].jobCode | string or null | The Job's code. Null when activityId names no Job of the company. |
record.results[].outcome | string | added, updated, reactivated, deactivated, unchanged or refused |
record.results[].message | string | Why the Job was refused, or why nothing changed. Present only then. |
Results are in the order sent for activityIds, or by Job code for allActiveJobs.
Errors
| Status | code | When |
|---|---|---|
| 400 | validation_failed | The body is not valid, sends both or neither of activityIds and allActiveJobs, or companyCode is missing when the key covers several companies |
| 403 | insufficient_scope | The key does not have activity:write |
| 403 | forbidden | A key bound to one company names another |
| 404 | not_found | companyCode names no active company that the key can reach |
| 500 | internal_error | The Phase Code could not be applied |