# Create and manage time entries

> Create and manage time entries in Rippling using the Time Entry API.

Create and manage time entries in Rippling using the Time Entry API.

## Overview

The Time Entry API lets you create, update, and delete time entries. Each time entry represents a single work session or shift, and is reflected on timecards in Rippling's Time & Attendance.

### Available endpoints

| Endpoint | Description |
| --- | --- |
| [GET List time entries](https://developer.rippling.com/documentation/rest-api/reference/list-time-entries) | Lists time entries. |
| [POST Create a new time entry](https://developer.rippling.com/documentation/rest-api/reference/create-time-entries) | Creates a new time entry. |
| [GET Retrieve a specific time entry](https://developer.rippling.com/documentation/rest-api/reference/get-time-entries) | Retrieves a specific time entry by ID. |
| [PATCH Update a time entry](https://developer.rippling.com/documentation/rest-api/reference/update-time-entries) | Updates a time entry. |
| [DELETE Delete a time entry](https://developer.rippling.com/documentation/rest-api/reference/delete-time-entries) | Deletes a time entry. |

* * *

## Requirements

-   The Rippling API SKU
-   An API token with the following scopes:
    -   `time-entries.read` to fetch time entries
    -   `time-entries.read-write` to fetch and manage time entries

* * *

## Fetch

Use [GET List time entries](https://developer.rippling.com/documentation/rest-api/reference/list-time-entries) to return a list of company time entries.

_Request:_

```json
curl -L -X GET 'https://rest.ripplingapis.com/time-entries/'
-H 'Accept: application/json'
-H 'Authorization: Bearer REDACTED'
```

_Response:_

```json
{
  "results": [
    {
      "id": "66a43405af71f094991bf94f",
      "created_at": "2024-09-18T16:00:21.551000+00:00",
      "updated_at": "2024-09-18T16:05:50.632000+00:00",
      "worker_id": "65699281fb7bc7c3510f44a6",
      "worker": null,
      "start_time": "2024-09-18T09:00:00-07:00",
      "end_time": "2024-09-18T17:00:00-07:00",
      "comments": [],
      "job_shifts": [
        {
          "start_time": "2024-09-18T09:00:00-07:00",
          "end_time": "2024-09-18T17:00:00-07:00",
          "original_start_time": null,
          "original_end_time": null,
          "job_codes_id": ["66b2512c4ad1db34abae1e82"],
          "is_hours_only_input": false
        }
      ],
      "breaks": [
        {
          "start_time": "2024-09-18T12:00:00-07:00",
          "end_time": "2024-09-18T12:30:00-07:00",
          "break_type_id": "66a8171c73afe1eced2f08ea"
        }
      ],
      "tags": ["projectX"],
      "idempotency_key": "unique-key-987",
      "create_extra_hours_run": false,
      "status": "DRAFT",
      "pay_period": {
        "start_date": "2024-09-16",
        "end_date": "2024-09-29",
        "pay_schedule_id": "65699295fb7bc7c3510f4075"
      },
      "time_entry_summary": {
        "regular_hours": 7.5,
        "over_time_hours": 0,
        "double_over_time_hours": 0,
        "duration": 8
      },
      "time_card_id": "66a43410af71f094991bf960",
      "time_card": null,
      "shift_input_values": []
    }
  ],
  "next_link": null
}
```

| Response field | Required | Description |
| --- | --- | --- |
| `id` | Yes | Time entry ID. |
| `created_at` | Yes | When the time entry was created. |
| `updated_at` | Yes | When the time entry was last updated. |
| `worker_id` | Yes | Worker for this entry. This is the Rippling role ID. |
| `worker` | No | Worker record. Expand `worker` to populate this. null when not expanded. |
| `start_time` | No | Start time of the entry. |
| `end_time` | No | End time of the entry. |
| `comments` | No | Comments on the entry. Each item can include `text`, `author_id`, and `created_at`. |
| `job_shifts` | No | Shifts worked during the entry. Each item includes `start_time`, `end_time`, `original_start_time`, `original_end_time`, `job_codes_id`, and `is_hours_only_input`. |
| `breaks` | No | Breaks clocked during the entry. Each item includes `start_time`, `end_time`, and `break_type_id`. |
| `tags` | No | Labels attached to the entry. |
| `idempotency_key` | No | Unique key from an external system. |
| `create_extra_hours_run` | No | Whether the entry should create an extra hours run. |
| `status` | No | `DRAFT`, `APPROVED`, `PAID`, or `FINALIZED`. |
| `pay_period` | No | Pay period for the entry: `start_date`, `end_date`, and `pay_schedule_id`. |
| `time_entry_summary` | No | Calculated hours for the entry: `regular_hours`, `over_time_hours`, `double_over_time_hours`, and `duration`. |
| `time_card_id` | No | Time card this entry belongs to. |
| `time_card` | No | Time card record. Expand `time_card` to populate this. `null` when not expanded. |
| `shift_input_values` | No | Piece-rate values collected on the entry. |

Use [GET Retrieve a specific time entry](https://developer.rippling.com/documentation/rest-api/reference/get-time-entries) to fetch a specific time entry.

* * *

## Create

Use [POST Create a new time entry](https://developer.rippling.com/documentation/rest-api/reference/create-time-entries) to create a new time entry. This time entry will populate in Rippling's Time & Attendance.

_Request:_

```json
curl -L -X POST 'https://rest.ripplingapis.com/time-entries/'
-H 'Accept: application/json'
-H 'Content-Type: application/json'
-H 'Authorization: Bearer REDACTED'
```

_Request body:_

```json
{
  "worker_id": "65699281fb7bc7c3510f44a6",
  "status": "DRAFT",
  "job_shifts": [
    {
      "start_time": "2024-09-18T09:00:00-07:00",
      "end_time": "2024-09-18T17:00:00-07:00",
      "original_start_time": null,
      "original_end_time": null,
      "job_codes_id": ["66b2512c4ad1db34abae1e82"],
      "is_hours_only_input": false
    }
  ],
  "breaks": [
    {
      "start_time": "2024-09-18T12:00:00-07:00",
      "end_time": "2024-09-18T12:30:00-07:00",
      "break_type_id": "66a8171c73afe1eced2f08ea"
    }
  ],
  "tags": ["projectX"],
  "idempotency_key": "unique-key-987"
}
```

| Request body field | Required | Description |
| --- | --- | --- |
| `worker_id` | YES | Worker for this entry. This is the Rippling role ID. |
| `duration` | NO | Hours worked, as a decimal such as `1` or `1.5`. Send this or `job_shifts`, not both. |
| `start_date` | NO | Date of a `duration` entry (`YYYY-MM-DD`). When omitted, the entry uses the date of the request. |
| `job_shifts` | NO | Shifts worked during the entry, including start and end times and [job codes](https://help.rippling.com/s/article/360060376093). `start_time` and `end_time` take precedence over `original_start_time` and `original_end_time`. Original times require start and end times. Each job code must already exist in Rippling. |
| `breaks` | NO | [Breaks](https://help.rippling.com/s/article/1500000530301) clocked during the entry. |
| `comments` | NO | Comments on the entry. Each item has `text`. |
| `tags` | NO | Labels attached to the entry. |
| `idempotency_key` | NO | Unique key from an external system. Rippling rejects a second entry with the same key. |
| `status` | NO | `DRAFT` (needs review), `APPROVED` (approved in Rippling), `PAID` (already paid, and no extra-hours pay run), or `FINALIZED` (treated like `APPROVED`). |
| `pay_period` | NO | Pay period for the entry: `start_date`, `end_date`, and `pay_schedule_id`. Periods are configured in Payroll. |
| `shift_input_values` | NO | Piece-rate values for the entry. Available with Global Payroll. |
| `create_extra_hours_run` | NO | Whether the entry should create an extra hours run. |

_Response:_

```json
{
  "id": "66a43405af71f094991bf94f",
  "created_at": "2024-09-18T16:00:21.551000+00:00",
  "updated_at": "2024-09-18T16:05:50.632000+00:00",
  "worker_id": "65699281fb7bc7c3510f44a6",
  "worker": null,
  "start_time": "2024-09-18T09:00:00-07:00",
  "end_time": "2024-09-18T17:00:00-07:00",
  "comments": [],
  "job_shifts": [
    {
      "start_time": "2024-09-18T09:00:00-07:00",
      "end_time": "2024-09-18T17:00:00-07:00",
      "original_start_time": null,
      "original_end_time": null,
      "job_codes_id": ["66b2512c4ad1db34abae1e82"],
      "is_hours_only_input": false
    }
  ],
  "breaks": [
    {
      "start_time": "2024-09-18T12:00:00-07:00",
      "end_time": "2024-09-18T12:30:00-07:00",
      "break_type_id": "66a8171c73afe1eced2f08ea"
    }
  ],
  "tags": ["projectX"],
  "idempotency_key": "unique-key-987",
  "create_extra_hours_run": false,
  "status": "DRAFT",
  "pay_period": {
    "start_date": "2024-09-16",
    "end_date": "2024-09-29",
    "pay_schedule_id": "65699295fb7bc7c3510f4075"
  },
  "time_entry_summary": {
    "regular_hours": 7.5,
    "over_time_hours": 0,
    "double_over_time_hours": 0,
    "duration": 8
  },
  "time_card_id": "66a43410af71f094991bf960",
  "time_card": null,
  "shift_input_values": []
}
```

* * *

## Update

Use [PATCH Update a time entry](https://developer.rippling.com/documentation/rest-api/reference/update-time-entries) to update a time entry. `worker_id` is required, and include only the additional fields you are changing.

_Request:_

```json
curl -L -X PATCH 'https://rest.ripplingapis.com/time-entries/66a43405af71f094991bf94f/'
-H 'Accept: application/json'
-H 'Content-Type: application/json'
-H 'Authorization: Bearer REDACTED'
```

_Request body:_

```json
{
  "worker_id": "65699281fb7bc7c3510f44a6",
  "job_shifts": [
    {
      "start_time": "2024-09-18T09:00:00-07:00",
      "end_time": "2024-09-18T18:00:00-07:00",
      "original_start_time": null,
      "original_end_time": null,
      "job_codes_id": ["66b2512c4ad1db34abae1e82"],
      "is_hours_only_input": false
    }
  ],
  "breaks": [
    {
      "start_time": "2024-09-18T12:00:00-07:00",
      "end_time": "2024-09-18T12:30:00-07:00",
      "break_type_id": "66a8171c73afe1eced2f08ea"
    }
  ]
}
```

_Response:_

```json
{
  "id": "66a43405af71f094991bf94f",
  "created_at": "2024-09-18T16:00:21.551000+00:00",
  "updated_at": "2024-09-18T18:10:12.104000+00:00",
  "worker_id": "65699281fb7bc7c3510f44a6",
  "worker": null,
  "start_time": "2024-09-18T09:00:00-07:00",
  "end_time": "2024-09-18T18:00:00-07:00",
  "comments": [],
  "job_shifts": [
    {
      "start_time": "2024-09-18T09:00:00-07:00",
      "end_time": "2024-09-18T18:00:00-07:00",
      "original_start_time": null,
      "original_end_time": null,
      "job_codes_id": ["66b2512c4ad1db34abae1e82"],
      "is_hours_only_input": false
    }
  ],
  "breaks": [
    {
      "start_time": "2024-09-18T12:00:00-07:00",
      "end_time": "2024-09-18T12:30:00-07:00",
      "break_type_id": "66a8171c73afe1eced2f08ea"
    }
  ],
  "tags": ["projectX"],
  "idempotency_key": "unique-key-987",
  "create_extra_hours_run": false,
  "status": "DRAFT",
  "pay_period": {
    "start_date": "2024-09-16",
    "end_date": "2024-09-29",
    "pay_schedule_id": "65699295fb7bc7c3510f4075"
  },
  "time_entry_summary": {
    "regular_hours": 8.5,
    "over_time_hours": 0,
    "double_over_time_hours": 0,
    "duration": 9
  },
  "time_card_id": "66a43410af71f094991bf960",
  "time_card": null,
  "shift_input_values": []
}
```

* * *

## Delete

Use [DELETE Delete a time entry](https://developer.rippling.com/documentation/rest-api/reference/delete-time-entries) to delete a time entry.

_Request:_

```json
curl -L -X DELETE 'https://rest.ripplingapis.com/time-entries/66a43405af71f094991bf94f/'
-H 'Accept: application/json'
-H 'Authorization: Bearer REDACTED'
```

A successful delete returns `204` and no response body.

* * *

## Best practices

-   **Utilize the `idempotency_key`:** This helps prevent accidental duplicate entries by ensuring that no other time entry with the same key can be created. Unable to be used with [PATCH Update a time entry](https://developer.rippling.com/documentation/rest-api/reference/update-time-entries).
-   **Provide comprehensive data:** Include all relevant optional fields to ensure that entries are as detailed and accurate as possible.
-   **Handle optional fields:** Implement checks in your integration to manage optional fields effectively, filling them only when necessary.

### Validation

A request is rejected when the hours, shifts, or breaks do not form a valid entry. Common issues to check for:

-   **Shifts:** Job shifts must cover the entry from start to end, must not overlap, and each shift’s start must be before its end. You cannot send both `duration` and `job_shifts` in the same request. Related errors include `entryMustHaveJobShifts`, `spanningJobShiftsNotPresent`, `overlappingJobShiftTimes`, and `invalidJobShiftStartGreaterThanEnd`.
-   **Breaks:** Breaks must fall inside the entry, must not overlap, and the entry cannot be only breaks. The worker must be eligible for the break type, and related errors include `overlappingBreakTimes`, `mustEndBreakBeforeEndingEntry`, `entryCannotBeOnlyBreaks`, and `ineligibleForBreakType`.
-   **The time entry:** Start must be before end, the entry cannot exceed 48 hours, and it cannot overlap another open or closed entry for that worker. Related errors include `timeEntryStartGreaterThanEnd`, `entryLongerThanFortyEightHours`, `existingEntryOverlapsWithOpenEntry`, and `existingEntryOverlapsWithClosedEntry`.
-   **Worker setup:** The worker needs to be assigned to an active Time & Attendance policy, the job code must be one they are assigned to, and the entry cannot start before their start date. Related errors include `noActivePolicyForUser`, `noActiveTimekeepingPolicy`, `employeeIneligibleForJobCode`, and `cannotCreateEntryUntilStartDate`.

### Statuses

Set the `status` upon time entry creation:

| Status | Outcome |
| --- | --- |
| `DRAFT` | Needs admin review. |
| `APPROVED` | Approved in Rippling. |
| `FINALIZED` | Treated like `APPROVED`. A finalized entry cannot be changed or unapproved. |
| `PAID` | Marks the entry manually paid and does not create an extra-hours pay run. A paid entry cannot be unapproved. |

### Pay periods

The `pay_period` field identifies the Payroll period for the entry: `start_date`, `end_date`, and `pay_schedule_id`. Those periods are configured directly in the company's Payroll app. An entry outside that period returns `timeEntryOutsidePayPeriod`.

Hours that are still unapproved when the regular pay run is submitted can be paid on a separate off-cycle run. On update, `create_extra_hours_run` asks Rippling to create that extra-hours run. Setting `status` to `PAID` records the hours as already paid and skips that run.

To read entries for a pay period, call [GET List time entries](https://developer.rippling.com/documentation/rest-api/reference/list-time-entries) and filter on `pay_period.start_date`. The entry’s `time_card` is the pay-period summary (approved and paid hours). Time cards can be fetched on their own using [GET List time cards](https://developer.rippling.com/documentation/rest-api/reference/list-time-cards), which also filters on `pay_period.start_date`.

* * *

## Errors

### Create

| `POST Create a new time entry` error | Details |
| --- | --- |
| `entryMustHaveJobShifts` | Raised if no job shifts are defined. |
| `spanningJobShiftsNotPresent` | Raised if job shifts do not span the entire duration from start to end time. |
| `invalidJobShiftStartGreaterThanEnd` | Raised if a job shift's start time is after its end time. |
| `invalidJobShiftEndTime` | Raised if a job shift's end time is after the entry's end time. |
| `invalidJobShiftStartEqualsEndTime` | Raised if a job shift's start time is equal to its end time. |
| `overlappingJobShiftTimes` | Raised if job shifts overlap. |
| `employeeIneligibleForJobCode` | Raised if the job code is not valid for the employee. |
| `invalidBreakStartTime` | Raised if a break's start time is invalid. |
| `mustEndBreakBeforeEndingEntry` | Raised if a break doesn't end before the entry ends. |
| `invalidBreakStartGreaterThanEnd` | Raised if a break's start time is after its end time. |
| `invalidBreakEndTime` | Raised if a break's end time is after the entry's end time. |
| `ineligibleForBreakType` | Raised if the break type is ineligible. |
| `overlappingBreakTimes` | Raised if breaks overlap. |
| `entryCannotBeOnlyBreaks` | Raised if the entry consists only of breaks. |
| `timeEntryStartGreaterThanEnd` | Raised if the entry's start time is after its end time. |
| `cannotReopenClosedEntry` | Raised if attempting to reopen a closed entry. |
| `existingEntryOverlapsWithClosedEntry` | Raised if an entry overlaps with another closed entry. |
| `existingEntryOverlapsWithOpenEntry` | Raised if an entry overlaps with another open entry. |
| `timeEntryOutsidePayPeriod` | Raised if the entry is outside the pay period. |
| `cannotApproveOpenEntry` | Raised if trying to approve an entry without an end time. |
| `cannotApproveEntry` | Raised if the approver is not eligible. |
| `cannotUnapprovePaidEntry` | Raised if trying to unapprove a paid entry. |
| `cannotUnapproveFinalizedEntry` | Raised if trying to unapprove a finalized entry. |
| `cannotModifyApprovedEntry` | Raised if trying to modify an approved entry without proper permissions. |
| `cannotModifyFinalizedEntry` | Raised if trying to modify a finalized entry. |
| `cannotModifyPaidEntry` | Raised if trying to modify a paid entry without proper permissions. |
| `noActivePolicyForUser` | Raised if a policy is not active for the user. |
| `cannotCreateEntryUntilStartDate` | Raised if a new entry is created before the role's start date. |
| `permissionDenied.authorDoesNotHavePermission` | Raised if the author does not have permission to make |

### Update

| `PATCH Update a time entry` error | Details |
| --- | --- |
| `entryMustHaveJobShifts` | Raised if no job shifts are defined. |
| `spanningJobShiftsNotPresent` | Raised if job shifts do not span the entire duration from start to end time. |
| `invalidJobShiftStartGreaterThanEnd` | Raised if a job shift's start time is after its end time. |
| `invalidJobShiftEndTime` | Raised if a job shift's end time is after the entry's end time. |
| `invalidJobShiftStartEqualsEndTime` | Raised if a job shift's start time is equal to its end time. |
| `employeeIneligibleForJobCode` | Raised if the job code is not valid for the employee. |
| `invalidBreakStartTime` | Raised if a break's start time is invalid. |
| `mustEndBreakBeforeEndingEntry` | Raised if a break doesn't end before the entry ends. |
| `invalidBreakStartGreaterThanEnd` | Raised if a break's start time is after its end time. |
| `invalidBreakEndTime` | Raised if a break's end time is after the entry's end time. |
| `ineligibleForBreakType` | Raised if the break type is ineligible. |
| `overlappingBreakTimes` | Raised if breaks overlap. |
| `entryCannotBeOnlyBreaks` | Raised if the entry consists only of breaks. |
| `timeEntryStartGreaterThanEnd` | Raised if the entry's start time is after its end time. |
| `cannotCreateFutureOpenEntry` | Raised if an open entry is created in the future. |
| `cannotModifyApprovedEntry` | Raised if trying to modify an approved entry without proper permissions. |
| `cannotModifyFinalizedEntry` | Raised if trying to modify a finalized entry. |
| `cannotModifyPaidEntry` | Raised if trying to modify a paid entry without proper permissions. |
| `noActivePolicyForUser` | Raised if a policy is not active for the user. |
| `noActiveTimekeepingPolicy` | Raised if no active timekeeping policy is available. |
| `permissionDenied.authorDoesNotHavePermission` | Raised if the author does not have permission to make changes. |
| `invalidShiftInputId` | Raised if a shift input ID is invalid. |
| `invalidShiftInputAuthorId` | Raised if a shift input author ID is invalid. |
| `entryLongerThanFortyEightHours` | Raised if the entry duration exceeds 48 hours. |
| `All ShiftInputGroupValues must have this TimeEntry as their parent` | Raised if a ShiftInputGroupValue is not correctly associated with the TimeEntry. |

### Delete

| `DELETE Delete a time entry` error | Details |
| --- | --- |
| `permissionDenied.authorDoesNotHavePermission` | Raised if the author does not have permission to delete the time entry. |
| `cannotModifyApprovedEntry` | Raised if trying to delete an approved entry without proper permissions. |
| `cannotModifyFinalizedEntry` | Raised if trying to delete a finalized entry. |
| `cannotModifyPaidEntry` | Raised if trying to delete a paid entry without proper permissions. |
| `resourceNotFound` | Raised if no time entry exists for the provided `id`. |

* * *

## Use cases

### Time entry for today

A worker logs a full day without shifts, job codes, or breaks, and Rippling uses the worker's configured hours for the start time.

1.  Create the entry with [POST Create a new time entry](https://developer.rippling.com/documentation/rest-api/reference/create-time-entries).
2.  Send `worker_id` and `duration`. Omit `start_date` so the entry uses today's date.
3.  A worker can have one time entry per day, and the entry cannot overlap an existing one.

### Time entry for another date

The same hours-only entry is for a day other than today.

1.  Create the entry with [POST Create a new time entry](https://developer.rippling.com/documentation/rest-api/reference/create-time-entries).
2.  Send `worker_id`, `duration`, and `start_date`.
3.  Do not send `job_shifts` with `duration`. A worker can have one time entry per day, and the entry cannot overlap an existing one.

### Approved time entry

The shift is unchanged, so it does not need admin approval.

1.  Create the entry with [POST Create a new time entry](https://developer.rippling.com/documentation/rest-api/reference/create-time-entries).
2.  Set `status` to `APPROVED`. Rippling treats the entry as approved.
3.  Omit `start_date` to use today's date. A worker can have one time entry per day, and the entry cannot overlap an existing one.

### Time entry with a break

The worker clocks a shift and a meal break. The worker must already be assigned to that break policy in Rippling in order to create a time entry with a break.

1.  Create the entry with [POST Create a new time entry](https://developer.rippling.com/documentation/rest-api/reference/create-time-entries).
2.  Send `duration` and a `breaks` item with `start_time`, `end_time`, and `break_type_id`.
3.  A worker can have one time entry per day, and the entry cannot overlap an existing one.

### Time entry with job codes

A worker spends part of the day in one role and the rest in another, with each role having its own job code. The worker must already be assigned to those job codes in Rippling in order to create a time entry with job codes.

1.  Create the entry with [POST Create a new time entry](https://developer.rippling.com/documentation/rest-api/reference/create-time-entries).
2.  Send `job_shifts` with one item per role. Each item needs `start_time`, `end_time`, and `job_codes_id`.
3.  Do not send `duration` with `job_shifts`. The entry cannot overlap an existing one.

### Time entry with job codes and a break

1.  Create the entry with [POST Create a new time entry](https://developer.rippling.com/documentation/rest-api/reference/create-time-entries).
2.  Send `job_shifts` with `start_time`, `end_time`, and `job_codes_id`.
3.  Send `breaks` with `start_time`, `end_time`, and `break_type_id`.
4.  The worker must already be assigned to the job code and the break policy. The entry cannot overlap an existing one.

### Time entry with a missed break

A worker clocked a shift and forgot a break, and a manager adds the break to the existing entry.

1.  Find the entry with [GET List time entries](https://developer.rippling.com/documentation/rest-api/reference/list-time-entries).
2.  Update it with [PATCH Update a time entry](https://developer.rippling.com/documentation/rest-api/reference/update-time-entries).
3.  Send the current `worker_id` and `job_shifts`, and add the break to `breaks` with `start_time`, `end_time`, and `break_type_id`.

* * *

## See also

-   [Get started with the Time API](https://developer.rippling.com/documentation/rest-api/guides/time-getting-started)
-   [API tokens and permissions](https://developer.rippling.com/documentation/rest-api/essentials/api-tokens)
-   [Create and edit a work hours policy](https://help.rippling.com/article?articleId=360056298793)

Source: https://developer.rippling.com/documentation/rest-api/guides/create-time-entry

> For the complete documentation index, see [llms.txt](https://developer.rippling.com/llms.txt). Public pages have a Markdown version at the same URL with .md added.
