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 value entry_types.
  • Timesheets follow a defined status flow:
    • PENDING_SUBMISSIONSUBMITTEDPENDING_APPROVALAPPROVED
  • Timesheets can be deleted if they are still in the PENDING_SUBMISSION or SUBMITTED 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 types
  • work_schedule data provides a weekly schedule type with entries organized by weekday
  • breakInterval 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

  1. A new timesheet entry can be created using Create time sheet entry endpoint
  2. An existing timesheet entry can be updated using Update time sheet entry endpoint
  3. 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:

  1. Only REGULAR type entries can be created
  2. For flexible work arrangement timesheets only create and delete entry endpoints can be used; the update endpoint is not available.
  3. 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)
  4. Entries cannot be created or deleted for the days with time offs
  5. Each day can have only one entry if team member worked that day
  6. 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