API Documentation
The Contractor's Work Log REST API lets you programmatically manage work logs, time entries, companies, invoices, and timers. Use it to integrate time tracking into your own tools, scripts, or apps.
API access is available on plans that include the API feature. You can check your plan on the pricing page.
Authentication
All API requests must be authenticated. Two methods are supported:
HTTP Basic Auth
Use your CWorkLog username and password. Pass them as the Basic Auth username and password with each request.
API Key (Bearer Token)
Generate an API key from your account settings. Send it in the Authorization header as a Bearer token.
curl -u "your_username:your_password" \ http://cworklog.com/api/status
curl -H "Authorization: Bearer YOUR_API_KEY" \ http://cworklog.com/api/status
Base URL
All endpoints are relative to the base URL below.
Response Format
All responses are returned as JSON. Successful responses include the data directly. Error responses include an error field with a human-readable message.
{
"success": true,
"data": { ... }
}
{
"error": "Work log not found"
}
Rate Limiting
API requests are rate limited to ensure fair usage and service stability. If you exceed the limit, you will receive a 429 Too Many Requests response.
Errors
The API uses standard HTTP status codes to indicate success or failure.
| Status Code | Meaning |
|---|---|
| 200 | Success. The request completed as expected. |
| 400 | Bad Request. Missing or invalid parameters. |
| 401 | Unauthorized. Invalid or missing authentication credentials. |
| 403 | Forbidden. Your plan does not include API access. |
| 404 | Not Found. The requested resource does not exist. |
| 429 | Too Many Requests. Rate limit exceeded. |
| 500 | Internal Server Error. Something went wrong on our end. |
Work Logs
GET /worklogs
Retrieve all work logs for the authenticated user. Also available as POST /worklogs/list with body parameters.
| Name | Type | Description |
|---|---|---|
| include_lockedoptional | boolean | Include locked work logs in the results. Default: false. |
| locked_onlyoptional | boolean | Return only locked work logs. Default: false. |
curl -u "jsmith:secret123" \ "http://cworklog.com/api/worklogs?include_locked=1"
{
"success": true,
"data": [
{
"id": 102,
"title": "Website Redesign",
"company_id": 5,
"company_name": "Acme Corp",
"description": "Full redesign of marketing site",
"locked": 0,
"created": "2025-09-15 08:30:00",
"total_time_seconds": 28800,
"total_time_formatted": "8:00:00"
},
{
"id": 103,
"title": "Mobile App Backend",
"company_id": 5,
"company_name": "Acme Corp",
"description": "REST API for mobile application",
"locked": 0,
"created": "2025-10-01 14:00:00",
"total_time_seconds": 14400,
"total_time_formatted": "4:00:00"
}
]
}
POST /worklogs/new
Create a new work log.
| Name | Type | Description |
|---|---|---|
| company_idrequired | integer | The company/client ID. Aliases: cid, client_id. |
| titlerequired | string | Title of the work log. |
| descriptionoptional | string | Longer description of the work log. |
| notesoptional | string | Private notes for the work log. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/worklogs/new \ -d "company_id=5&title=Logo+Design&description=New+branding+project"
{
"success": true,
"data": {
"id": 147,
"title": "Logo Design",
"company_id": 5,
"description": "New branding project",
"created": "2025-11-20 10:15:32"
}
}
POST /worklogs/update
Update a single field on an existing work log.
| Name | Type | Description |
|---|---|---|
| idrequired | integer | Work log ID to update. |
| column_keyrequired | string | The field to update (e.g. title, description, notes). |
| valuerequired | string | The new value for the field. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/worklogs/update \ -d "id=147&column_key=title&value=Updated+Logo+Design"
{
"success": true
}
POST /worklogs/delete
Delete a work log and all associated time entries.
| Name | Type | Description |
|---|---|---|
| idrequired | integer | Work log ID to delete. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/worklogs/delete \ -d "id=147"
{
"success": true
}
POST /worklogs/timelogs
Get all time log entries for a specific work log.
| Name | Type | Description |
|---|---|---|
| widrequired | integer | Work log ID. Alias: id. |
| optsoptional | object | Additional options for filtering or formatting. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/worklogs/timelogs \ -d "wid=102"
{
"success": true,
"data": [
{
"id": 501,
"work_log_id": 102,
"start_time": "2025-10-12 09:00:00",
"stop_time": "2025-10-12 12:30:00",
"notes": "Header and navigation layout",
"duration_seconds": 12600,
"duration_formatted": "3:30:00"
},
{
"id": 502,
"work_log_id": 102,
"start_time": "2025-10-12 13:30:00",
"stop_time": "2025-10-12 17:00:00",
"notes": "Footer and responsive styles",
"duration_seconds": 12600,
"duration_formatted": "3:30:00"
}
]
}
Time Logs
POST /timelogs/new
Create a new time log entry for a work log.
| Name | Type | Description |
|---|---|---|
| worklog_idrequired | integer | The work log to attach this time entry to. |
| start_timerequired | string | Start time in YYYY-MM-DD HH:MM:SS format. |
| stop_timerequired | string | Stop time in YYYY-MM-DD HH:MM:SS format. |
| notesoptional | string | Notes describing the work done. |
| optsoptional | object | Additional options. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/timelogs/new \ -d "worklog_id=102&start_time=2025-10-14+09:00:00&stop_time=2025-10-14+12:00:00¬es=API+integration+work"
{
"success": true,
"data": {
"id": 510,
"work_log_id": 102,
"start_time": "2025-10-14 09:00:00",
"stop_time": "2025-10-14 12:00:00",
"notes": "API integration work",
"duration_seconds": 10800
}
}
POST /timelogs
List time logs for a specific work log. Functionally equivalent to POST /worklogs/timelogs.
| Name | Type | Description |
|---|---|---|
| widrequired | integer | Work log ID. Alias: id. |
| optsoptional | object | Additional options. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/timelogs \ -d "wid=102"
{
"success": true,
"data": [
{
"id": 501,
"work_log_id": 102,
"start_time": "2025-10-12 09:00:00",
"stop_time": "2025-10-12 12:30:00",
"notes": "Header layout",
"duration_seconds": 12600
}
]
}
POST /timelogs/copy
Copy one or more time log entries to a different work log.
| Name | Type | Description |
|---|---|---|
| timelog_idsrequired | string|array | Comma-separated IDs or array of time log IDs to copy. Aliases: timelogs, rows. |
| to_worklog_idrequired | integer | Destination work log ID. |
| note_overrideoptional | string | Override notes on the copied entries. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/timelogs/copy \ -d "timelog_ids=501,502&to_worklog_id=103"
{
"success": true,
"data": {
"copied_count": 2
}
}
POST /timelogs/move
Move time log entries to a different work log.
| Name | Type | Description |
|---|---|---|
| timelogsrequired | array | Array of time log row objects to move. |
| to_worklog_idrequired | integer | Destination work log ID. |
curl -u "jsmith:secret123" \
-X POST http://cworklog.com/api/timelogs/move \
-H "Content-Type: application/json" \
-d '{"timelogs": [{"id": 501}, {"id": 502}], "to_worklog_id": 103}'
{
"success": true,
"data": {
"moved_count": 2
}
}
POST /timelogs/upload
Bulk upload multiple time log entries at once.
| Name | Type | Description |
|---|---|---|
| timelogsrequired | array | Array of objects with work_log_id, start_time, stop_time, notes, and optional opts. Alias: rows. |
curl -u "jsmith:secret123" \
-X POST http://cworklog.com/api/timelogs/upload \
-H "Content-Type: application/json" \
-d '{
"timelogs": [
{
"work_log_id": 102,
"start_time": "2025-10-15 09:00:00",
"stop_time": "2025-10-15 11:00:00",
"notes": "Morning session"
},
{
"work_log_id": 102,
"start_time": "2025-10-15 13:00:00",
"stop_time": "2025-10-15 16:30:00",
"notes": "Afternoon session"
}
]
}'
{
"success": true,
"data": {
"uploaded_count": 2
}
}
POST /timelogs/update_start_time
Update the start time of a time log entry.
| Name | Type | Description |
|---|---|---|
| timelog_idrequired | integer | Time log ID. Alias: id. |
| start_timerequired | string | New start time in YYYY-MM-DD HH:MM:SS format. |
| notesoptional | string | Updated notes. |
| optsoptional | object | Additional options. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/timelogs/update_start_time \ -d "timelog_id=501&start_time=2025-10-12+08:45:00"
{
"success": true
}
POST /timelogs/update_stop_time
Update the stop time of a time log entry. You can pass a datetime string or an integer number of minutes.
| Name | Type | Description |
|---|---|---|
| timelog_idrequired | integer | Time log ID. Alias: id. |
| stop_time_or_minutes_intrequired | string|integer | New stop time (YYYY-MM-DD HH:MM:SS) or total duration in minutes from start. |
| notesoptional | string | Updated notes. |
| optsoptional | object | Additional options. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/timelogs/update_stop_time \ -d "timelog_id=501&stop_time_or_minutes_int=2025-10-12+13:00:00"
{
"success": true
}
POST /timelogs/update_stop_time_subtract_min
Subtract a number of minutes from the stop time of a time log entry.
| Name | Type | Description |
|---|---|---|
| widrequired | integer | Work log ID. |
| timelog_idrequired | integer | Time log ID. Alias: id. |
| stop_time_subtract_minutesrequired | integer | Number of minutes to subtract from the current stop time. |
| optsoptional | object | Additional options. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/timelogs/update_stop_time_subtract_min \ -d "wid=102&timelog_id=501&stop_time_subtract_minutes=15"
{
"success": true
}
POST /timelogs/update_notes
Update only the notes on a time log entry.
| Name | Type | Description |
|---|---|---|
| timelog_idrequired | integer | Time log ID. Alias: id. |
| notesrequired | string | New notes text. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/timelogs/update_notes \ -d "timelog_id=501¬es=Updated+header+layout+with+sticky+nav"
{
"success": true
}
POST /timelogs/delete
Delete a time log entry.
| Name | Type | Description |
|---|---|---|
| timelog_idrequired | integer | Time log ID. Aliases: time_log_id, tid, id. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/timelogs/delete \ -d "timelog_id=501"
{
"success": true
}
Timer
POST /start
Start a timer on a work log. Creates a time log entry with a start time and no stop time.
| Name | Type | Description |
|---|---|---|
| widrequired | integer | Work log ID to start the timer on. |
| start_timeoptional | string | Backdate the start time. Format: YYYY-MM-DD HH:MM:SS. Defaults to now. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/start \ -d "wid=102"
{
"success": true,
"data": {
"timelog_id": 515,
"work_log_id": 102,
"start_time": "2025-11-20 14:30:05"
}
}
POST /stop
Stop a running timer on a work log. Sets the stop time on the open time log entry.
| Name | Type | Description |
|---|---|---|
| widrequired | integer | Work log ID to stop the timer on. |
| timelog_idoptional | integer | Specific time log entry to stop (if multiple timers are running). |
| notesoptional | string | Notes to attach to this time entry. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/stop \ -d "wid=102¬es=Finished+header+component"
{
"success": true,
"data": {
"timelog_id": 515,
"work_log_id": 102,
"start_time": "2025-11-20 14:30:05",
"stop_time": "2025-11-20 16:45:12",
"duration_seconds": 8107,
"duration_formatted": "2:15:07"
}
}
Companies
GET /companies
Retrieve all companies (clients) for the authenticated user.
curl -u "jsmith:secret123" \ http://cworklog.com/api/companies
{
"success": true,
"data": [
{
"id": 5,
"name": "Acme Corp",
"street": "123 Main St",
"city": "Springfield",
"state": "IL",
"zip": "62704",
"country": "US",
"phone": "555-0142",
"email": "billing@acmecorp.example",
"default_hourly_rate": "75.00"
},
{
"id": 8,
"name": "Globex Industries",
"street": "456 Oak Ave",
"city": "Shelbyville",
"state": "IL",
"zip": "62705",
"country": "US",
"phone": "",
"email": "accounts@globex.example",
"default_hourly_rate": "100.00"
}
]
}
POST /companies/new
Create a new company (client).
| Name | Type | Description |
|---|---|---|
| namerequired | string | Company name. |
| streetoptional | string | Street address line 1. |
| street2optional | string | Street address line 2. |
| cityoptional | string | City. |
| stateoptional | string | State or province. |
| zipoptional | string | ZIP or postal code. |
| countryoptional | string | Country code or name. |
| phoneoptional | string | Phone number. |
| emailoptional | string | Contact email address. |
| notesoptional | string | Private notes about the company. |
| default_hourly_rateoptional | number | Default hourly rate for this client. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/companies/new \ -d "name=Initech&city=Austin&state=TX&email=contact@initech.example&default_hourly_rate=85"
{
"success": true,
"data": {
"id": 12,
"name": "Initech"
}
}
POST /companies/delete
Delete a company and its associated work logs.
| Name | Type | Description |
|---|---|---|
| idrequired | integer | Company ID to delete. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/companies/delete \ -d "id=12"
{
"success": true
}
User
GET /status
Get the current user status including running timers, plan info, and account details. Useful as a health check or to retrieve active timers.
curl -u "jsmith:secret123" \ http://cworklog.com/api/status
{
"success": true,
"data": {
"user": {
"id": 42,
"username": "jsmith",
"email": "jsmith@example.com",
"timezone": "America/New_York"
},
"plan": {
"name": "Professional",
"max_clients": -1,
"max_active_worklogs": -1,
"allow_api_key": 1
},
"unfinished_timers": [
{
"timelog_id": 515,
"work_log_id": 102,
"work_log_title": "Website Redesign",
"start_time": "2025-11-20 14:30:05"
}
]
}
}
POST /user/set_timezone
Set the timezone for the authenticated user. Affects how times are displayed and recorded.
| Name | Type | Description |
|---|---|---|
| timezonerequired | string | IANA timezone identifier (e.g. America/New_York, Europe/London). |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/user/set_timezone \ -d "timezone=America/Chicago"
{
"success": true
}
POST /user/set_auto_upgrade
Enable or disable automatic plan upgrades when usage limits are reached.
| Name | Type | Description |
|---|---|---|
| enabledrequired | boolean | Set to 1 or true to enable, 0 or false to disable. |
curl -u "jsmith:secret123" \ -X POST http://cworklog.com/api/user/set_auto_upgrade \ -d "enabled=1"
{
"success": true
}
GET /plans
List all available subscription plans.
curl -u "jsmith:secret123" \ http://cworklog.com/api/plans
{
"success": true,
"data": [
{
"name": "Free",
"shortname": "free",
"cost_monthly": "0.00",
"max_clients": 2,
"max_active_worklogs": 3,
"allow_api_key": 0
},
{
"name": "Professional",
"shortname": "pro",
"cost_monthly": "9.00",
"max_clients": -1,
"max_active_worklogs": -1,
"allow_api_key": 1
}
]
}
Invoices
GET /invoices
Retrieve invoices. Optionally filter by work log.
| Name | Type | Description |
|---|---|---|
| work_log_idoptional | integer | Filter invoices to a specific work log. |
curl -u "jsmith:secret123" \ "http://cworklog.com/api/invoices?work_log_id=102"
{
"success": true,
"data": [
{
"id": 31,
"work_log_id": 102,
"invoice_number": "INV-2025-031",
"amount_billed_total": "600.00",
"amount_clocked": "525.00",
"tax_rate": "0.00",
"amount_tax": "0.00",
"created": "2025-10-31 10:00:00"
}
]
}
GET /invoices/email_templates
Retrieve the list of available email templates for invoice delivery.
curl -u "jsmith:secret123" \ http://cworklog.com/api/invoices/email_templates
{
"success": true,
"data": [
{
"key": "default",
"name": "Default Invoice Template"
},
{
"key": "minimal",
"name": "Minimal Template"
}
]
}
GET /invoices/email_preview
Generate a preview of the invoice email before sending.
| Name | Type | Description |
|---|---|---|
| work_log_idrequired | integer | Work log ID for the invoice. |
| templateoptional | string | Email template key. |
| invoice_numberoptional | string | Invoice number to display. |
| messageoptional | string | Custom message to include in the email body. |
| amount_billed_totaloptional | number | Total amount billed. |
| from_nameoptional | string | Sender name to display in the email. |
curl -u "jsmith:secret123" \ "http://cworklog.com/api/invoices/email_preview?work_log_id=102&template=default&invoice_number=INV-2025-032&amount_billed_total=750.00&from_name=John+Smith"
{
"success": true,
"data": {
"html": "<html>...rendered email preview HTML...</html>",
"subject": "Invoice INV-2025-032 from John Smith"
}
}
POST /invoices/email
Send an invoice email to a client with an optional PDF attachment.
| Name | Type | Description |
|---|---|---|
| work_log_idrequired | integer | Work log ID for the invoice. |
| to_emailrequired | string | Recipient email address. |
| subjectrequired | string | Email subject line. |
| htmlrequired | string | HTML content of the email body. |
| invoice_numberrequired | string | Invoice number. |
| amount_billed_totalrequired | number | Total amount billed. |
| pdfoptional | string | Base64-encoded PDF to attach. |
| cc_emailoptional | string | CC email address. |
| messageoptional | string | Custom message to include. |
| email_templateoptional | string | Email template key. |
| amount_clockedoptional | number | Amount based on clocked hours. |
| amount_creditedoptional | number | Credits applied. |
| amount_taxoptional | number | Tax amount. |
| tax_rateoptional | number | Tax rate percentage. |
| from_nameoptional | string | Sender display name. |
| private_notesoptional | string | Private notes (not shown to client, saved for your records). |
curl -u "jsmith:secret123" \
-X POST http://cworklog.com/api/invoices/email \
-H "Content-Type: application/json" \
-d '{
"work_log_id": 102,
"to_email": "billing@acmecorp.example",
"subject": "Invoice INV-2025-032 from John Smith",
"html": "<h1>Invoice</h1><p>Amount due: $750.00</p>",
"invoice_number": "INV-2025-032",
"amount_billed_total": 750.00,
"amount_clocked": 675.00,
"tax_rate": 0,
"amount_tax": 0,
"from_name": "John Smith"
}'
{
"success": true,
"data": {
"invoice_id": 32,
"sent_to": "billing@acmecorp.example"
}
}