Managing timesheets
Timesheet API Overview
This guide explains how team members can create, update, submit and delete timesheets using the Oyster API, along with who can confirm them and how flexible arrangements work.
Note for Reseller partners using the API - you will need to use the X-Oyster-Customer-Id
header for all of the calls in this guide. For more information, see the Reseller guide to using the Oyster API.
Definitions
Timesheet: a document used to track the time a particular engagement has worked or not worked during a certain period.
Timesheet entry types: the individual records within a timesheet that document specific periods of work. They can be categorized by different types such as:
- Regular - Regular working hours
- On Call Active - Hours worked while on call
- Overtime - Overtime hours
- Night work - Hours worked during night shifts
Timesheet Lifecycle & Capabilities
- For more information about timesheet user actions, see this guide.
- Timesheets can use either the default work schedule or a custom schedule (which can also be saved as the new default).
- Available entry types can be retrieved from the Retrieve time tracking sources by source type for engagement endpoint, by giving
source_type
with valueentry_types
. - Timesheets follow a defined status flow:
PENDING_SUBMISSION
→SUBMITTED
→PENDING_APPROVAL
→APPROVED
- Timesheets can be deleted if they are still in the
PENDING_SUBMISSION
orSUBMITTED
state.
1. Creating a Timesheet
To create a timesheet there are two options: using the default schedule the team member has in Oyster or by using a custom schedule.
1.1. Using the Default Work Schedule
You can create a timesheet using the default schedule assigned to the team member with Create a time sheet endpoint. For this, the request is sent with just engagementId
and timePeriod
. Time period should be the current month. If a timesheet is already created, this will result in error with status 422.
Example request
curl --request POST \
--url https://api.oysterhr.com/v0.1/time_tracking/time_sheets/ \
--header 'accept: application/json' \
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE' \
--header 'content-type: application/json' \
--data '
{
"engagementId": "eng123",
"timePeriod": {
"month": "5",
"year": "2025"
}
}
'
Example error
curl --request POST \
--url https://api.oysterhr.com/v0.1/time_tracking/time_sheets/ \
--header 'accept: application/json' \
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE' \
--header 'content-type: application/json' \
--data '
{
"error": {
"message": "A time sheet for this engagement and time period already exists"
}
}
'
1.2. Getting Entry Types & Work Schedule
If you want to know a team member’s current custom work schedule, you can retrieve it with the Retrieve time tracking rulesets for engagement endpoint. This endpoint can also provide the timesheet entry types available for a given team member which you can use to define a timesheet’s or the default work schedule, and update entries.
Example request
curl --request GET \
--url https://api.oysterhr.com/v0.1/time_tracking/rulesets/eng123 \
--header 'accept: application/json' \
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE' \
--header 'content-type: application/json'
Response
In response there are two sources: entry_types
and work_schedule
entry_types
data is an array that provides a list of values and titles for the available entry typeswork_schedule
data provides a weekly schedule type with entries organized by weekdaybreakInterval
is an optional value and can be missing for the entry. The specific dates shown in this example are not relevant - only the time values matter.
{
"data": {
"rules": {},
"ruleset_id": "eng123",
"schemas": {},
"scope": {},
"sources": {
"entry_types": {
"data": [
{
"value": "REGULAR",
"title": "Regular hours"
},
{
"value": "ON_CALL_ACTIVE",
"title": "On-call active"
},
{
"value": "OVERTIME",
"title": "Overtime"
}
]
},
"work_schedule": {
"data": {
"weekly": [
{
"day": "MONDAY",
"type": "REGULAR",
"present": {
"fromTime": "1970-01-01T09:00:00Z",
"toTime": "1970-01-0T18:00:00Z"
},
"breakInterval": {
"fromTime": "1970-01-0T13:00:00Z",
"toTime": "1970-01-0T14:00:00Z"
}
},
{
"day": "MONDAY",
"type": "NIGHT_WORK",
"present": {
"fromTime": "1970-01-0T22:00:00Z",
"toTime": "1970-01-0T00:00:00Z"
},
"breakInterval": null
},
{
"day": "TUESDAY",
"type": "REGULAR",
"present": {
"fromTime": "1970-01-0T09:00:00Z",
"toTime": "1970-01-0T18:00:00Z"
},
"breakInterval": {
"fromTime": "1970-01-0T13:00:00Z",
"toTime": "1970-01-0T14:00:00Z"
}
}
]
}
}
}
}
}
1.3. Create timesheet using custom schedule
To create a timesheet using a custom work schedule, include a workSchedule
attribute in your request. To created time sheet a Create a time sheet endpoint should be used. Additionally there is an option to save it as the new default one for the team member, by having setAsDefault
attribute’s value true
.
Request payload example
This custom schedule shows that the team member works on Mondays and Tuesdays, with an additional night shift on Mondays.
{
engagementId: "eng123",
timePeriod: {
month: "5",
year: "2025",
},
workSchedule: {
weekly: [
{
day: "MONDAY",
type: "REGULAR",
present: {
fromTime: "09:00",
toTime: "18:00",
},
breakInterval: {
fromTime: "13:00",
toTime: "14:00",
}
},
{
day: "MONDAY",
type: "NIGHT_WORK",
present: {
fromTime: "22:00",
toTime: "00:00",
},
breakInterval: null
},
{
day: "TUESDAY",
type: "REGULAR",
present: {
fromTime: "09:00",
toTime: "18:00",
},
breakInterval: {
fromTime: "13:00",
toTime: "14:00",
}
}
],
setAsDefault: true
}
}
1.4. Response of the timesheet creation endpoint
Whether creating a timesheet with or without a work schedule, the response format remains the same.
The response includes the following key information:
- Timesheet ID and engagement details (ID, name, type, country)
- Time period information (year and month)
- Status details (current status, status reason, lock state)
- Amendment information (whether it's an amendment, has amendments, related IDs)
- Creation and approval details (who created, submitted and confirmed)
- Work summary showing duration by work type (e.g., regular hours)
- Detailed totals including:
- Total hours worked
- Hours worked on holidays
- Weekend and weekday hours
- Time not worked
- Time off hours
- Time off entries list
- Detailed entries for each work period, including:
- Entry ID and timesheet ID
- Date and type
- Present times (from/to)
- Break intervals
- Work duration
Response
{
"data": {
"id": "W2hbJV6b",
"engagement": {
"engagementId": "eng123",
"name": "Cris Ward",
"type": "EMPLOYMENT",
"countryCode": "US"
},
"timePeriod": {
"year": "2021",
"month": "1"
},
"status": "PENDING_SUBMISSION",
"statusReason": null,
"locked": false,
"isAmendment": false,
"hasAmendmentTimeSheet": false,
"amendmentTimeSheetId": null,
"originalTimeSheetId": null,
"createdBy": "TEAM_MEMBER",
"submittedBy": null,
"confirmedBy": null,
"flexibleWorkArrangement": false,
"workSummary": [
{
"type": "REGULAR",
"duration": {
"hours": 28,
"minutes": 0
}
},
{
"type": "NIGHT_WORK",
"duration": {
"hours": 8,
"minutes": 0
}
}
],
"totals": {
"worked": {
"hours": 28,
"minutes": 0
},
"workedOnHolidays": {
"hours": 0,
"minutes": 0
},
"workedOnWeekends": {
"hours": 0,
"minutes": 0
},
"workedOnWeekdays": {
"hours": 28,
"minutes": 0
},
"notWorked": {
"hours": 220,
"minutes": 0
},
"timeOff": {
"hours": 0,
"minutes": 0
}
},
"timeOffs": [],
"entries": [
{
"id": "zbmE5kPj",
"timeSheetId": "W2hbJV6b",
"date": "2025-05-05",
"type": "REGULAR",
"notes": null,
"present": {
"fromTime": "2025-05-05T09:00:00Z",
"toTime": "2025-05-05T18:00:00Z",
"duration": {
"hours": 8,
"minutes": 0
}
},
"breakInterval": {
"fromTime": "2025-05-05T013:00:00Z",
"toTime": "2025-05-05T14:00:00Z",
"duration": {
"hours": 1,
"minutes": 0
}
},
"workDuration": {
"hours": 8,
"minutes": 0
}
},
...
]
}
}
2. Updating timesheet entries
Timesheet entries can be updated by team members when the timesheet status is pending_submission
or submitted
, indicating that the timesheet is not yet locked.
2.1. Retrieving only entry types from the rulesets
Before updating timesheet entries, you need to know the available entry types to provide team members with the correct options for selection using Retrieve time tracking sources by source type for engagement endpoint with source_type
being entry_types
.
Example request
curl --request GET \
--url https://api.oysterhr.com/v0.1/time_tracking/rulesets/eng123/sources/entry_types \
--header 'accept: application/json' \
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE' \
--header 'content-type: application/json'
Response
{
"data": [
{
"value": "REGULAR",
"title": "Regular hours"
},
{
"value": "ON_CALL_ACTIVE",
"title": "On-call active"
},
{
"value": "OVERTIME",
"title": "Overtime"
}
]
}
2.2. Creating, updating and deleting timesheet entries
Timesheet entries can be modified in the following ways
- A new timesheet entry can be created using Create time sheet entry endpoint
- An existing timesheet entry can be updated using Update time sheet entry endpoint
- An existing timesheet entry can be deleted using Delete time sheet entry endpoint
3. Submitting a timesheet
When timesheet is ready for submission, with all the necessary modifications made, it can be submitted using Submit time sheet endpoint.
Example request
curl --request POST \
--url https://api.oysterhr.com/v1/time_tracking/time_sheets/W2hbJV6b/submit \
--header 'accept: application/json' \
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE' \
--header 'content-type: application/json'
Response
When successful, response code is 200
{}
4. Confirming timesheets
When the month ends and timesheet is locked and no more edits allowed, its status is changed to pending_approval
and timesheet is available for review.
Example request
curl --request POST \
--url https://api.oysterhr.com/v1/time_tracking/time_sheets/W2hbJV6b/confirm \
--header 'accept: application/json' \
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE' \
--header 'content-type: application/json'
Response
When successful, response code is 200
{}
5. Deleting a timesheet
Timesheets can be deleted using Delete an individual time sheet endpoint when they are in the state pending_submission
or submitted
(this means it’s not locked and edits can be made).
Example request
curl --request DELETE \
--url https://api.oysterhr.com/v1/time_tracking/time_sheets/W2hbJV6b \
--header 'accept: application/json' \
--header 'authorization: Bearer BEARER_TOKEN_GOES_HERE' \
--header 'content-type: application/json'
Response
When successful, response code is 200
6. Using flexible arrangement timesheets
Flexible arrangement timesheets are only available in France. To distinguish flexible arrangement timesheets from regular ones, the parameter flexibleWorkArrangement
is set to true.
These timesheets have several limitations:
- Only
REGULAR
type entries can be created - For flexible work arrangement timesheets only create and delete entry endpoints can be used; the update endpoint is not available.
- Entry present interval is fixed at "09:00-17:00" (8 Hours) without break intervals, as the system records worked days rather than hours (half-day time off being the only exception)
- Entries cannot be created or deleted for the days with time offs
- Each day can have only one entry if team member worked that day
- When creating a timesheet and providing a work schedule, we are only capturing days, for the time period we automatically apply interval from 09:00-17:00
Updated about 10 hours ago