Working with timesheets

Overview

The Timesheets API allows you to programmatically submit, review, and track hourly work for time-based contractor contracts. Both contractors and clients can create timesheets, which are then reviewed and included in the next invoice for the payment cycle.

This guide covers the complete timesheet workflow from creation through approval and payment tracking.

When to use this workflow

Use the Timesheets API when you need to:

  • Submit hourly work logs for time-based contracts
  • Review and approve contractor-submitted timesheets
  • Track billable hours for project-based work
  • Automate timesheet creation from time-tracking systems
  • Manage hourly report presets for recurring work types

Prerequisites

Before you begin, ensure you have:

  • A valid API token with timesheets:write and timesheets:read scopes
  • The contract_id of the time-based contractor contract
  • Work details including date, hours worked, and description
  • (Optional) A timesheet preset ID if using predefined hourly rates

Timesheets can only be created for time-based contracts. Fixed-price and milestone contracts do not support timesheet submission.

Step-by-step workflow

This example demonstrates submitting 40 hours of backend development work for a contractor’s weekly timesheet.

1

Identify the contract

First, retrieve the contract ID for the time-based contractor. You can list contracts and filter by type.

1import requests
2import os
3
4response = requests.get(
5 'https://api.letsdeel.com/rest/v2/contracts',
6 headers={
7 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}'
8 },
9 params={
10 'type': 'ongoing_time_based'
11 }
12)
13
14contracts = response.json()['data']
15contract_id = contracts[0]['id']

Response:

1{
2 "data": [
3 {
4 "id": "c3f9a1d2",
5 "title": "Backend Developer - Hourly",
6 "type": "ongoing_time_based",
7 "status": "active",
8 "worker_first_name": "Elena",
9 "worker_last_name": "Rodriguez",
10 "compensation_details": {
11 "amount": 85.00,
12 "currency_code": "USD",
13 "frequency": "hourly"
14 }
15 }
16 ]
17}

Save the id field - this is your contract_id for timesheet creation.

2

Create a timesheet entry

Submit the hours worked with a clear description of the work performed.

1import requests
2import os
3from datetime import date
4
5timesheet_data = {
6 "data": {
7 "contract_id": "c3f9a1d2",
8 "quantity": 40,
9 "date": str(date.today()),
10 "description": "Backend API development - implemented user authentication endpoints and payment processing integration"
11 }
12}
13
14response = requests.post(
15 'https://api.letsdeel.com/rest/v2/timesheets',
16 headers={
17 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}',
18 'Content-Type': 'application/json'
19 },
20 json=timesheet_data
21)
22
23timesheet = response.json()

Include specific details in the description to help reviewers understand the work performed. This aids in approval and provides context for invoicing.

Response:

1{
2 "data": {
3 "id": "ts_9876543210",
4 "contract_id": "c3f9a1d2",
5 "quantity": 40,
6 "date": "2026-02-05",
7 "description": "Backend API development - implemented user authentication endpoints and payment processing integration",
8 "status": "pending",
9 "created_at": "2026-02-05T14:30:00Z"
10 }
11}

The timesheet is now created with pending status, waiting for review.

3

Using hourly report presets

For recurring work types, create reusable presets with predefined rates and descriptions.

1import requests
2import os
3
4preset_data = {
5 "data": {
6 "contract_id": "c3f9a1d2",
7 "title": "Backend Development",
8 "description": "Standard backend development work including API implementation, database optimization, and code reviews",
9 "rate": 85.00
10 }
11}
12
13response = requests.post(
14 'https://api.letsdeel.com/rest/v2/timesheets/presets',
15 headers={
16 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}',
17 'Content-Type': 'application/json'
18 },
19 json=preset_data
20)
21
22preset = response.json()
23preset_id = preset['data']['id']

Once created, use the preset ID when submitting timesheets:

1timesheet_data = {
2 "data": {
3 "contract_id": "c3f9a1d2",
4 "quantity": 40,
5 "date": "2026-02-05",
6 "hourly_report_preset_id": preset_id
7 }
8}
9
10response = requests.post(
11 'https://api.letsdeel.com/rest/v2/timesheets',
12 headers={
13 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}',
14 'Content-Type': 'application/json'
15 },
16 json=timesheet_data
17)

The preset automatically applies the rate and description, streamlining timesheet submission.

4

Review and approve timesheets

Clients can review and approve submitted timesheets. This step moves them from pending to approved status.

1import requests
2import os
3
4review_data = {
5 "data": {
6 "status": "approved",
7 "reason": "Work verified and approved. Quality meets expectations."
8 }
9}
10
11response = requests.post(
12 f'https://api.letsdeel.com/rest/v2/timesheets/ts_9876543210/reviews',
13 headers={
14 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}',
15 'Content-Type': 'application/json'
16 },
17 json=review_data
18)

The reason field is optional but recommended. It creates an audit trail and helps explain approval or rejection decisions.

To reject a timesheet, set status to "rejected":

1{
2 "data": {
3 "status": "rejected",
4 "reason": "Hours do not match project tracking system. Please verify and resubmit."
5 }
6}
5

Retrieve timesheet details

Check the status and details of a specific timesheet.

1import requests
2import os
3
4response = requests.get(
5 'https://api.letsdeel.com/rest/v2/timesheets/ts_9876543210',
6 headers={
7 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}'
8 }
9)
10
11timesheet = response.json()

Response:

1{
2 "data": {
3 "id": "ts_9876543210",
4 "status": "approved",
5 "contract_id": "c3f9a1d2",
6 "quantity": 40,
7 "date": "2026-02-05",
8 "description": "Backend API development - implemented user authentication endpoints and payment processing integration",
9 "created_at": "2026-02-05T14:30:00Z",
10 "contract": {
11 "id": "c3f9a1d2",
12 "title": "Backend Developer - Hourly"
13 },
14 "reported_by": {
15 "id": "user_123",
16 "full_name": "Elena Rodriguez"
17 },
18 "reviewed_by": {
19 "id": "reviewer_456",
20 "full_name": "Michael Chen",
21 "reviewed_at": "2026-02-05T16:45:00Z",
22 "remarks": "Work verified and approved. Quality meets expectations."
23 },
24 "hourly_report_preset": {
25 "id": "preset_789",
26 "rate": 85.00,
27 "title": "Backend Development",
28 "description": "Standard backend development work"
29 }
30 }
31}
6

Track payment status

Monitor when approved timesheets are paid by checking the status periodically.

1import requests
2import os
3
4response = requests.get(
5 'https://api.letsdeel.com/rest/v2/timesheets/ts_9876543210',
6 headers={
7 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}'
8 }
9)
10
11status = response.json()['data']['status']
12
13if status == 'paid':
14 print('Timesheet has been paid')
15elif status == 'approved':
16 print('Timesheet approved, pending payment')
17elif status == 'pending':
18 print('Timesheet awaiting review')

Once the invoice is paid, the timesheet status changes to paid.

Common scenarios

Scenario 1: Submitting multiple days of work

For contractors tracking work across multiple days, submit separate timesheet entries for each day.

1import requests
2import os
3
4days_worked = [
5 {"date": "2026-02-03", "hours": 8, "description": "Database schema design and migration scripts"},
6 {"date": "2026-02-04", "hours": 9, "description": "API endpoint implementation for user management"},
7 {"date": "2026-02-05", "hours": 8, "description": "Unit tests and integration tests for authentication"},
8 {"date": "2026-02-06", "hours": 7.5, "description": "Code review and bug fixes from QA testing"},
9 {"date": "2026-02-07", "hours": 7.5, "description": "Performance optimization and documentation"}
10]
11
12for day in days_worked:
13 timesheet_data = {
14 "data": {
15 "contract_id": "c3f9a1d2",
16 "quantity": day["hours"],
17 "date": day["date"],
18 "description": day["description"]
19 }
20 }
21
22 response = requests.post(
23 'https://api.letsdeel.com/rest/v2/timesheets',
24 headers={
25 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}',
26 'Content-Type': 'application/json'
27 },
28 json=timesheet_data
29 )
30
31 print(f"Created timesheet for {day['date']}: {response.json()['data']['id']}")

This creates individual timesheet entries for each day, making it easier to track daily work breakdown.

Scenario 2: Auto-approved timesheets

For pre-authorized work or trusted contractors, use auto-approval to bypass manual review.

1import requests
2import os
3
4timesheet_data = {
5 "data": {
6 "contract_id": "c3f9a1d2",
7 "quantity": 40,
8 "date": "2026-02-05",
9 "description": "Weekly maintenance and support work - per service agreement",
10 "is_auto_approved": True
11 }
12}
13
14response = requests.post(
15 'https://api.letsdeel.com/rest/v2/timesheets',
16 headers={
17 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}',
18 'Content-Type': 'application/json'
19 },
20 json=timesheet_data
21)

The timesheet is created with approved status immediately, bypassing the manual review workflow.

Only use auto-approval for pre-authorized work or established contractors with a proven track record. Manual review provides an important control point for verifying hours and work quality.

Scenario 3: Updating a timesheet before approval

Correct errors in pending timesheets before they are reviewed.

1import requests
2import os
3
4update_data = {
5 "data": {
6 "quantity": 38,
7 "description": "Backend API development - implemented user authentication endpoints and payment processing integration (corrected hours)"
8 }
9}
10
11response = requests.patch(
12 'https://api.letsdeel.com/rest/v2/timesheets/ts_9876543210',
13 headers={
14 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}',
15 'Content-Type': 'application/json'
16 },
17 json=update_data
18)

You can only update timesheets in pending status. Once approved or paid, timesheets cannot be modified. If you need to correct an approved timesheet, contact Deel support or create an adjustment.

Scenario 4: Listing all timesheets for a contract

Retrieve timesheet history for reporting and tracking purposes.

1import requests
2import os
3
4response = requests.get(
5 'https://api.letsdeel.com/rest/v2/contracts/c3f9a1d2/timesheets',
6 headers={
7 'Authorization': f'Bearer {os.getenv("DEEL_API_TOKEN")}'
8 },
9 params={
10 'limit': 50,
11 'statuses': 'approved,paid'
12 }
13)
14
15timesheets = response.json()['data']
16
17total_hours = sum(ts['quantity'] for ts in timesheets)
18print(f"Total approved/paid hours: {total_hours}")

This retrieves all approved and paid timesheets for the contract, allowing you to calculate total billable hours.

Best practices

  • Be specific: Include what was accomplished, not just general work categories
  • Link to deliverables: Reference pull requests, tickets, or project milestones when applicable
  • Use consistent formatting: Establish a description template for your team
  • Avoid vague entries: “Worked on project” is less useful than “Implemented OAuth2 authentication flow”
  • Submit regularly: Daily or weekly submissions prevent backlog and ensure accurate time tracking
  • Respect payment cycles: Submit timesheets early in the cycle to ensure inclusion in the current invoice
  • Track in real-time: Use time-tracking tools and sync to Deel API to maintain accuracy
  • Review before submission: Verify hours and descriptions before creating timesheets
  • Create presets for common work: Set up presets for recurring work types (development, design, consulting)
  • Use descriptive titles: Make preset titles clear and specific for easy selection
  • Update rates regularly: Keep preset rates current with contract amendments
  • Document preset usage: Maintain internal documentation on when to use each preset
  • Review promptly: Approve or reject timesheets within 24-48 hours to maintain contractor trust
  • Provide clear feedback: When rejecting, explain exactly what needs to be corrected
  • Use auto-approval selectively: Reserve for established contractors with proven accuracy
  • Maintain audit trail: Include approval reasons for compliance and record-keeping
  • Validate contract type: Ensure contracts are time-based before attempting timesheet creation
  • Check scopes: Verify your API token has required timesheets:write and timesheets:read scopes
  • Handle rejections gracefully: Implement retry logic for rate limits and temporary failures
  • Log all operations: Track timesheet IDs and statuses for debugging and reconciliation
  • Track by status: Regularly query pending timesheets to ensure timely reviews
  • Monitor payment cycles: Correlate timesheet submission dates with invoice generation
  • Calculate utilization: Use contract timesheet data to analyze contractor productivity
  • Export for accounting: Integrate timesheet data with finance systems for reconciliation

Troubleshooting

Verify that the timesheet is approved and that the submission date falls within the current payment cycle. If the invoice has already been generated, the timesheet will appear on the next invoice. Check the payment cycle dates using the contracts endpoint.

Timesheets can only be created for ongoing_time_based or fixed_term_time_based contracts. Verify the contract type using GET /rest/v2/contracts/{contract_id}. For fixed-price contracts, use milestones or invoice adjustments instead.

Your API token must have timesheets:write scope to create or update timesheets, and timesheets:read scope to retrieve them. Generate a new token with the required scopes in the Developer Center.

Timesheets can only be modified while in pending status. Once approved, they are locked for audit purposes. If you need to correct an approved timesheet, contact Deel support or create an invoice adjustment for the difference.

Ensure the hourly_report_preset_id exists and belongs to the specified contract. Use GET /contracts/{contract_id}/timesheets/presets to list available presets for the contract. Presets are contract-specific and cannot be shared across contracts.

When rejecting a timesheet, always include a reason field to explain the rejection. This helps the contractor understand what needs to be corrected. If a timesheet was rejected without reason, contact the reviewer or check the timesheet details for any reviewer remarks.

Next steps