API reference

A predictable REST API for time, catalog, billing, and reporting data. Machine-readable spec: /api/v1/openapi.json (imports straight into Postman).

Authentication

Send Authorization: Bearer <token>. Two token kinds, both created on the in-app Developer page:

Endpoints marked manager+ or admin refuse lower-grade personal tokens — money (rates, invoices, reports) never reaches member tokens.

Conventions

Company

The authenticated workspace.

GET/v1/companyRetrieve the company
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "name": "Fieldnote Studio",
  "is_active": true,
  "base_uri": "https://fieldnote.timetrakkr.com",
  "full_domain": "fieldnote.timetrakkr.com",
  "week_start_day": "Monday",
  "currency": "USD",
  "time_zone": "America/New_York"
}
ERRORS
{
  "message": "Project not found"
}

Time entries

Tracked time. Member-grade personal tokens see and modify only their own entries; submitted or approved weeks are locked.

GET/v1/time_entriesList time entries
PARAMETERS
  • from — Only entries on/after this date (YYYY-MM-DD)
  • to — Only entries on/before this date
  • user_id — Filter to one member
  • client_id — Filter by client
  • project_id — Filter by project
  • task_id — Filter by task
  • billable — true/false
  • is_billed — true = already invoiced
  • is_running — true = timer currently running
  • approval_status — unsubmitted, submitted, or approved
  • external_reference_id — Match an external reference
  • updated_since — ISO 8601 datetime
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "time_entries": [
    {
      "id": "3f0a1c2e-5b6d-4e7f-8a90-1b2c3d4e5f60",
      "spent_date": "2026-08-03",
      "hours": 2,
      "notes": "Homepage hero — second revision round",
      "billable": true,
      "is_locked": false,
      "is_billed": false,
      "is_running": false,
      "approval_status": "unsubmitted",
      "user": {
        "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
        "name": "Priya Nair"
      },
      "project": {
        "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
        "name": "Acme Rebrand"
      },
      "task": {
        "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "Design"
      },
      "client": {
        "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "Acme Corp"
      },
      "external_reference": null,
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/time_entries?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/time_entries?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/time_entriesCreate a time entry

Personal tokens log as their owner. Weeks already submitted for approval refuse new time (422). Omitting `hours` (and the times) starts a running timer and returns the entry with `is_running` true. An agent member's entry needs started_at and ended_at, and is refused (422, with a `code`) if it overlaps that member's other entries, ends in the future, or takes its day past 24 hours.

BODY
  • project_id — UUID, required
  • task_id — UUID, required
  • spent_date — YYYY-MM-DD, required
  • hours — Decimal hours (rounded per org policy). OMIT to start a running timer instead.
  • notes — Entry notes
  • billable — Defaults true
  • external_reference.id — External system reference — import provenance, unique per org (a reused one is 409)
  • issue_key — Jira issue this entry is for, e.g. ACME-142. Several entries may share one.
  • user_id — Required with an organization key; ignored for personal tokens
  • started_at — ISO timestamp the work started (send with ended_at). Without hours, the duration comes from these.
  • ended_at — ISO timestamp the work ended
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope
  • 409Another entry in the workspace already has that external_reference.id (deleted entries keep theirs). Look it up with ?external_reference_id=.
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "3f0a1c2e-5b6d-4e7f-8a90-1b2c3d4e5f60",
  "spent_date": "2026-08-03",
  "hours": 2,
  "notes": "Homepage hero — second revision round",
  "billable": true,
  "is_locked": false,
  "is_billed": false,
  "is_running": false,
  "approval_status": "unsubmitted",
  "user": {
    "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
    "name": "Priya Nair"
  },
  "project": {
    "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
    "name": "Acme Rebrand"
  },
  "task": {
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Design"
  },
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "external_reference": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/time_entries/{id}Retrieve a time entry
PARAMETERS
  • id (required) — The time entry id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "3f0a1c2e-5b6d-4e7f-8a90-1b2c3d4e5f60",
  "spent_date": "2026-08-03",
  "hours": 2,
  "notes": "Homepage hero — second revision round",
  "billable": true,
  "is_locked": false,
  "is_billed": false,
  "is_running": false,
  "approval_status": "unsubmitted",
  "user": {
    "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
    "name": "Priya Nair"
  },
  "project": {
    "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
    "name": "Acme Rebrand"
  },
  "task": {
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Design"
  },
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "external_reference": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/time_entries/{id}Update a time entry

Locked (approved/invoiced) and submitted entries return 422.

PARAMETERS
  • id (required) — The time entry id (UUID)
BODY
  • project_id — UUID
  • task_id — UUID
  • spent_date — YYYY-MM-DD (target week must be open)
  • hours — Decimal hours
  • notes — Entry notes
  • billable — true/false
  • external_reference.id — Set or replace the external reference
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope
  • 404No such record in this workspace
  • 409Another entry in the workspace already has that external_reference.id (deleted entries keep theirs). Look it up with ?external_reference_id=.
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "3f0a1c2e-5b6d-4e7f-8a90-1b2c3d4e5f60",
  "spent_date": "2026-08-03",
  "hours": 2,
  "notes": "Homepage hero — second revision round",
  "billable": true,
  "is_locked": false,
  "is_billed": false,
  "is_running": false,
  "approval_status": "unsubmitted",
  "user": {
    "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
    "name": "Priya Nair"
  },
  "project": {
    "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
    "name": "Acme Rebrand"
  },
  "task": {
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Design"
  },
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "external_reference": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/time_entries/{id}Delete a time entry
PARAMETERS
  • id (required) — The time entry id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/time_entries/{id}/restartRestart the timer on an entry · personal token

Personal tokens only — timers belong to a person. Stops any other running timer.

PARAMETERS
  • id (required) — The time entry id (UUID)
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (personal)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "3f0a1c2e-5b6d-4e7f-8a90-1b2c3d4e5f60",
  "spent_date": "2026-08-03",
  "hours": 2,
  "notes": "Homepage hero — second revision round",
  "billable": true,
  "is_locked": false,
  "is_billed": false,
  "is_running": false,
  "approval_status": "unsubmitted",
  "user": {
    "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
    "name": "Priya Nair"
  },
  "project": {
    "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
    "name": "Acme Rebrand"
  },
  "task": {
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Design"
  },
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "external_reference": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/time_entries/{id}/stopStop the running timer on an entry

A timer that has run past the sanity cap refuses with 409 and a message; re-send with actual_hours to bank the real figure.

PARAMETERS
  • id (required) — The time entry id (UUID)
BODY
  • actual_hours — Hours actually worked. Only needed to stop a timer that ran implausibly long and was refused (409).
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "3f0a1c2e-5b6d-4e7f-8a90-1b2c3d4e5f60",
  "spent_date": "2026-08-03",
  "hours": 2,
  "notes": "Homepage hero — second revision round",
  "billable": true,
  "is_locked": false,
  "is_billed": false,
  "is_running": false,
  "approval_status": "unsubmitted",
  "user": {
    "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
    "name": "Priya Nair"
  },
  "project": {
    "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
    "name": "Acme Rebrand"
  },
  "task": {
    "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
    "name": "Design"
  },
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "external_reference": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/time_entries/{id}/external_referenceRemove an entry’s external reference
PARAMETERS
  • id (required) — The time entry id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}

Clients

Clients, their currencies and notes, and the people to contact there.

GET/v1/clientsList clients

Archived clients are left out unless include_archived=true.

PARAMETERS
  • name — Case-insensitive name fragment
  • name_exact — The whole name, ignoring capitalization. Use this to look a record up before creating it.
  • include_archived — true to include archived records, which are otherwise left out
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "clients": [
    {
      "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
      "name": "Acme Corp",
      "is_active": true,
      "address": "14 Bridge Street, Bristol BS1 4TW",
      "currency": "USD",
      "notes": "Invoices to accounts payable, net 30.",
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/clients?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/clients?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/clientsCreate a client · manager+

With find_existing, a match returns 200 and the client as it is: the other fields are not applied to it. No match creates one and returns 201.

BODY
  • name — Required
  • currency — 3-letter code
  • address — Postal address
  • notes — Notes
  • find_existing — true to return the active client of this name (ignoring capitalization) instead of creating a second one
RESPONSES
  • 200find_existing matched an existing record, which is returned unchanged
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
  "name": "Acme Corp",
  "is_active": true,
  "address": "14 Bridge Street, Bristol BS1 4TW",
  "currency": "USD",
  "notes": "Invoices to accounts payable, net 30.",
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/clients/{id}Retrieve a client
PARAMETERS
  • id (required) — The client id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
  "name": "Acme Corp",
  "is_active": true,
  "address": "14 Bridge Street, Bristol BS1 4TW",
  "currency": "USD",
  "notes": "Invoices to accounts payable, net 30.",
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/clients/{id}Update a client · manager+
PARAMETERS
  • id (required) — The client id (UUID)
BODY
  • name — Client name
  • currency — 3-letter code
  • address — Postal address
  • notes — Notes
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
  "name": "Acme Corp",
  "is_active": true,
  "address": "14 Bridge Street, Bristol BS1 4TW",
  "currency": "USD",
  "notes": "Invoices to accounts payable, net 30.",
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/clients/{id}Archive a client · manager+

Archives the client; it is not deleted, and keeps its projects, invoices and contacts. Refuses (422) while the client has active projects. Deleting for good is done in the app, by an owner or admin, once the client is archived and has nothing attached.

PARAMETERS
  • id (required) — The client id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}
GET/v1/clients/{id}/contactsList a client’s contacts

Sorted by name.

PARAMETERS
  • id (required) — The client id (UUID)
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "contacts": [
    {
      "id": "91a2b3c4-d5e6-4f70-8a9b-0c1d2e3f4a5b",
      "name": "Dana Reyes",
      "title": "Finance lead",
      "email": "[email protected]",
      "phone": "+1 555 0142",
      "client": {
        "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e"
      },
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/clients/ID/contacts?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/clients/ID/contacts?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/clients/{id}/contactsAdd a contact to a client · manager+

Unknown fields are refused with 400 rather than ignored. To find-or-create a contact, POST and treat 409 as found: its body carries the existing contact’s id.

PARAMETERS
  • id (required) — The client id (UUID)
BODY
  • name — Required
  • title — Job title
  • email — Email address. Unique on the client, ignoring capitalization.
  • phone — Phone number
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 409That email is already on this client. The body carries the existing contact’s id as contact_id.
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "91a2b3c4-d5e6-4f70-8a9b-0c1d2e3f4a5b",
  "name": "Dana Reyes",
  "title": "Finance lead",
  "email": "[email protected]",
  "phone": "+1 555 0142",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e"
  },
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/clients/{id}/contacts/{contactId}Update a contact · manager+

Send only the fields to change; null clears title, email or phone.

PARAMETERS
  • id (required) — The client id (UUID)
  • contactId (required) — The contact id (UUID)
BODY
  • name — Contact name
  • title — Job title
  • email — Email address. Unique on the client, ignoring capitalization.
  • phone — Phone number
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 409That email is already on this client. The body carries the existing contact’s id as contact_id.
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "91a2b3c4-d5e6-4f70-8a9b-0c1d2e3f4a5b",
  "name": "Dana Reyes",
  "title": "Finance lead",
  "email": "[email protected]",
  "phone": "+1 555 0142",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e"
  },
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/clients/{id}/contacts/{contactId}Remove a contact · manager+
PARAMETERS
  • id (required) — The client id (UUID)
  • contactId (required) — The contact id (UUID)
RESPONSES
  • 204Done. No body.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}

Projects

Projects, and who is assigned to them.

GET/v1/projectsList projects

Archived projects are left out unless include_archived=true or is_active=false. Combine client_id and name_exact to look a project up before creating it.

PARAMETERS
  • client_id — Filter by client
  • engagement_id — Filter by engagement. Add include_archived=true to see all of an engagement’s projects
  • name — Case-insensitive name fragment
  • name_exact — The whole name, ignoring capitalization. Use this to look a record up before creating it.
  • is_active — true for active projects only, false for archived only
  • include_archived — true to include archived records, which are otherwise left out
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "projects": [
    {
      "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
      "name": "Acme Rebrand",
      "code": null,
      "is_active": true,
      "client": {
        "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "Acme Corp"
      },
      "hourly_rate": 145,
      "engagement_id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z",
      "billing_type": "hourly_project_rate",
      "fixed_fee": null,
      "budget": 120,
      "budget_is_monthly": false,
      "over_budget_notification_percentage": 80,
      "show_budget_to_all": false,
      "cost_budget": 9000,
      "cost_budget_include_expenses": false,
      "fee_budget": 18000,
      "fee_budget_is_monthly": false,
      "fee_budget_notification_percentage": 80
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/projects?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/projects?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/projectsCreate a project · manager+

With find_existing, a match returns 200 and the project as it is, budget and billing included: the budget fields, billing_type and fixed_fee apply only to a project this call creates. No match creates one and returns 201. A client_id from another workspace is 404. Unknown fields are refused with 400.

BODY
  • client_id — UUID, required
  • name — Required
  • code — Short code
  • hourly_rate — Project hourly rate
  • starts_on — YYYY-MM-DD
  • ends_on — YYYY-MM-DD
  • engagement_id — UUID of one of the client’s engagements; null takes the project out of its engagement
  • billing_type — hourly_project_rate, hourly_person_rate, hourly_task_rate, fixed_fee or non_billable
  • fixed_fee — The fee for a fixed_fee project, in its currency; null clears it. Refused (422) on any other billing type
  • budget — Project hours budget; null removes it
  • budget_is_monthly — true makes budget a monthly allowance instead of a total
  • over_budget_notification_percentage — When the over-budget alert fires, 1-100 (default 80); null switches alerts off. Left out, it keeps its value. Reads back as null when alerts are off, or when the project has no hours or cost budget (budget and cost_budget null)
  • show_budget_to_all — true lets members see budget progress
  • cost_budget — Internal cost budget in the workspace currency; null removes it
  • cost_budget_include_expenses — true counts expenses against cost_budget
  • fee_budget — Fee budget in the project currency; null removes it. The billable hours at their hourly rates are measured against it, on a fixed-fee project too
  • fee_budget_is_monthly — true makes fee_budget a monthly amount instead of a total
  • fee_budget_notification_percentage — When the fee budget alert fires, 1-100 (default 80); null switches it off. Separate from the hours budget threshold
  • find_existing — true to return the client’s active project of this name (ignoring capitalization) instead of creating a second one
RESPONSES
  • 200find_existing matched an existing record, which is returned unchanged
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No client with that client_id in this workspace
  • 422Refused by a business rule — the message says which
  • 422A budget setting (budget_is_monthly, over_budget_notification_percentage, cost_budget_include_expenses) was sent for a project with no budget, or in the same request that removes its budgets; or a fee budget setting (fee_budget_is_monthly, fee_budget_notification_percentage) for a project with no fee budget, or in the request that removes it. The message says which. Send budget or cost_budget (fee_budget for the fee settings) with it; show_budget_to_all alone is not a budget.
  • 422engagement_id names another client’s engagement, or client_id moves the project away from the client its engagement belongs to. Nothing is changed. The message says which.
  • 422fixed_fee was sent for a project whose billing_type is not fixed_fee. Send billing_type "fixed_fee" with it. Nothing is changed.
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
  "name": "Acme Rebrand",
  "code": null,
  "is_active": true,
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "hourly_rate": 145,
  "engagement_id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z",
  "billing_type": "hourly_project_rate",
  "fixed_fee": null,
  "budget": 120,
  "budget_is_monthly": false,
  "over_budget_notification_percentage": 80,
  "show_budget_to_all": false,
  "cost_budget": 9000,
  "cost_budget_include_expenses": false,
  "fee_budget": 18000,
  "fee_budget_is_monthly": false,
  "fee_budget_notification_percentage": 80
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/projects/{id}Retrieve a project

A plain member sees budget only when show_budget_to_all is true, and never sees cost_budget or the fee budget fields (null), whatever show_budget_to_all says. billing_type and fixed_fee are null for a plain member. On a fixed_fee project, reports count fixed_fee spread over the billable hours logged to the project across all time; changing billing_type away from fixed_fee keeps the fee, as the app does.

PARAMETERS
  • id (required) — The project id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
  "name": "Acme Rebrand",
  "code": null,
  "is_active": true,
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "hourly_rate": 145,
  "engagement_id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z",
  "billing_type": "hourly_project_rate",
  "fixed_fee": null,
  "budget": 120,
  "budget_is_monthly": false,
  "over_budget_notification_percentage": 80,
  "show_budget_to_all": false,
  "cost_budget": 9000,
  "cost_budget_include_expenses": false,
  "fee_budget": 18000,
  "fee_budget_is_monthly": false,
  "fee_budget_notification_percentage": 80
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/projects/{id}Update a project · manager+

Moving a project to another client while it is in an engagement is refused (422) unless the same request sets engagement_id to null or to one of the new client’s engagements. Send only the fields to change: the budget fields left out keep their values. budget_by is not accepted, because a project can carry a project budget, per-person budgets and per-task budgets at once. Unknown fields are refused with 400.

PARAMETERS
  • id (required) — The project id (UUID)
BODY
  • client_id — UUID
  • name — Project name
  • code — Short code
  • is_active — false archives, true restores
  • hourly_rate — Project hourly rate
  • starts_on — YYYY-MM-DD
  • ends_on — YYYY-MM-DD
  • engagement_id — UUID of one of the client’s engagements; null takes the project out of its engagement
  • billing_type — hourly_project_rate, hourly_person_rate, hourly_task_rate, fixed_fee or non_billable
  • fixed_fee — The fee for a fixed_fee project, in its currency; null clears it. Refused (422) on any other billing type
  • budget — Project hours budget; null removes it
  • budget_is_monthly — true makes budget a monthly allowance instead of a total
  • over_budget_notification_percentage — When the over-budget alert fires, 1-100 (default 80); null switches alerts off. Left out, it keeps its value. Reads back as null when alerts are off, or when the project has no hours or cost budget (budget and cost_budget null)
  • show_budget_to_all — true lets members see budget progress
  • cost_budget — Internal cost budget in the workspace currency; null removes it
  • cost_budget_include_expenses — true counts expenses against cost_budget
  • fee_budget — Fee budget in the project currency; null removes it. The billable hours at their hourly rates are measured against it, on a fixed-fee project too
  • fee_budget_is_monthly — true makes fee_budget a monthly amount instead of a total
  • fee_budget_notification_percentage — When the fee budget alert fires, 1-100 (default 80); null switches it off. Separate from the hours budget threshold
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 422A budget setting (budget_is_monthly, over_budget_notification_percentage, cost_budget_include_expenses) was sent for a project with no budget, or in the same request that removes its budgets; or a fee budget setting (fee_budget_is_monthly, fee_budget_notification_percentage) for a project with no fee budget, or in the request that removes it. The message says which. Send budget or cost_budget (fee_budget for the fee settings) with it; show_budget_to_all alone is not a budget.
  • 422engagement_id names another client’s engagement, or client_id moves the project away from the client its engagement belongs to. Nothing is changed. The message says which.
  • 422fixed_fee was sent for a project whose billing_type is not fixed_fee. Send billing_type "fixed_fee" with it. Nothing is changed.
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
  "name": "Acme Rebrand",
  "code": null,
  "is_active": true,
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "hourly_rate": 145,
  "engagement_id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z",
  "billing_type": "hourly_project_rate",
  "fixed_fee": null,
  "budget": 120,
  "budget_is_monthly": false,
  "over_budget_notification_percentage": 80,
  "show_budget_to_all": false,
  "cost_budget": 9000,
  "cost_budget_include_expenses": false,
  "fee_budget": 18000,
  "fee_budget_is_monthly": false,
  "fee_budget_notification_percentage": 80
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/projects/{id}Archive a project · manager+

Archives the project; it is not deleted, and keeps its time, expenses and tasks. Deleting for good is done in the app, by an owner or admin, once the project is archived and has nothing attached.

PARAMETERS
  • id (required) — The project id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}
GET/v1/projects/{id}/user_assignmentsList a project’s people
PARAMETERS
  • id (required) — The project id (UUID)
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "user_assignments": [
    {
      "id": "5e6f7081-92a3-4d4e-b5c6-d7e8f90a1b2c",
      "is_project_manager": false,
      "is_active": true,
      "hourly_rate": 145,
      "budget": 40,
      "user": {
        "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
        "name": "Priya Nair"
      },
      "project": {
        "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
        "name": "Acme Rebrand"
      }
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/projects/ID/user_assignments?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/projects/ID/user_assignments?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/projects/{id}/user_assignmentsAssign a person to a project · manager+
PARAMETERS
  • id (required) — The project id (UUID)
BODY
  • user_id — Member UUID, required
  • is_project_manager — Grants the PM flag
  • hourly_rate — Per-assignment billable rate
  • budget — This person’s hours budget on the project, up to two decimals
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "5e6f7081-92a3-4d4e-b5c6-d7e8f90a1b2c",
  "is_project_manager": false,
  "is_active": true,
  "hourly_rate": 145,
  "budget": 40,
  "user": {
    "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
    "name": "Priya Nair"
  },
  "project": {
    "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
    "name": "Acme Rebrand"
  }
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/projects/{id}/user_assignments/{assignmentId}Update an assignment · manager+

The assignment must belong to the project in the path, or 404 and nothing changes. budget is null for a plain member reading assignments, as hourly_rate is.

PARAMETERS
  • id (required) — The project id (UUID)
  • assignmentId (required) — Assignment UUID
BODY
  • is_project_manager — PM flag
  • hourly_rate — Per-assignment billable rate
  • budget — This person’s hours budget on the project; null removes it
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "5e6f7081-92a3-4d4e-b5c6-d7e8f90a1b2c",
  "is_project_manager": false,
  "is_active": true,
  "hourly_rate": 145,
  "budget": 40,
  "user": {
    "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
    "name": "Priya Nair"
  },
  "project": {
    "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
    "name": "Acme Rebrand"
  }
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/projects/{id}/user_assignments/{assignmentId}Remove an assignment · manager+
PARAMETERS
  • id (required) — The project id (UUID)
  • assignmentId (required) — Assignment UUID
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}
GET/v1/projects/{id}/task_assignmentsList a project’s tasks (compatibility view)

Tasks belong to projects here, so each task is its own assignment.

PARAMETERS
  • id (required) — The project id (UUID)
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "tasks": [
    {
      "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Design",
      "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
      "billable_by_default": true,
      "default_hourly_rate": 145,
      "budget": 24,
      "is_active": true,
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/projects/ID/task_assignments?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/projects/ID/task_assignments?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}

Engagements

Named groups of one client’s projects — “Support”, “Website rebuild” — for reporting and budgeting across them. Two clients can each have an engagement of the same name.

GET/v1/engagementsList engagements

Sorted by client, then name. project_count includes archived projects. List an engagement’s projects with GET /v1/projects?engagement_id=&include_archived=true.

PARAMETERS
  • client_id — One client’s engagements
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "engagements": [
    {
      "id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
      "name": "Support",
      "notes": "Annual support terms, renewed each January.",
      "client": {
        "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "Acme Corp"
      },
      "project_count": 3,
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/engagements?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/engagements?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/engagementsCreate an engagement · manager+

Adds no projects: set engagement_id on a project to put it in. Unknown fields are refused with 400.

BODY
  • client_id — UUID, required
  • name — Required. Unique on the client, ignoring capitalization
  • notes — Notes
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No client with that client_id in this workspace
  • 409The client already has an engagement of that name
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
  "name": "Support",
  "notes": "Annual support terms, renewed each January.",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "project_count": 3,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/engagements/{id}Retrieve an engagement
PARAMETERS
  • id (required) — The engagement id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
  "name": "Support",
  "notes": "Annual support terms, renewed each January.",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "project_count": 3,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/engagements/{id}Update an engagement · manager+

The client cannot be changed: the engagement’s projects belong to it. Unknown fields are refused with 400.

PARAMETERS
  • id (required) — The engagement id (UUID)
BODY
  • name — Engagement name
  • notes — Notes; null clears them
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 409The client already has an engagement of that name
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
  "name": "Support",
  "notes": "Annual support terms, renewed each January.",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "project_count": 3,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/engagements/{id}Delete an engagement · manager+

Deletes the engagement for good. Its projects are kept, archived ones included, and simply have no engagement afterwards.

PARAMETERS
  • id (required) — The engagement id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}

Tasks

Tasks (each belongs to one project).

GET/v1/tasksList tasks
PARAMETERS
  • project_id — Filter by project
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "tasks": [
    {
      "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
      "name": "Design",
      "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
      "billable_by_default": true,
      "default_hourly_rate": 145,
      "budget": 24,
      "is_active": true,
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/tasks?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/tasks?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/tasksCreate a task · manager+
BODY
  • project_id — UUID, required
  • name — Required
  • billable_by_default — Defaults true
  • default_hourly_rate — Task rate
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "name": "Design",
  "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
  "billable_by_default": true,
  "default_hourly_rate": 145,
  "budget": 24,
  "is_active": true,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/tasks/{id}Retrieve a task
PARAMETERS
  • id (required) — The task id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "name": "Design",
  "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
  "billable_by_default": true,
  "default_hourly_rate": 145,
  "budget": 24,
  "is_active": true,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/tasks/{id}Update a task · manager+
PARAMETERS
  • id (required) — The task id (UUID)
BODY
  • name — Task name
  • billable_by_default — true/false
  • default_hourly_rate — Task rate
  • budget — Task hours budget, up to two decimals; null removes it
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
  "name": "Design",
  "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
  "billable_by_default": true,
  "default_hourly_rate": 145,
  "budget": 24,
  "is_active": true,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/tasks/{id}Delete a task · manager+
PARAMETERS
  • id (required) — The task id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}

People

Employee profiles from the People add-on: job title, dates, contact details, emergency contact, linked accounts and recorded systems access. Personal access tokens created with **Include People profile data** only (the `people` scope) — it is never on by default, an organization key is refused, and connected (OAuth) apps cannot reach these. A token belonging to an HR admin sees everybody, including archived people; any other token sees only its owner. Compensation and documents are never returned.

GET/v1/peopleList profiles · personal token

`systems_with_access` is null when the workspace does not track systems access or the token is not an HR admin’s — null never means "none".

PARAMETERS
  • include_archived — false leaves out people who have left. Defaults to true.
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (personal)
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "people": [
    {
      "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
      "name": "Priya Nair",
      "email": "[email protected]",
      "is_archived": false,
      "is_invited": false,
      "has_profile": true,
      "percent_complete": 90,
      "job_title": "Senior Designer",
      "start_date": "2024-03-04",
      "end_date": null,
      "personal_email": "[email protected]",
      "phone": "+1 555 0100",
      "address": {
        "line_1": "12 Example Street",
        "line_2": null,
        "city": "Austin",
        "region": "TX",
        "postcode": "78701",
        "country": "US"
      },
      "emergency_contact": {
        "name": "Arun Nair",
        "phone": "+1 555 0101",
        "relation": "Brother"
      },
      "accounts": {
        "github": "priya-nair",
        "slack": null,
        "jira": null,
        "linkedin": null
      },
      "systems_with_access": [
        "Google Workspace",
        "GitHub"
      ]
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/people?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/people?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/people/{id}Retrieve a profile · personal token

Returns 404 for a person the token may not see, the same as for one who does not exist.

PARAMETERS
  • id (required) — The membership id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (personal)
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
  "name": "Priya Nair",
  "email": "[email protected]",
  "is_archived": false,
  "is_invited": false,
  "has_profile": true,
  "percent_complete": 90,
  "job_title": "Senior Designer",
  "start_date": "2024-03-04",
  "end_date": null,
  "personal_email": "[email protected]",
  "phone": "+1 555 0100",
  "address": {
    "line_1": "12 Example Street",
    "line_2": null,
    "city": "Austin",
    "region": "TX",
    "postcode": "78701",
    "country": "US"
  },
  "emergency_contact": {
    "name": "Arun Nair",
    "phone": "+1 555 0101",
    "relation": "Brother"
  },
  "accounts": {
    "github": "priya-nair",
    "slack": null,
    "jira": null,
    "linkedin": null
  },
  "systems_with_access": [
    "Google Workspace",
    "GitHub"
  ]
}
ERRORS
{
  "message": "Project not found"
}

Users

Workspace members. Rates are money — manager+ tokens only.

GET/v1/usersList users
PARAMETERS
  • role — owner, admin, manager, or member
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "users": [
    {
      "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
      "name": "Priya Nair",
      "first_name": "Priya",
      "last_name": "Nair",
      "email": "[email protected]",
      "kind": "human",
      "is_active": true,
      "access_roles": [
        "member"
      ],
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/users?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/users?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/users/meRetrieve the token’s owner · personal token
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (personal)
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
  "name": "Priya Nair",
  "first_name": "Priya",
  "last_name": "Nair",
  "email": "[email protected]",
  "kind": "human",
  "is_active": true,
  "access_roles": [
    "member"
  ],
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/users/{id}Retrieve a user
PARAMETERS
  • id (required) — The user id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
  "name": "Priya Nair",
  "first_name": "Priya",
  "last_name": "Nair",
  "email": "[email protected]",
  "kind": "human",
  "is_active": true,
  "access_roles": [
    "member"
  ],
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/users/{id}/billable_ratesList a user’s billable rates · manager+
PARAMETERS
  • id (required) — The user id (UUID)
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "billable_rates": [
    {
      "id": "6f708192-a3b4-4e5f-c6d7-e8f90a1b2c3d",
      "amount": 145,
      "start_date": "2026-01-01",
      "end_date": null
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/users/ID/billable_rates?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/users/ID/billable_rates?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/users/{id}/cost_ratesList a user’s cost rates · manager+
PARAMETERS
  • id (required) — The user id (UUID)
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "cost_rates": [
    {
      "id": "6f708192-a3b4-4e5f-c6d7-e8f90a1b2c3d",
      "amount": 68,
      "start_date": "2026-01-01",
      "end_date": null
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/users/ID/cost_rates?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/users/ID/cost_rates?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/users/{id}/project_assignmentsList a user’s project assignments
PARAMETERS
  • id (required) — The user id (UUID)
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "user_assignments": [
    {
      "id": "5e6f7081-92a3-4d4e-b5c6-d7e8f90a1b2c",
      "is_project_manager": false,
      "is_active": true,
      "hourly_rate": 145,
      "budget": 40,
      "user": {
        "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
        "name": "Priya Nair"
      },
      "project": {
        "id": "9c8b7a65-4321-4fed-9876-543210fedcba",
        "name": "Acme Rebrand"
      }
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/users/ID/project_assignments?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/users/ID/project_assignments?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/users/{id}/teammatesList a user’s direct reports
PARAMETERS
  • id (required) — The user id (UUID)
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "users": [
    {
      "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
      "name": "Priya Nair",
      "first_name": "Priya",
      "last_name": "Nair",
      "email": "[email protected]",
      "kind": "human",
      "is_active": true,
      "access_roles": [
        "member"
      ],
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/users/ID/teammates?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/users/ID/teammates?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}

Roles

Work roles (job functions), assignable to members.

GET/v1/rolesList roles
PARAMETERS
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "roles": [
    {
      "id": "80192a3b-4c5d-4f60-9718-293a4b5c6d7e",
      "name": "Senior designer",
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/roles?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/roles?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/rolesCreate a role · manager+
BODY
  • name — Required
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "80192a3b-4c5d-4f60-9718-293a4b5c6d7e",
  "name": "Senior designer",
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/roles/{id}Update a role · manager+
PARAMETERS
  • id (required) — The role id (UUID)
BODY
  • name — Required
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "80192a3b-4c5d-4f60-9718-293a4b5c6d7e",
  "name": "Senior designer",
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/roles/{id}Delete a role · manager+
PARAMETERS
  • id (required) — The role id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}

Invoices

Invoices, payments, and sending. Admin tokens or organization keys only.

GET/v1/invoicesList invoices
PARAMETERS
  • state — draft, open, partially_paid, paid, closed
  • client_id — Filter by client
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "invoices": [
    {
      "id": "4d5e6f70-8192-4c3d-a4b5-c6d7e8f90a1b",
      "number": "1042",
      "state": "draft",
      "subject": "August retainer",
      "client": {
        "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "Acme Corp"
      },
      "amount": 5400,
      "paid_amount": 0,
      "due_amount": 5400,
      "currency": "USD",
      "issue_date": "2026-08-01",
      "due_date": "2026-08-31",
      "period_start": "2026-07-01",
      "period_end": "2026-07-31",
      "sent_at": null,
      "created_at": "2026-08-03T09:12:00.000Z",
      "updated_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/invoices?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/invoices?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/invoicesCreate an invoice · admin

Provide line_items OR period_start+period_end. Generated invoices mark hours billed so nothing is double-invoiced.

BODY
  • client_id — UUID, required
  • line_items — Array of {kind, description, quantity, unit_price} for a free-form invoice
  • period_start — With period_end: generate from uninvoiced time instead
  • period_end — YYYY-MM-DD
  • grouping — task (default), person, or detailed
  • include_expenses — Defaults true
  • subject — Subject line
  • issue_date — Defaults today
  • due_date — YYYY-MM-DD
  • tax — Percent
  • discount — Percent
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "4d5e6f70-8192-4c3d-a4b5-c6d7e8f90a1b",
  "number": "1042",
  "state": "draft",
  "subject": "August retainer",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "amount": 5400,
  "paid_amount": 0,
  "due_amount": 5400,
  "currency": "USD",
  "issue_date": "2026-08-01",
  "due_date": "2026-08-31",
  "period_start": "2026-07-01",
  "period_end": "2026-07-31",
  "sent_at": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/invoices/{id}Retrieve an invoice (lines + payments)
PARAMETERS
  • id (required) — The invoice id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "4d5e6f70-8192-4c3d-a4b5-c6d7e8f90a1b",
  "number": "1042",
  "state": "draft",
  "subject": "August retainer",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "amount": 5400,
  "paid_amount": 0,
  "due_amount": 5400,
  "currency": "USD",
  "issue_date": "2026-08-01",
  "due_date": "2026-08-31",
  "period_start": "2026-07-01",
  "period_end": "2026-07-31",
  "sent_at": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/invoices/{id}/ublDownload the invoice as a UBL 2.1 (EN 16931) XML e-invoice

Returns application/xml (not the JSON envelope). Supplier details come from Invoices → Configure → Company info.

PARAMETERS
  • id (required) — The invoice id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/invoices/{id}Update a draft invoice · admin
PARAMETERS
  • id (required) — The invoice id (UUID)
BODY
  • subject — Subject
  • notes — Notes
  • issue_date — YYYY-MM-DD
  • due_date — YYYY-MM-DD
  • tax — Percent
  • discount — Percent
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "4d5e6f70-8192-4c3d-a4b5-c6d7e8f90a1b",
  "number": "1042",
  "state": "draft",
  "subject": "August retainer",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "amount": 5400,
  "paid_amount": 0,
  "due_amount": 5400,
  "currency": "USD",
  "issue_date": "2026-08-01",
  "due_date": "2026-08-31",
  "period_start": "2026-07-01",
  "period_end": "2026-07-31",
  "sent_at": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/invoices/{id}Delete a draft invoice · admin
PARAMETERS
  • id (required) — The invoice id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}
POST/v1/invoices/{id}/messagesMark an invoice sent · admin

Moves draft → open. Email delivery stays a deliberate in-app action.

PARAMETERS
  • id (required) — The invoice id (UUID)
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "4d5e6f70-8192-4c3d-a4b5-c6d7e8f90a1b",
  "event_type": "send",
  "state": "open"
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/invoices/{id}/paymentsRecord a payment · admin
PARAMETERS
  • id (required) — The invoice id (UUID)
BODY
  • amount — Required
  • paid_date — YYYY-MM-DD, required
  • notes — Notes
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "4d5e6f70-8192-4c3d-a4b5-c6d7e8f90a1b",
  "number": "1042",
  "state": "draft",
  "subject": "August retainer",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "amount": 5400,
  "paid_amount": 0,
  "due_amount": 5400,
  "currency": "USD",
  "issue_date": "2026-08-01",
  "due_date": "2026-08-31",
  "period_start": "2026-07-01",
  "period_end": "2026-07-31",
  "sent_at": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/invoices/{id}/payments/{paymentId}Delete a payment · admin
PARAMETERS
  • id (required) — The invoice id (UUID)
  • paymentId (required) — Payment UUID
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}

Estimates

Quotes that convert to invoices. Admin tokens or organization keys only.

GET/v1/estimatesList estimates · manager+
PARAMETERS
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "estimates": [
    {
      "id": "b2c3d4e5-f607-4182-93a4-b5c6d7e8f90a",
      "number": "204",
      "state": "draft",
      "subject": "Rebrand — phase two",
      "client": {
        "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
        "name": "Acme Corp"
      },
      "amount": 18500,
      "currency": "USD",
      "issue_date": "2026-08-01",
      "created_at": "2026-08-03T09:12:00.000Z"
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/estimates?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/estimates?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/estimatesCreate an estimate · admin
BODY
  • client_id — UUID, required
  • line_items — Array of {kind, description, quantity, unit_price}, required
  • subject — Subject
  • issue_date — Defaults today
  • tax — Percent
  • discount — Percent
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "b2c3d4e5-f607-4182-93a4-b5c6d7e8f90a",
  "number": "204",
  "state": "draft",
  "subject": "Rebrand — phase two",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "amount": 18500,
  "currency": "USD",
  "issue_date": "2026-08-01",
  "created_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/estimates/{id}Retrieve an estimate · manager+
PARAMETERS
  • id (required) — The estimate id (UUID)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "b2c3d4e5-f607-4182-93a4-b5c6d7e8f90a",
  "number": "204",
  "state": "draft",
  "subject": "Rebrand — phase two",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "amount": 18500,
  "currency": "USD",
  "issue_date": "2026-08-01",
  "created_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
PATCH/v1/estimates/{id}Change estimate state · admin
PARAMETERS
  • id (required) — The estimate id (UUID)
BODY
  • state — sent, accepted, or declined
RESPONSES
  • 200OK
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "b2c3d4e5-f607-4182-93a4-b5c6d7e8f90a",
  "number": "204",
  "state": "draft",
  "subject": "Rebrand — phase two",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "amount": 18500,
  "currency": "USD",
  "issue_date": "2026-08-01",
  "created_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/estimates/{id}Delete a draft estimate · admin
PARAMETERS
  • id (required) — The estimate id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}
POST/v1/estimates/{id}/convertConvert to a draft invoice · admin
PARAMETERS
  • id (required) — The estimate id (UUID)
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (admin)
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "4d5e6f70-8192-4c3d-a4b5-c6d7e8f90a1b",
  "number": "1042",
  "state": "draft",
  "subject": "August retainer",
  "client": {
    "id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
    "name": "Acme Corp"
  },
  "amount": 5400,
  "paid_amount": 0,
  "due_amount": 5400,
  "currency": "USD",
  "issue_date": "2026-08-01",
  "due_date": "2026-08-31",
  "period_start": "2026-07-01",
  "period_end": "2026-07-31",
  "sent_at": null,
  "created_at": "2026-08-03T09:12:00.000Z",
  "updated_at": "2026-08-03T09:12:00.000Z"
}
ERRORS
{
  "message": "Project not found"
}

Expenses

Expenses and categories. Member tokens manage their own.

GET/v1/expensesList expenses
PARAMETERS
  • user_id — Required with an organization key
  • from — YYYY-MM-DD
  • to — YYYY-MM-DD
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "expenses": [
    {
      "id": "90a1b2c3-d4e5-4f60-8172-93a4b5c6d7e8",
      "spent_date": "2026-08-03",
      "total_cost": 42.5,
      "billable": true,
      "is_reimbursable": true,
      "notes": "Stock photography license",
      "project": {
        "name": "Acme Rebrand"
      },
      "client": {
        "name": "Acme Corp"
      },
      "expense_category": {
        "name": "Software"
      },
      "user": {
        "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9"
      }
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/expenses?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/expenses?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/expensesCreate an expense
BODY
  • spent_date — YYYY-MM-DD, required
  • total_cost — Required
  • project_id — UUID
  • expense_category_id — UUID
  • billable — true/false
  • is_reimbursable — true/false
  • notes — Notes
  • user_id — Required with an organization key
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "90a1b2c3-d4e5-4f60-8172-93a4b5c6d7e8",
  "spent_date": "2026-08-03",
  "total_cost": 42.5,
  "billable": true,
  "is_reimbursable": true,
  "notes": "Stock photography license",
  "project": {
    "name": "Acme Rebrand"
  },
  "client": {
    "name": "Acme Corp"
  },
  "expense_category": {
    "name": "Software"
  },
  "user": {
    "id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9"
  }
}
ERRORS
{
  "message": "Project not found"
}
DELETE/v1/expenses/{id}Delete an expense
PARAMETERS
  • id (required) — The expense id (UUID)
RESPONSES
  • 200Done. The body is empty.
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope
  • 404No such record in this workspace
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
ERRORS
{
  "message": "Project not found"
}
GET/v1/expense_categoriesList expense categories
PARAMETERS
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "expense_categories": [
    {
      "id": "a1b2c3d4-e5f6-4071-8293-a4b5c6d7e8f9",
      "name": "Software",
      "unit_price": null,
      "is_active": true
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/expense_categories?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/expense_categories?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
POST/v1/expense_categoriesCreate an expense category · manager+
BODY
  • name — Required
  • unit_price — For unit-based categories
RESPONSES
  • 201Created
  • 400Malformed JSON, or a field failed validation
  • 401Missing, expired or invalid token
  • 403Token lacks the write scope or the required grade (org-wide)
  • 422Refused by a business rule — the message says which
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "id": "a1b2c3d4-e5f6-4071-8293-a4b5c6d7e8f9",
  "name": "Software",
  "unit_price": null,
  "is_active": true
}
ERRORS
{
  "message": "Project not found"
}

Reports

Aggregations with money — manager+ tokens or organization keys.

GET/v1/reports/time/{dimension}Time report by clients, projects, tasks, or team · manager+

`billable_amount` is billable hours at their hourly rates, except on a fixed-fee project, where it is the fee spread over all the billable hours logged to the project: a range gets its share of the fee, and the figure moves as more hours are logged. A fixed-fee project with no fee set earns 0, and its row says `fixed_fee_not_set: true`. Uninvoiced amounts and invoices still price time at hourly rates. Rows are grouped by id, so two projects (or clients, or people) with the same name are two rows. Each carries its id beside its name — `client_id` on clients rows, `project_id` with `client_id` and `client_name` on projects rows, `user_id` on team rows — and a name that another one in the workspace shares is qualified: a project with its client, "Support (Northwind)", and numbered as well if another such project has a client of the same name, "Support (Northwind) (2)"; a client or person with a number, "Alex Kim (2)". Match on the ids, not the names. Rows are paginated like every list: `per_page` rows a page (100 by default, up to 2,000), with `total_entries` counting them all — follow `next_page`. Tasks group by name across projects, as one kind of work.

PARAMETERS
  • dimension (required) — clients, projects, tasks, team, or engagements
  • from (required) — YYYY-MM-DD
  • to (required) — YYYY-MM-DD
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "project_name": "Acme Rebrand",
  "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
  "client_id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
  "client_name": "Acme Corp",
  "total_hours": 128.5,
  "billable_hours": 119,
  "billable_amount": 17255,
  "fixed_fee_not_set": false
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/reports/expenses/{dimension}Expense report by categories, projects, or team · manager+
PARAMETERS
  • dimension (required) — categories, projects, or team
  • from (required) — YYYY-MM-DD
  • to (required) — YYYY-MM-DD
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 404No such record in this workspace
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE RESPONSE
{
  "project": "Acme Rebrand",
  "total_amount": 412.75,
  "billable_amount": 380
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/reports/project_budgetHours budget vs actual per project · manager+

One row per project with an hours, cost or fee budget, or with per-person budgets; client_id and client_name say whose project it is. When budget_resets_monthly is true, budget is ONE month’s allowance and budget_remaining subtracts the whole range’s spend from it; compare budget_spent with budget_for_range instead, which is budget times months_in_range (the calendar months the range touches), with budget_remaining_for_range beside it. For a budget that does not reset, budget_for_range equals budget. A project budgeted only per person has budget, budget_for_range and both remaining fields null. budget_by says what budget is: project_hours whenever the project has an hours budget, whatever else it carries. With none, budget is null and budget_by names the budget the row is listed for instead: project_fees (see the fee_budget fields), then person_hours (see budget_people), then internal_cost (see cost_budget), in that order when there are several; none is a row with no budget set. budget_people is empty unless the project has per-person budgets; when it has, it lists each person with a budget and everyone else who logged time in the range, assigned or not, with budget and budget_remaining null and has_budget false. Where the project has a fee budget, the row adds fee_budget, fee_budget_is_monthly, fee_budget_spent (the billable hours in range at their hourly rates — not the fixed-fee allocation), fee_budget_remaining, and fee_budget_for_range with fee_budget_remaining_for_range, scaled by months_in_range like the hours; without one these fields are absent.

PARAMETERS
  • from (required) — YYYY-MM-DD
  • to (required) — YYYY-MM-DD
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "results": [
    {
      "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
      "project_name": "Acme Rebrand",
      "client_id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
      "client_name": "Acme Corp",
      "budget": 40,
      "budget_spent": 98,
      "budget_remaining": -58,
      "budget_by": "project_hours",
      "budget_resets_monthly": true,
      "months_in_range": 3,
      "budget_for_range": 120,
      "budget_remaining_for_range": 22,
      "fee_budget": 6000,
      "fee_budget_is_monthly": true,
      "fee_budget_spent": 14250,
      "fee_budget_remaining": -8250,
      "fee_budget_for_range": 18000,
      "fee_budget_remaining_for_range": 3750,
      "budget_people": [
        {
          "user_id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
          "user_name": "Ana Ortiz",
          "budget": 30,
          "budget_spent": 26.5,
          "budget_remaining": 3.5,
          "has_budget": true
        }
      ]
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/reports/project_budget?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/reports/project_budget?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/reports/engagement_budgetHours budget vs actual per engagement · manager+

One row per engagement, archived projects included. budget_for_range sums its projects’ hours budgets over the range (a monthly budget times months_in_range), and is null when no project in it has one. actual_hours is every project in the engagement; budgeted_actual_hours only the projects with a budget, and budget_remaining_for_range is budget_for_range minus that — hours on an unbudgeted project are not set against another project’s budget. projects lists each project with its own figures. budget_people sums per-person budgets across the engagement’s projects that budget per person, and lists everyone else who logged time on them with budget null and has_budget false, as project_budget does. fee_budgets is the fee budgets of its projects over the range, one entry per currency (amounts in different currencies are never added): budget_for_range (a monthly fee budget times months_in_range), spent (the billable hours in the range at their hourly rates) and remaining_for_range; empty when no project has one. A project with a fee budget also carries fee_budget_for_range, fee_spent and currency.

PARAMETERS
  • from (required) — YYYY-MM-DD
  • to (required) — YYYY-MM-DD
  • engagement_id — One engagement
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "results": [
    {
      "engagement_id": "a2b3c4d5-e6f7-4081-9a2b-3c4d5e6f7a8b",
      "engagement_name": "Support",
      "client_id": "2b3c4d5e-6f70-4b8c-9d0e-1f2a3b4c5d6e",
      "client_name": "Acme Corp",
      "months_in_range": 3,
      "budget_for_range": 60,
      "actual_hours": 52.5,
      "budgeted_actual_hours": 48,
      "budget_remaining_for_range": 12,
      "projects": [
        {
          "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
          "project_name": "Support 2026",
          "is_active": true,
          "budget_for_range": 60,
          "actual_hours": 48,
          "fee_budget_for_range": 9000,
          "fee_spent": 6960,
          "currency": "USD"
        }
      ],
      "fee_budgets": [
        {
          "currency": "USD",
          "budget_for_range": 9000,
          "spent": 6960,
          "remaining_for_range": 2040
        }
      ],
      "budget_people": [
        {
          "user_id": "7a1b2c3d-4e5f-4061-8273-8495a6b7c8d9",
          "user_name": "Ana Ortiz",
          "budget": 30,
          "budget_spent": 26.5,
          "budget_remaining": 3.5,
          "has_budget": true
        }
      ]
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/reports/engagement_budget?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/reports/engagement_budget?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}
GET/v1/reports/uninvoicedApproved, unbilled hours and amounts per project · manager+
PARAMETERS
  • page — Page number, starting at 1
  • per_page — Items per page (default 100, max 2000)
RESPONSES
  • 200OK
  • 401Missing, expired or invalid token
  • 403Token lacks the read scope or the required grade (org-wide)
  • 429Rate limited. Retry after the Retry-After header.
EXAMPLE LIST
{
  "results": [
    {
      "project_id": "9c8b7a65-4321-4fed-9876-543210fedcba",
      "project_name": "Acme Rebrand",
      "client_name": "Acme Corp",
      "uninvoiced_hours": 22.75,
      "uninvoiced_amount": 3298.75,
      "uninvoiced_expenses": 42.5
    }
  ],
  "per_page": 100,
  "total_pages": 1,
  "total_entries": 1,
  "next_page": null,
  "previous_page": null,
  "page": 1,
  "links": {
    "first": "https://timetrakkr.com/api/v1/reports/uninvoiced?page=1",
    "next": null,
    "previous": null,
    "last": "https://timetrakkr.com/api/v1/reports/uninvoiced?page=1"
  }
}
ERRORS
{
  "message": "Project not found"
}