API Reference

REST API for Tracking Tabs. Base URL: https://demo.trackingtabs.com/api/v1

Machine-readable spec: /api/v1/openapi.json (OpenAPI 3.0; import it into Postman, Insomnia or a client generator). AI assistants: MCP server.

Getting started

1. Create a personal access token at Settings → API Tokens. A token belongs to one workspace and acts with your role there. API access needs a Solo, Team or Business plan; GET /api/v1/me works on every plan.

2. Send it as a bearer token:

curl -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  https://demo.trackingtabs.com/api/v1/me

3. Requests and responses are JSON. Lists return { "data": [...], "total", "limit", "offset" }; single records are returned as the object itself; deletes return 204. Errors always look like { "error": { "code", "message", "details"? } }.

Access. Members see and change only their own time entries and only projects that aren't restricted to other people. Admins and owners see the whole workspace; creating or changing projects, clients and webhooks, and deleting tags, tasks and invoices, needs admin or owner.

Rate limit. 600 requests per hour per token. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (Unix seconds); a 429 adds Retry-After.

CORS. The API allows any origin (auth is the bearer token, never a cookie), so browser apps and extensions can call it directly. Never ship a token in a public web page.

Retries. POST /api/v1/entries accepts an idempotency_key (or an Idempotency-Key header); repeating it returns the original entry with 200.

Webhook deliveries

Events: time_entry.created, time_entry.updated, time_entry.deleted, project.created, project.updated, project.deleted, client.created, client.updated, client.deleted. They fire for changes made through the API.

Each delivery is a POST with JSON { "event", "created_at", "data" }, where data is the record in the same shape the API returns. Headers: X-Webhook-Event, User-Agent: TrackingTabs-Webhooks/1.0 and X-Signature: sha256=<hex>, the HMAC-SHA256 of the raw body keyed with the webhook's secret (shown once when you create it). Compare with a constant-time check. A non-2xx answer is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours (6 attempts in all). URLs on private or internal networks are rejected.

// Node.js
import { createHmac, timingSafeEqual } from 'node:crypto'
const expected = 'sha256=' + createHmac('sha256', SECRET).update(rawBody).digest('hex')
const ok = timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-signature']))

Auth

GET
/api/v1/me

Get the authenticated user and workspace

Returns the user and workspace the bearer token belongs to. Works on every plan, so clients can use it to verify a token and show whether the plan includes API access.

Plan: Any plan

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/me' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "user": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "email": {
          "type": "string",
          "format": "email"
        },
        "full_name": {
          "type": "string",
          "nullable": true
        },
        "timezone": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "email",
        "full_name",
        "timezone"
      ],
      "additionalProperties": false
    },
    "workspace": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "name": {
          "type": "string"
        },
        "slug": {
          "type": "string"
        },
        "plan": {
          "type": "string",
          "enum": [
            "free",
            "solo",
            "team",
            "business"
          ]
        },
        "role": {
          "type": "string",
          "enum": [
            "owner",
            "admin",
            "member"
          ]
        },
        "api_access": {
          "type": "boolean",
          "description": "Whether the plan includes API access"
        }
      },
      "required": [
        "id",
        "name",
        "slug",
        "plan",
        "role",
        "api_access"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "user",
    "workspace"
  ],
  "additionalProperties": false
}

Entries

GET
/api/v1/entries

List time entries

Returns time entries in the token's workspace, newest first by `start`. Members see only their own entries; admins and owners see everyone's and can filter by `user_id`.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
fromquerydate-timeInclusive lower bound on `start`.
toquerydate-timeExclusive upper bound on `start`.
project_idqueryuuidOnly this project.
task_idqueryuuidOnly this task.
client_idqueryuuidOnly entries on projects of this client.
tagquerystringOnly entries carrying this tag name.
billablequery"true" | "false"`true` for billable entries only, `false` for non-billable only.
runningquery"true" | "false"`true` for running timers only, `false` for completed entries only.
user_idqueryuuidOnly this user (admins/owners; ignored for members).
limitqueryinteger (1–200) = 50Page size (default 50, max 200).
offsetqueryinteger (0–) = 0Number of records to skip (default 0).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/entries' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "task_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "description": {
            "type": "string"
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          },
          "stop": {
            "type": "string",
            "format": "date-time",
            "description": "`null` while the timer is running",
            "nullable": true
          },
          "duration_seconds": {
            "type": "integer",
            "nullable": true
          },
          "billable": {
            "type": "boolean"
          },
          "hourly_rate": {
            "type": "number",
            "nullable": true,
            "description": "Rate snapshot taken when the entry was created"
          },
          "currency": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tag names"
          },
          "source": {
            "type": "string",
            "description": "Where the entry came from: web, api or import"
          },
          "idempotency_key": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          }
        },
        "required": [
          "id",
          "workspace_id",
          "user_id",
          "project_id",
          "task_id",
          "description",
          "start",
          "stop",
          "duration_seconds",
          "billable",
          "hourly_rate",
          "currency",
          "tags",
          "source",
          "idempotency_key",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false
      }
    },
    "total": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    }
  },
  "required": [
    "data",
    "total",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
POST
/api/v1/entries

Create a time entry

Creates a time entry for the token owner. If `stop` is omitted the entry is a running timer, and a timer already running in this workspace is stopped at the new entry's `start` (409 if the user's timer is running in another workspace — there is one running timer per user). Completed entries may not overlap another entry of the same user (409). Pass `idempotency_key` (or an `Idempotency-Key` header) to retry safely: a repeated key returns the original entry with status 200.

Plan: Solo, Team or Business

Request body

{
  "description": "Drafting Q2 retainer renewal",
  "project_id": "5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456",
  "tags": [
    "client-work",
    "writing"
  ],
  "start": "2026-05-24T14:00:00Z",
  "stop": "2026-05-24T15:30:00Z",
  "billable": true,
  "idempotency_key": "renewal-draft-2026-05-24"
}
Body schema
{
  "type": "object",
  "properties": {
    "description": {
      "type": "string",
      "maxLength": 2000,
      "default": ""
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true,
      "description": "A task implies its project"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 64
      },
      "maxItems": 20,
      "description": "Tag names. Missing tags are created in the workspace."
    },
    "start": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "stop": {
      "type": "string",
      "format": "date-time",
      "description": "Omit to create a running timer",
      "nullable": true
    },
    "billable": {
      "type": "boolean",
      "description": "Defaults to the project's billable flag, else the workspace default"
    },
    "idempotency_key": {
      "type": "string",
      "minLength": 1,
      "maxLength": 128,
      "description": "Retry-safe key; also accepted as the Idempotency-Key header"
    }
  },
  "required": [
    "start"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/entries' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"description":"Drafting Q2 retainer renewal","project_id":"5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456","tags":["client-work","writing"],"start":"2026-05-24T14:00:00Z","stop":"2026-05-24T15:30:00Z","billable":true,"idempotency_key":"renewal-draft-2026-05-24"}'

Responses

201
Created
200
Idempotent replay: the entry created earlier with this key
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
409
Conflict (`conflict`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "user_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "description": {
      "type": "string"
    },
    "start": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "stop": {
      "type": "string",
      "format": "date-time",
      "description": "`null` while the timer is running",
      "nullable": true
    },
    "duration_seconds": {
      "type": "integer",
      "nullable": true
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true,
      "description": "Rate snapshot taken when the entry was created"
    },
    "currency": {
      "type": "string"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Tag names"
    },
    "source": {
      "type": "string",
      "description": "Where the entry came from: web, api or import"
    },
    "idempotency_key": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "user_id",
    "project_id",
    "task_id",
    "description",
    "start",
    "stop",
    "duration_seconds",
    "billable",
    "hourly_rate",
    "currency",
    "tags",
    "source",
    "idempotency_key",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "user_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "description": {
      "type": "string"
    },
    "start": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "stop": {
      "type": "string",
      "format": "date-time",
      "description": "`null` while the timer is running",
      "nullable": true
    },
    "duration_seconds": {
      "type": "integer",
      "nullable": true
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true,
      "description": "Rate snapshot taken when the entry was created"
    },
    "currency": {
      "type": "string"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Tag names"
    },
    "source": {
      "type": "string",
      "description": "Where the entry came from: web, api or import"
    },
    "idempotency_key": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "user_id",
    "project_id",
    "task_id",
    "description",
    "start",
    "stop",
    "duration_seconds",
    "billable",
    "hourly_rate",
    "currency",
    "tags",
    "source",
    "idempotency_key",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
GET
/api/v1/entries/current

Get the running timer

Returns the token owner's running entry in this workspace, or `null` when no timer is running.

Plan: Solo, Team or Business

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/entries/current' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "workspace_id": {
          "type": "string",
          "format": "uuid"
        },
        "user_id": {
          "type": "string",
          "format": "uuid"
        },
        "project_id": {
          "type": "string",
          "format": "uuid",
          "nullable": true
        },
        "task_id": {
          "type": "string",
          "format": "uuid",
          "nullable": true
        },
        "description": {
          "type": "string"
        },
        "start": {
          "type": "string",
          "format": "date-time",
          "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
        },
        "stop": {
          "type": "string",
          "format": "date-time",
          "description": "`null` while the timer is running",
          "nullable": true
        },
        "duration_seconds": {
          "type": "integer",
          "nullable": true
        },
        "billable": {
          "type": "boolean"
        },
        "hourly_rate": {
          "type": "number",
          "nullable": true,
          "description": "Rate snapshot taken when the entry was created"
        },
        "currency": {
          "type": "string"
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "Tag names"
        },
        "source": {
          "type": "string",
          "description": "Where the entry came from: web, api or import"
        },
        "idempotency_key": {
          "type": "string",
          "nullable": true
        },
        "created_at": {
          "type": "string",
          "format": "date-time",
          "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
        },
        "updated_at": {
          "type": "string",
          "format": "date-time",
          "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
        }
      },
      "required": [
        "id",
        "workspace_id",
        "user_id",
        "project_id",
        "task_id",
        "description",
        "start",
        "stop",
        "duration_seconds",
        "billable",
        "hourly_rate",
        "currency",
        "tags",
        "source",
        "idempotency_key",
        "created_at",
        "updated_at"
      ],
      "additionalProperties": false,
      "nullable": true
    }
  },
  "required": [
    "data"
  ],
  "additionalProperties": false
}
POST
/api/v1/entries/start

Start a timer

Starts a timer now for the token owner. A timer already running in this workspace is stopped first. There is one running timer per user, so a timer running in another workspace returns 409.

Plan: Solo, Team or Business

Request body (optional)

{
  "description": "Code review",
  "project_id": "5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456",
  "tags": [
    "review"
  ]
}
Body schema
{
  "type": "object",
  "properties": {
    "description": {
      "type": "string",
      "maxLength": 2000,
      "default": ""
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 64
      },
      "maxItems": 20,
      "description": "Tag names. Missing tags are created in the workspace."
    },
    "billable": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/entries/start' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"description":"Code review","project_id":"5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456","tags":["review"]}'

Responses

201
Timer started
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
409
Conflict (`conflict`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "user_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "description": {
      "type": "string"
    },
    "start": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "stop": {
      "type": "string",
      "format": "date-time",
      "description": "`null` while the timer is running",
      "nullable": true
    },
    "duration_seconds": {
      "type": "integer",
      "nullable": true
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true,
      "description": "Rate snapshot taken when the entry was created"
    },
    "currency": {
      "type": "string"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Tag names"
    },
    "source": {
      "type": "string",
      "description": "Where the entry came from: web, api or import"
    },
    "idempotency_key": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "user_id",
    "project_id",
    "task_id",
    "description",
    "start",
    "stop",
    "duration_seconds",
    "billable",
    "hourly_rate",
    "currency",
    "tags",
    "source",
    "idempotency_key",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
POST
/api/v1/entries/stop

Stop the running timer

Stops the token owner's running entry in this workspace. Returns 404 if no timer is running.

Plan: Solo, Team or Business

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/entries/stop' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Stopped
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "user_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "description": {
      "type": "string"
    },
    "start": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "stop": {
      "type": "string",
      "format": "date-time",
      "description": "`null` while the timer is running",
      "nullable": true
    },
    "duration_seconds": {
      "type": "integer",
      "nullable": true
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true,
      "description": "Rate snapshot taken when the entry was created"
    },
    "currency": {
      "type": "string"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Tag names"
    },
    "source": {
      "type": "string",
      "description": "Where the entry came from: web, api or import"
    },
    "idempotency_key": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "user_id",
    "project_id",
    "task_id",
    "description",
    "start",
    "stop",
    "duration_seconds",
    "billable",
    "hourly_rate",
    "currency",
    "tags",
    "source",
    "idempotency_key",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
GET
/api/v1/entries/{id}

Get a time entry

Members can read only their own entries; admins and owners can read any entry in the workspace.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidEntry ID.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/entries/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "user_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "description": {
      "type": "string"
    },
    "start": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "stop": {
      "type": "string",
      "format": "date-time",
      "description": "`null` while the timer is running",
      "nullable": true
    },
    "duration_seconds": {
      "type": "integer",
      "nullable": true
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true,
      "description": "Rate snapshot taken when the entry was created"
    },
    "currency": {
      "type": "string"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Tag names"
    },
    "source": {
      "type": "string",
      "description": "Where the entry came from: web, api or import"
    },
    "idempotency_key": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "user_id",
    "project_id",
    "task_id",
    "description",
    "start",
    "stop",
    "duration_seconds",
    "billable",
    "hourly_rate",
    "currency",
    "tags",
    "source",
    "idempotency_key",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
PATCH
/api/v1/entries/{id}

Update a time entry

Patches fields on an entry. Members can update only their own entries; admins and owners can update any entry in the workspace. `tags` replaces the entry's tags. Setting `stop` to `null` is not allowed on a completed entry; start a timer instead.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidEntry ID.

Request body

{
  "description": "Drafting Q2 retainer renewal (revised)",
  "billable": true
}
Body schema
{
  "type": "object",
  "properties": {
    "description": {
      "type": "string",
      "maxLength": 2000
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 64
      },
      "maxItems": 20,
      "description": "Replaces the entry's tags"
    },
    "start": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "stop": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "billable": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

Example request

curl -X PATCH 'https://demo.trackingtabs.com/api/v1/entries/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"description":"Drafting Q2 retainer renewal (revised)","billable":true}'

Responses

200
Updated
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
409
Conflict (`conflict`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "user_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "task_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "description": {
      "type": "string"
    },
    "start": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "stop": {
      "type": "string",
      "format": "date-time",
      "description": "`null` while the timer is running",
      "nullable": true
    },
    "duration_seconds": {
      "type": "integer",
      "nullable": true
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true,
      "description": "Rate snapshot taken when the entry was created"
    },
    "currency": {
      "type": "string"
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string"
      },
      "description": "Tag names"
    },
    "source": {
      "type": "string",
      "description": "Where the entry came from: web, api or import"
    },
    "idempotency_key": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "user_id",
    "project_id",
    "task_id",
    "description",
    "start",
    "stop",
    "duration_seconds",
    "billable",
    "hourly_rate",
    "currency",
    "tags",
    "source",
    "idempotency_key",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
DELETE
/api/v1/entries/{id}

Delete a time entry

Deletes an entry permanently. Same access rules as update.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidEntry ID.

Example request

curl -X DELETE 'https://demo.trackingtabs.com/api/v1/entries/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

204
Deleted
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)

Projects

GET
/api/v1/projects

List projects

Returns projects visible to the token owner, ordered by name. If a project has explicit members, only those members (plus admins and owners) see it.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
client_idqueryuuidOnly this client's projects.
archivedquery"true" | "false"`true` to include archived projects (default `false`).
limitqueryinteger (1–1000) = 500Page size (default 500, max 1000).
offsetqueryinteger (0–) = 0Number of records to skip (default 0).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/projects' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "client_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "name": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "billable": {
            "type": "boolean"
          },
          "hourly_rate": {
            "type": "number",
            "nullable": true
          },
          "currency": {
            "type": "string"
          },
          "archived": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          }
        },
        "required": [
          "id",
          "workspace_id",
          "client_id",
          "name",
          "color",
          "billable",
          "hourly_rate",
          "currency",
          "archived",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false
      }
    },
    "total": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    }
  },
  "required": [
    "data",
    "total",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
POST
/api/v1/projects

Create a project

Creates a project. Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Request body

{
  "name": "Q2 Retainer",
  "client_id": "5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456",
  "color": "#3B82F6",
  "billable": true,
  "hourly_rate": 175,
  "currency": "USD"
}
Body schema
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "color": {
      "type": "string",
      "pattern": "^#[0-9a-fA-F]{6}$"
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "minimum": 0,
      "nullable": true
    },
    "currency": {
      "type": "string",
      "minLength": 3,
      "maxLength": 3,
      "description": "ISO 4217 currency code, e.g. USD"
    }
  },
  "required": [
    "name"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/projects' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"name":"Q2 Retainer","client_id":"5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456","color":"#3B82F6","billable":true,"hourly_rate":175,"currency":"USD"}'

Responses

201
Created
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "name": {
      "type": "string"
    },
    "color": {
      "type": "string"
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true
    },
    "currency": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "client_id",
    "name",
    "color",
    "billable",
    "hourly_rate",
    "currency",
    "archived",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
GET
/api/v1/projects/{id}

Get a project

Returns one project, subject to the same visibility rules as the list.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidProject ID.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/projects/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "name": {
      "type": "string"
    },
    "color": {
      "type": "string"
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true
    },
    "currency": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "client_id",
    "name",
    "color",
    "billable",
    "hourly_rate",
    "currency",
    "archived",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
PATCH
/api/v1/projects/{id}

Update or archive a project

Patches a project; set `archived` to archive or restore it. Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidProject ID.

Request body

{
  "hourly_rate": 190,
  "archived": false
}
Body schema
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "color": {
      "type": "string",
      "pattern": "^#[0-9a-fA-F]{6}$"
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "minimum": 0,
      "nullable": true
    },
    "currency": {
      "type": "string",
      "minLength": 3,
      "maxLength": 3,
      "description": "ISO 4217 currency code, e.g. USD"
    },
    "archived": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

Example request

curl -X PATCH 'https://demo.trackingtabs.com/api/v1/projects/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"hourly_rate":190,"archived":false}'

Responses

200
Updated
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "name": {
      "type": "string"
    },
    "color": {
      "type": "string"
    },
    "billable": {
      "type": "boolean"
    },
    "hourly_rate": {
      "type": "number",
      "nullable": true
    },
    "currency": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "client_id",
    "name",
    "color",
    "billable",
    "hourly_rate",
    "currency",
    "archived",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
DELETE
/api/v1/projects/{id}

Delete a project

Deletes a project and its tasks. Time entries are kept and lose their project. Prefer archiving. Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidProject ID.

Example request

curl -X DELETE 'https://demo.trackingtabs.com/api/v1/projects/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

204
Deleted
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)

Clients

GET
/api/v1/clients

List clients

Returns clients in the workspace, ordered by name.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
archivedquery"true" | "false"`true` to include archived clients (default `false`).
limitqueryinteger (1–1000) = 500Page size (default 500, max 1000).
offsetqueryinteger (0–) = 0Number of records to skip (default 0).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/clients' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "archived": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          }
        },
        "required": [
          "id",
          "workspace_id",
          "name",
          "archived",
          "created_at",
          "updated_at"
        ],
        "additionalProperties": false
      }
    },
    "total": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    }
  },
  "required": [
    "data",
    "total",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
POST
/api/v1/clients

Create a client

Creates a client. Names are unique per workspace. Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Request body

{
  "name": "LVC Beverly Hills"
}
Body schema
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    }
  },
  "required": [
    "name"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/clients' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"name":"LVC Beverly Hills"}'

Responses

201
Created
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
409
Conflict (`conflict`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "name",
    "archived",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
GET
/api/v1/clients/{id}

Get a client

Returns one client.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidClient ID.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/clients/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "name",
    "archived",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
PATCH
/api/v1/clients/{id}

Rename or archive a client

Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidClient ID.

Request body

{
  "archived": true
}
Body schema
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 200
    },
    "archived": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

Example request

curl -X PATCH 'https://demo.trackingtabs.com/api/v1/clients/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"archived":true}'

Responses

200
Updated
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
409
Conflict (`conflict`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "updated_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "name",
    "archived",
    "created_at",
    "updated_at"
  ],
  "additionalProperties": false
}
DELETE
/api/v1/clients/{id}

Delete a client

Deletes a client. Its projects are kept and lose their client. Prefer archiving. Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidClient ID.

Example request

curl -X DELETE 'https://demo.trackingtabs.com/api/v1/clients/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

204
Deleted
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)

Tags

GET
/api/v1/tags

List tags

Returns all tags in the workspace, ordered by name.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
limitqueryinteger (1–1000) = 500Page size (default 500, max 1000).
offsetqueryinteger (0–) = 0Number of records to skip (default 0).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/tags' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          }
        },
        "required": [
          "id",
          "workspace_id",
          "name",
          "created_at"
        ],
        "additionalProperties": false
      }
    },
    "total": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    }
  },
  "required": [
    "data",
    "total",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
POST
/api/v1/tags

Create a tag

Creates a tag. If a tag with that name exists, it is returned with status 200.

Plan: Solo, Team or Business

Request body

{
  "name": "client-work"
}
Body schema
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64
    }
  },
  "required": [
    "name"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/tags' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"name":"client-work"}'

Responses

201
Created
200
Already existed
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "name",
    "created_at"
  ],
  "additionalProperties": false
}
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "name",
    "created_at"
  ],
  "additionalProperties": false
}
PATCH
/api/v1/tags/{id}

Rename a tag

Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidTag ID.

Request body

{
  "name": "client"
}
Body schema
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64
    }
  },
  "required": [
    "name"
  ],
  "additionalProperties": false
}

Example request

curl -X PATCH 'https://demo.trackingtabs.com/api/v1/tags/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"name":"client"}'

Responses

200
Updated
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
409
Conflict (`conflict`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "name",
    "created_at"
  ],
  "additionalProperties": false
}
DELETE
/api/v1/tags/{id}

Delete a tag

Removes the tag from all entries. Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidTag ID.

Example request

curl -X DELETE 'https://demo.trackingtabs.com/api/v1/tags/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

204
Deleted
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)

Tasks

GET
/api/v1/tasks

List tasks

Returns tasks in the workspace, optionally for one project, ordered by name.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
project_idqueryuuidOnly this project's tasks.
archivedquery"true" | "false"`true` to include archived tasks (default `false`).
limitqueryinteger (1–1000) = 500Page size (default 500, max 1000).
offsetqueryinteger (0–) = 0Number of records to skip (default 0).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/tasks' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "project_id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "archived": {
            "type": "boolean"
          },
          "estimated_seconds": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          }
        },
        "required": [
          "id",
          "workspace_id",
          "project_id",
          "name",
          "archived",
          "estimated_seconds",
          "created_at"
        ],
        "additionalProperties": false
      }
    },
    "total": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    }
  },
  "required": [
    "data",
    "total",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
POST
/api/v1/tasks

Create a task

Creates a task under a project.

Plan: Solo, Team or Business

Request body

{
  "project_id": "5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456",
  "name": "Design review",
  "estimated_seconds": 7200
}
Body schema
{
  "type": "object",
  "properties": {
    "project_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 255
    },
    "estimated_seconds": {
      "type": "integer",
      "minimum": 0,
      "nullable": true
    }
  },
  "required": [
    "project_id",
    "name"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/tasks' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"project_id":"5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456","name":"Design review","estimated_seconds":7200}'

Responses

201
Created
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "estimated_seconds": {
      "type": "integer",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "project_id",
    "name",
    "archived",
    "estimated_seconds",
    "created_at"
  ],
  "additionalProperties": false
}
GET
/api/v1/tasks/{id}

Get a task

Returns one task.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidTask ID.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/tasks/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "estimated_seconds": {
      "type": "integer",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "project_id",
    "name",
    "archived",
    "estimated_seconds",
    "created_at"
  ],
  "additionalProperties": false
}
PATCH
/api/v1/tasks/{id}

Update or archive a task

Patches a task's name, estimate or archived flag.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidTask ID.

Request body

{
  "archived": true
}
Body schema
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "minLength": 1,
      "maxLength": 255
    },
    "estimated_seconds": {
      "type": "integer",
      "minimum": 0,
      "nullable": true
    },
    "archived": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

Example request

curl -X PATCH 'https://demo.trackingtabs.com/api/v1/tasks/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"archived":true}'

Responses

200
Updated
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "project_id": {
      "type": "string",
      "format": "uuid"
    },
    "name": {
      "type": "string"
    },
    "archived": {
      "type": "boolean"
    },
    "estimated_seconds": {
      "type": "integer",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "project_id",
    "name",
    "archived",
    "estimated_seconds",
    "created_at"
  ],
  "additionalProperties": false
}
DELETE
/api/v1/tasks/{id}

Delete a task

Deletes a task; entries keep their project and lose the task. Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidTask ID.

Example request

curl -X DELETE 'https://demo.trackingtabs.com/api/v1/tasks/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

204
Deleted
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)

Members

GET
/api/v1/members

List workspace members

Returns everyone in the workspace with their role. `hourly_rate` is only filled in for admins and owners.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
limitqueryinteger (1–1000) = 500Page size (default 500, max 1000).
offsetqueryinteger (0–) = 0Number of records to skip (default 0).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/members' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string"
          },
          "full_name": {
            "type": "string",
            "nullable": true
          },
          "role": {
            "type": "string",
            "enum": [
              "owner",
              "admin",
              "member"
            ]
          },
          "joined_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          },
          "hourly_rate": {
            "type": "number",
            "nullable": true,
            "description": "Member billing rate; only returned to admins and owners (null otherwise)"
          }
        },
        "required": [
          "user_id",
          "email",
          "full_name",
          "role",
          "joined_at",
          "hourly_rate"
        ],
        "additionalProperties": false
      }
    },
    "total": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    }
  },
  "required": [
    "data",
    "total",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}

Reports

GET
/api/v1/reports/summary

Summary report

Totals for completed entries in the range, grouped by one dimension. Amounts use each entry's rate snapshot; `currency` is the workspace default. Members get their own time only.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
from*querydate or date-timeStart of the range. A date is the start of that day in `timezone`; a timestamp is used as-is (inclusive).
to*querydate or date-timeEnd of the range. A date includes that whole day in `timezone`; a timestamp is exclusive.
timezonequerystringIANA timezone for date bounds and per-day grouping. Defaults to the token owner's profile timezone.
project_idqueryuuid[]Only these projects. Repeat the parameter for several.
client_idqueryuuid[]Only projects of these clients. Repeatable.
tag_idqueryuuid[]Only entries with at least one of these tags. Repeatable.
user_idqueryuuid[]Only these users (admins/owners; members always see only their own time). Repeatable.
billablequery"true" | "false"`true` for billable entries only, `false` for non-billable only.
descriptionquerystringCase-insensitive substring match on the entry description.
group_byquery"project" | "client" | "user" | "task" | "tag" | "description" = "project"Grouping dimension (default `project`). With `tag`, an entry counts once per tag.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/reports/summary?from=2026-05-01&to=2026-05-31' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "from": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "to": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "timezone": {
      "type": "string"
    },
    "group_by": {
      "type": "string",
      "enum": [
        "project",
        "client",
        "user",
        "task",
        "tag",
        "description"
      ]
    },
    "total_seconds": {
      "type": "integer"
    },
    "billable_seconds": {
      "type": "integer"
    },
    "billable_amount": {
      "type": "number"
    },
    "currency": {
      "type": "string"
    },
    "groups": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "Group id, or `none` for entries without one"
          },
          "label": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "seconds": {
            "type": "integer"
          },
          "billable_seconds": {
            "type": "integer"
          },
          "amount": {
            "type": "number"
          }
        },
        "required": [
          "key",
          "label",
          "seconds",
          "billable_seconds",
          "amount"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "from",
    "to",
    "timezone",
    "group_by",
    "total_seconds",
    "billable_seconds",
    "billable_amount",
    "currency",
    "groups"
  ],
  "additionalProperties": false
}
GET
/api/v1/reports/detailed

Detailed report

Completed entries in the range with project, client, task, user and amount resolved, newest first. Paginated; `total_seconds` covers the whole range, not just the page.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
from*querydate or date-timeStart of the range. A date is the start of that day in `timezone`; a timestamp is used as-is (inclusive).
to*querydate or date-timeEnd of the range. A date includes that whole day in `timezone`; a timestamp is exclusive.
timezonequerystringIANA timezone for date bounds and per-day grouping. Defaults to the token owner's profile timezone.
project_idqueryuuid[]Only these projects. Repeat the parameter for several.
client_idqueryuuid[]Only projects of these clients. Repeatable.
tag_idqueryuuid[]Only entries with at least one of these tags. Repeatable.
user_idqueryuuid[]Only these users (admins/owners; members always see only their own time). Repeatable.
billablequery"true" | "false"`true` for billable entries only, `false` for non-billable only.
descriptionquerystringCase-insensitive substring match on the entry description.
limitqueryinteger (1–500) = 100Page size (default 100, max 500).
offsetqueryinteger (0–) = 0Number of records to skip (default 0).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/reports/detailed?from=2026-05-01&to=2026-05-31' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Local date of `start` in the report timezone"
          },
          "user_id": {
            "type": "string",
            "format": "uuid"
          },
          "user_name": {
            "type": "string"
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "project_name": {
            "type": "string",
            "nullable": true
          },
          "client_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "client_name": {
            "type": "string",
            "nullable": true
          },
          "task_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "task_name": {
            "type": "string",
            "nullable": true
          },
          "description": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          },
          "stop": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          },
          "duration_seconds": {
            "type": "integer"
          },
          "billable": {
            "type": "boolean"
          },
          "hourly_rate": {
            "type": "number",
            "nullable": true
          },
          "amount": {
            "type": "number",
            "nullable": true
          },
          "currency": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "date",
          "user_id",
          "user_name",
          "project_id",
          "project_name",
          "client_id",
          "client_name",
          "task_id",
          "task_name",
          "description",
          "tags",
          "start",
          "stop",
          "duration_seconds",
          "billable",
          "hourly_rate",
          "amount",
          "currency"
        ],
        "additionalProperties": false
      }
    },
    "total": {
      "type": "integer"
    },
    "total_seconds": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    }
  },
  "required": [
    "data",
    "total",
    "total_seconds",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
GET
/api/v1/reports/weekly

Weekly (timesheet) report

Seconds per day for each project or user, with daily and grand totals. Days are local dates in `timezone`. The range may span up to 92 days.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
from*querydate or date-timeStart of the range. A date is the start of that day in `timezone`; a timestamp is used as-is (inclusive).
to*querydate or date-timeEnd of the range. A date includes that whole day in `timezone`; a timestamp is exclusive.
timezonequerystringIANA timezone for date bounds and per-day grouping. Defaults to the token owner's profile timezone.
project_idqueryuuid[]Only these projects. Repeat the parameter for several.
client_idqueryuuid[]Only projects of these clients. Repeatable.
tag_idqueryuuid[]Only entries with at least one of these tags. Repeatable.
user_idqueryuuid[]Only these users (admins/owners; members always see only their own time). Repeatable.
billablequery"true" | "false"`true` for billable entries only, `false` for non-billable only.
descriptionquerystringCase-insensitive substring match on the entry description.
group_byquery"project" | "user" = "project"Row dimension (default `project`).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/reports/weekly?from=2026-05-01&to=2026-05-31' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "timezone": {
      "type": "string"
    },
    "group_by": {
      "type": "string",
      "enum": [
        "project",
        "user"
      ]
    },
    "days": {
      "type": "array",
      "items": {
        "type": "string",
        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
        "description": "Calendar date, YYYY-MM-DD"
      }
    },
    "rows": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "color": {
            "type": "string"
          },
          "by_day": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Seconds per day, keyed by YYYY-MM-DD"
          },
          "total_seconds": {
            "type": "integer"
          }
        },
        "required": [
          "key",
          "label",
          "by_day",
          "total_seconds"
        ],
        "additionalProperties": false
      }
    },
    "totals": {
      "type": "object",
      "additionalProperties": {
        "type": "integer"
      }
    },
    "total_seconds": {
      "type": "integer"
    }
  },
  "required": [
    "timezone",
    "group_by",
    "days",
    "rows",
    "totals",
    "total_seconds"
  ],
  "additionalProperties": false
}

Invoices

GET
/api/v1/invoices

List invoices

Returns invoices in the workspace, newest issue date first.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
statusquery"draft" | "sent" | "paid" | "void"Only invoices in this status.
client_idqueryuuidOnly this client's invoices.
fromquerydateIssue date on or after.
toquerydateIssue date on or before.
limitqueryinteger (1–200) = 50Page size (default 50, max 200).
offsetqueryinteger (0–) = 0Number of records to skip (default 0).

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/invoices' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "workspace_id": {
            "type": "string",
            "format": "uuid"
          },
          "client_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "client": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "name": {
                "type": "string"
              }
            },
            "required": [
              "id",
              "name"
            ],
            "additionalProperties": false,
            "nullable": true
          },
          "number": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "sent",
              "paid",
              "void"
            ]
          },
          "currency": {
            "type": "string"
          },
          "issue_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Calendar date, YYYY-MM-DD"
          },
          "due_date": {
            "type": "string",
            "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
            "description": "Calendar date, YYYY-MM-DD",
            "nullable": true
          },
          "notes": {
            "type": "string",
            "nullable": true
          },
          "recipient_email": {
            "type": "string",
            "nullable": true
          },
          "line_items": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string"
                },
                "description": {
                  "type": "string",
                  "minLength": 1
                },
                "quantity": {
                  "type": "number",
                  "minimum": 0
                },
                "unit": {
                  "type": "string",
                  "default": "hr"
                },
                "rate_cents": {
                  "type": "integer",
                  "minimum": 0
                },
                "amount_cents": {
                  "type": "integer",
                  "minimum": 0
                },
                "project_id": {
                  "type": "string",
                  "format": "uuid",
                  "nullable": true
                },
                "time_entry_ids": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "format": "uuid"
                  }
                }
              },
              "required": [
                "description",
                "quantity",
                "rate_cents"
              ],
              "additionalProperties": false
            }
          },
          "tax_rate_pct": {
            "type": "number"
          },
          "discount_pct": {
            "type": "number"
          },
          "markup_pct": {
            "type": "number"
          },
          "subtotal_cents": {
            "type": "integer"
          },
          "tax_cents": {
            "type": "integer"
          },
          "discount_cents": {
            "type": "integer"
          },
          "total_cents": {
            "type": "integer"
          },
          "sent_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
            "nullable": true
          },
          "paid_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
            "nullable": true
          },
          "paid_via": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          }
        },
        "required": [
          "id",
          "workspace_id",
          "client_id",
          "number",
          "status",
          "currency",
          "issue_date",
          "due_date",
          "notes",
          "recipient_email",
          "line_items",
          "tax_rate_pct",
          "discount_pct",
          "markup_pct",
          "subtotal_cents",
          "tax_cents",
          "discount_cents",
          "total_cents",
          "sent_at",
          "paid_at",
          "paid_via",
          "created_at"
        ],
        "additionalProperties": true
      }
    },
    "total": {
      "type": "integer"
    },
    "limit": {
      "type": "integer"
    },
    "offset": {
      "type": "integer"
    }
  },
  "required": [
    "data",
    "total",
    "limit",
    "offset"
  ],
  "additionalProperties": false
}
POST
/api/v1/invoices

Create an invoice

Creates an invoice (draft by default). Line-item amounts are in cents; totals, tax and the invoice number are computed from the workspace invoice settings. Use `POST /api/v1/invoices/from-entries` to build line items from billable time.

Plan: Solo, Team or Business

Request body

{
  "client_id": "5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456",
  "line_items": [
    {
      "description": "Design work",
      "quantity": 12.5,
      "unit": "hr",
      "rate_cents": 17500
    }
  ],
  "due_date": "2026-06-30"
}
Body schema
{
  "type": "object",
  "properties": {
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "number": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64
    },
    "currency": {
      "type": "string",
      "minLength": 3,
      "maxLength": 3
    },
    "issue_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "due_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "nullable": true
    },
    "notes": {
      "type": "string",
      "nullable": true
    },
    "recipient_email": {
      "type": "string",
      "format": "email",
      "nullable": true
    },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "minLength": 1
          },
          "quantity": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "default": "hr"
          },
          "rate_cents": {
            "type": "integer",
            "minimum": 0
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "time_entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "description",
          "quantity",
          "rate_cents"
        ],
        "additionalProperties": false
      },
      "default": []
    },
    "tax_rate_pct": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
    },
    "discount_pct": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
    },
    "markup_pct": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
    }
  },
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/invoices' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"client_id":"5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456","line_items":[{"description":"Design work","quantity":12.5,"unit":"hr","rate_cents":17500}],"due_date":"2026-06-30"}'

Responses

201
Created
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "client": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "nullable": true
    },
    "number": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "sent",
        "paid",
        "void"
      ]
    },
    "currency": {
      "type": "string"
    },
    "issue_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD"
    },
    "due_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD",
      "nullable": true
    },
    "notes": {
      "type": "string",
      "nullable": true
    },
    "recipient_email": {
      "type": "string",
      "nullable": true
    },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "minLength": 1
          },
          "quantity": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "default": "hr"
          },
          "rate_cents": {
            "type": "integer",
            "minimum": 0
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "time_entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "description",
          "quantity",
          "rate_cents"
        ],
        "additionalProperties": false
      }
    },
    "tax_rate_pct": {
      "type": "number"
    },
    "discount_pct": {
      "type": "number"
    },
    "markup_pct": {
      "type": "number"
    },
    "subtotal_cents": {
      "type": "integer"
    },
    "tax_cents": {
      "type": "integer"
    },
    "discount_cents": {
      "type": "integer"
    },
    "total_cents": {
      "type": "integer"
    },
    "sent_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_via": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "client_id",
    "number",
    "status",
    "currency",
    "issue_date",
    "due_date",
    "notes",
    "recipient_email",
    "line_items",
    "tax_rate_pct",
    "discount_pct",
    "markup_pct",
    "subtotal_cents",
    "tax_cents",
    "discount_cents",
    "total_cents",
    "sent_at",
    "paid_at",
    "paid_via",
    "created_at"
  ],
  "additionalProperties": true
}
POST
/api/v1/invoices/from-entries

Build invoice line items from time

Groups billable, completed entries in the range (optionally for one client) into line items by project and rate. Nothing is saved; pass the returned `line_items` to `POST /api/v1/invoices`.

Plan: Solo, Team or Business

Request body

{
  "client_id": "5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456",
  "from": "2026-05-01",
  "to": "2026-05-31"
}
Body schema
{
  "type": "object",
  "properties": {
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "from": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "description": "Calendar date, YYYY-MM-DD"
        },
        {
          "type": "string",
          "format": "date-time",
          "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
        }
      ],
      "description": "A date (YYYY-MM-DD, interpreted in the report timezone) or an ISO 8601 timestamp"
    },
    "to": {
      "anyOf": [
        {
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
          "description": "Calendar date, YYYY-MM-DD"
        },
        {
          "type": "string",
          "format": "date-time",
          "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
        }
      ],
      "description": "Inclusive; a date covers the whole day"
    }
  },
  "required": [
    "from",
    "to"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/invoices/from-entries' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"client_id":"5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456","from":"2026-05-01","to":"2026-05-31"}'

Responses

200
Preview
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "minLength": 1
          },
          "quantity": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "default": "hr"
          },
          "rate_cents": {
            "type": "integer",
            "minimum": 0
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "time_entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "description",
          "quantity",
          "rate_cents"
        ],
        "additionalProperties": false
      }
    },
    "entry_count": {
      "type": "integer"
    },
    "total_hours": {
      "type": "number"
    }
  },
  "required": [
    "line_items",
    "entry_count",
    "total_hours"
  ],
  "additionalProperties": false
}
GET
/api/v1/invoices/{id}

Get an invoice

Returns one invoice with its client.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidInvoice ID.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/invoices/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "client": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "nullable": true
    },
    "number": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "sent",
        "paid",
        "void"
      ]
    },
    "currency": {
      "type": "string"
    },
    "issue_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD"
    },
    "due_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD",
      "nullable": true
    },
    "notes": {
      "type": "string",
      "nullable": true
    },
    "recipient_email": {
      "type": "string",
      "nullable": true
    },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "minLength": 1
          },
          "quantity": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "default": "hr"
          },
          "rate_cents": {
            "type": "integer",
            "minimum": 0
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "time_entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "description",
          "quantity",
          "rate_cents"
        ],
        "additionalProperties": false
      }
    },
    "tax_rate_pct": {
      "type": "number"
    },
    "discount_pct": {
      "type": "number"
    },
    "markup_pct": {
      "type": "number"
    },
    "subtotal_cents": {
      "type": "integer"
    },
    "tax_cents": {
      "type": "integer"
    },
    "discount_cents": {
      "type": "integer"
    },
    "total_cents": {
      "type": "integer"
    },
    "sent_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_via": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "client_id",
    "number",
    "status",
    "currency",
    "issue_date",
    "due_date",
    "notes",
    "recipient_email",
    "line_items",
    "tax_rate_pct",
    "discount_pct",
    "markup_pct",
    "subtotal_cents",
    "tax_cents",
    "discount_cents",
    "total_cents",
    "sent_at",
    "paid_at",
    "paid_via",
    "created_at"
  ],
  "additionalProperties": true
}
PATCH
/api/v1/invoices/{id}

Update an invoice

Patches an invoice; totals are recomputed.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidInvoice ID.

Request body

{
  "notes": "Net 15"
}
Body schema
{
  "type": "object",
  "properties": {
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "number": {
      "type": "string",
      "minLength": 1,
      "maxLength": 64
    },
    "currency": {
      "type": "string",
      "minLength": 3,
      "maxLength": 3
    },
    "issue_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
    },
    "due_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "nullable": true
    },
    "notes": {
      "type": "string",
      "nullable": true
    },
    "recipient_email": {
      "type": "string",
      "format": "email",
      "nullable": true
    },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "minLength": 1
          },
          "quantity": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "default": "hr"
          },
          "rate_cents": {
            "type": "integer",
            "minimum": 0
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "time_entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "description",
          "quantity",
          "rate_cents"
        ],
        "additionalProperties": false
      },
      "default": []
    },
    "tax_rate_pct": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
    },
    "discount_pct": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
    },
    "markup_pct": {
      "type": "number",
      "minimum": 0,
      "maximum": 100
    }
  },
  "additionalProperties": false
}

Example request

curl -X PATCH 'https://demo.trackingtabs.com/api/v1/invoices/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"notes":"Net 15"}'

Responses

200
Updated
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "client": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "nullable": true
    },
    "number": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "sent",
        "paid",
        "void"
      ]
    },
    "currency": {
      "type": "string"
    },
    "issue_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD"
    },
    "due_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD",
      "nullable": true
    },
    "notes": {
      "type": "string",
      "nullable": true
    },
    "recipient_email": {
      "type": "string",
      "nullable": true
    },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "minLength": 1
          },
          "quantity": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "default": "hr"
          },
          "rate_cents": {
            "type": "integer",
            "minimum": 0
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "time_entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "description",
          "quantity",
          "rate_cents"
        ],
        "additionalProperties": false
      }
    },
    "tax_rate_pct": {
      "type": "number"
    },
    "discount_pct": {
      "type": "number"
    },
    "markup_pct": {
      "type": "number"
    },
    "subtotal_cents": {
      "type": "integer"
    },
    "tax_cents": {
      "type": "integer"
    },
    "discount_cents": {
      "type": "integer"
    },
    "total_cents": {
      "type": "integer"
    },
    "sent_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_via": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "client_id",
    "number",
    "status",
    "currency",
    "issue_date",
    "due_date",
    "notes",
    "recipient_email",
    "line_items",
    "tax_rate_pct",
    "discount_pct",
    "markup_pct",
    "subtotal_cents",
    "tax_cents",
    "discount_cents",
    "total_cents",
    "sent_at",
    "paid_at",
    "paid_via",
    "created_at"
  ],
  "additionalProperties": true
}
DELETE
/api/v1/invoices/{id}

Delete an invoice

Requires the admin or owner role.

Plan: Solo, Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidInvoice ID.

Example request

curl -X DELETE 'https://demo.trackingtabs.com/api/v1/invoices/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

204
Deleted
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
GET
/api/v1/invoices/{id}/pdf

Download an invoice PDF

Renders the invoice as a PDF.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidInvoice ID.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/invoices/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456/pdf' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
PDF fileapplication/pdf
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
POST
/api/v1/invoices/{id}/send

Email an invoice

Emails the invoice PDF to `to` (or the invoice's recipient) and marks it sent. This sends real email.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidInvoice ID.

Request body (optional)

{
  "to": "billing@client.com"
}
Body schema
{
  "type": "object",
  "properties": {
    "to": {
      "type": "string",
      "format": "email",
      "description": "Defaults to the invoice's recipient_email"
    }
  },
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/invoices/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456/send' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"to":"billing@client.com"}'

Responses

200
Sent
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
502
The email provider rejected the message
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "client": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "nullable": true
    },
    "number": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "sent",
        "paid",
        "void"
      ]
    },
    "currency": {
      "type": "string"
    },
    "issue_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD"
    },
    "due_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD",
      "nullable": true
    },
    "notes": {
      "type": "string",
      "nullable": true
    },
    "recipient_email": {
      "type": "string",
      "nullable": true
    },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "minLength": 1
          },
          "quantity": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "default": "hr"
          },
          "rate_cents": {
            "type": "integer",
            "minimum": 0
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "time_entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "description",
          "quantity",
          "rate_cents"
        ],
        "additionalProperties": false
      }
    },
    "tax_rate_pct": {
      "type": "number"
    },
    "discount_pct": {
      "type": "number"
    },
    "markup_pct": {
      "type": "number"
    },
    "subtotal_cents": {
      "type": "integer"
    },
    "tax_cents": {
      "type": "integer"
    },
    "discount_cents": {
      "type": "integer"
    },
    "total_cents": {
      "type": "integer"
    },
    "sent_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_via": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "client_id",
    "number",
    "status",
    "currency",
    "issue_date",
    "due_date",
    "notes",
    "recipient_email",
    "line_items",
    "tax_rate_pct",
    "discount_pct",
    "markup_pct",
    "subtotal_cents",
    "tax_cents",
    "discount_cents",
    "total_cents",
    "sent_at",
    "paid_at",
    "paid_via",
    "created_at"
  ],
  "additionalProperties": true
}
POST
/api/v1/invoices/{id}/mark-paid

Mark an invoice paid

Sets status `paid` and records `paid_at` (and `paid_via` when given).

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
id*pathuuidInvoice ID.

Request body (optional)

{
  "paid_via": "ACH"
}
Body schema
{
  "type": "object",
  "properties": {
    "paid_via": {
      "type": "string",
      "maxLength": 120
    }
  },
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/invoices/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456/mark-paid' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"paid_via":"ACH"}'

Responses

200
Updated
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "workspace_id": {
      "type": "string",
      "format": "uuid"
    },
    "client_id": {
      "type": "string",
      "format": "uuid",
      "nullable": true
    },
    "client": {
      "type": "object",
      "properties": {
        "id": {
          "type": "string",
          "format": "uuid"
        },
        "name": {
          "type": "string"
        }
      },
      "required": [
        "id",
        "name"
      ],
      "additionalProperties": false,
      "nullable": true
    },
    "number": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "draft",
        "sent",
        "paid",
        "void"
      ]
    },
    "currency": {
      "type": "string"
    },
    "issue_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD"
    },
    "due_date": {
      "type": "string",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Calendar date, YYYY-MM-DD",
      "nullable": true
    },
    "notes": {
      "type": "string",
      "nullable": true
    },
    "recipient_email": {
      "type": "string",
      "nullable": true
    },
    "line_items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "minLength": 1
          },
          "quantity": {
            "type": "number",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "default": "hr"
          },
          "rate_cents": {
            "type": "integer",
            "minimum": 0
          },
          "amount_cents": {
            "type": "integer",
            "minimum": 0
          },
          "project_id": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "time_entry_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        },
        "required": [
          "description",
          "quantity",
          "rate_cents"
        ],
        "additionalProperties": false
      }
    },
    "tax_rate_pct": {
      "type": "number"
    },
    "discount_pct": {
      "type": "number"
    },
    "markup_pct": {
      "type": "number"
    },
    "subtotal_cents": {
      "type": "integer"
    },
    "tax_cents": {
      "type": "integer"
    },
    "discount_cents": {
      "type": "integer"
    },
    "total_cents": {
      "type": "integer"
    },
    "sent_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "paid_via": {
      "type": "string",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "workspace_id",
    "client_id",
    "number",
    "status",
    "currency",
    "issue_date",
    "due_date",
    "notes",
    "recipient_email",
    "line_items",
    "tax_rate_pct",
    "discount_pct",
    "markup_pct",
    "subtotal_cents",
    "tax_cents",
    "discount_cents",
    "total_cents",
    "sent_at",
    "paid_at",
    "paid_via",
    "created_at"
  ],
  "additionalProperties": true
}

Webhooks

GET
/api/v1/webhooks

List webhooks

Requires the admin or owner role and a Team or Business plan.

Plan: Team or Business · Role: admin or owner

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/webhooks' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "time_entry.created",
                "time_entry.updated",
                "time_entry.deleted",
                "project.created",
                "project.updated",
                "project.deleted",
                "client.created",
                "client.updated",
                "client.deleted"
              ]
            }
          },
          "active": {
            "type": "boolean"
          },
          "last_delivery_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
            "nullable": true
          },
          "last_delivery_status": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
          }
        },
        "required": [
          "id",
          "url",
          "events",
          "active",
          "last_delivery_at",
          "last_delivery_status",
          "created_at"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "data"
  ],
  "additionalProperties": false
}
POST
/api/v1/webhooks

Create a webhook

Subscribes a URL to events. The response includes the signing `secret` once; store it to verify the `X-Signature` header (`sha256=` + HMAC-SHA256 of the raw body).

Plan: Team or Business · Role: admin or owner

Request body

{
  "url": "https://example.com/hooks/tracking",
  "events": [
    "time_entry.created",
    "time_entry.updated"
  ]
}
Body schema
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "format": "uri",
      "maxLength": 2000
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "time_entry.created",
          "time_entry.updated",
          "time_entry.deleted",
          "project.created",
          "project.updated",
          "project.deleted",
          "client.created",
          "client.updated",
          "client.deleted"
        ]
      },
      "minItems": 1
    },
    "active": {
      "type": "boolean",
      "default": true
    }
  },
  "required": [
    "url",
    "events"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/webhooks' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com/hooks/tracking","events":["time_entry.created","time_entry.updated"]}'

Responses

201
Created
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "url": {
      "type": "string",
      "format": "uri"
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "time_entry.created",
          "time_entry.updated",
          "time_entry.deleted",
          "project.created",
          "project.updated",
          "project.deleted",
          "client.created",
          "client.updated",
          "client.deleted"
        ]
      }
    },
    "active": {
      "type": "boolean"
    },
    "last_delivery_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "last_delivery_status": {
      "type": "integer",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    },
    "secret": {
      "type": "string",
      "description": "HMAC signing secret. Returned only when the webhook is created."
    }
  },
  "required": [
    "id",
    "url",
    "events",
    "active",
    "last_delivery_at",
    "last_delivery_status",
    "created_at",
    "secret"
  ],
  "additionalProperties": false
}
GET
/api/v1/webhooks/{id}

Get a webhook

Requires the admin or owner role and a Team or Business plan.

Plan: Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidWebhook ID.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/webhooks/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
Success
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "url": {
      "type": "string",
      "format": "uri"
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "time_entry.created",
          "time_entry.updated",
          "time_entry.deleted",
          "project.created",
          "project.updated",
          "project.deleted",
          "client.created",
          "client.updated",
          "client.deleted"
        ]
      }
    },
    "active": {
      "type": "boolean"
    },
    "last_delivery_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "last_delivery_status": {
      "type": "integer",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "url",
    "events",
    "active",
    "last_delivery_at",
    "last_delivery_status",
    "created_at"
  ],
  "additionalProperties": false
}
PATCH
/api/v1/webhooks/{id}

Update a webhook

Change the URL, events or active flag.

Plan: Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidWebhook ID.

Request body

{
  "active": false
}
Body schema
{
  "type": "object",
  "properties": {
    "url": {
      "type": "string",
      "format": "uri",
      "maxLength": 2000
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "time_entry.created",
          "time_entry.updated",
          "time_entry.deleted",
          "project.created",
          "project.updated",
          "project.deleted",
          "client.created",
          "client.updated",
          "client.deleted"
        ]
      },
      "minItems": 1
    },
    "active": {
      "type": "boolean"
    }
  },
  "additionalProperties": false
}

Example request

curl -X PATCH 'https://demo.trackingtabs.com/api/v1/webhooks/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"active":false}'

Responses

200
Updated
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "url": {
      "type": "string",
      "format": "uri"
    },
    "events": {
      "type": "array",
      "items": {
        "type": "string",
        "enum": [
          "time_entry.created",
          "time_entry.updated",
          "time_entry.deleted",
          "project.created",
          "project.updated",
          "project.deleted",
          "client.created",
          "client.updated",
          "client.deleted"
        ]
      }
    },
    "active": {
      "type": "boolean"
    },
    "last_delivery_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z",
      "nullable": true
    },
    "last_delivery_status": {
      "type": "integer",
      "nullable": true
    },
    "created_at": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 timestamp with timezone, e.g. 2026-05-24T18:00:00Z"
    }
  },
  "required": [
    "id",
    "url",
    "events",
    "active",
    "last_delivery_at",
    "last_delivery_status",
    "created_at"
  ],
  "additionalProperties": false
}
DELETE
/api/v1/webhooks/{id}

Delete a webhook

Stops all future deliveries.

Plan: Team or Business · Role: admin or owner

Parameters

NameInTypeDescription
id*pathuuidWebhook ID.

Example request

curl -X DELETE 'https://demo.trackingtabs.com/api/v1/webhooks/5b2e1c4d-6f8a-4b3e-9c7d-abcdef123456' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

204
Deleted
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
404
Not found (`not_found`)
429
Rate limit exceeded (`rate_limited`)

Import & export

GET
/api/v1/export/entries.csv

Export entries as CSV

CSV of time entries (members: their own; admins: everyone's), newest first. Cells that start with =, +, - or @ are prefixed with ' so spreadsheets don't run them as formulas.

Plan: Solo, Team or Business

Parameters

NameInTypeDescription
fromquerydate-timeInclusive lower bound on `start`.
toquerydate-timeInclusive upper bound on `start`.

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/export/entries.csv' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
CSV filetext/csv
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
GET
/api/v1/export/workspace.zip

Export the workspace as a ZIP

ZIP of JSON files: workspace, projects, clients, tasks, tags, entries and, for admins, members. Members get only their own entries and only projects (and tasks) they can see.

Plan: Solo, Team or Business

Example request

curl -X GET 'https://demo.trackingtabs.com/api/v1/export/workspace.zip' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN'

Responses

200
ZIP fileapplication/zip
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
POST
/api/v1/import/csv

Parse a CSV for import

Parses a Toggl, Harvest or generic CSV and returns the parsed entries plus the projects and clients that would be created. Nothing is saved; send `allEntries` to `POST /api/v1/import/commit`.

Plan: Solo, Team or Business

Request body

{
  "source": "toggl",
  "file": "<base64 CSV>"
}
Body schema
{
  "type": "object",
  "properties": {
    "source": {
      "type": "string",
      "enum": [
        "toggl",
        "harvest",
        "generic"
      ]
    },
    "file": {
      "type": "string",
      "minLength": 1,
      "description": "The CSV file, base64-encoded"
    },
    "mapping": {
      "type": "object",
      "additionalProperties": {
        "type": "string",
        "nullable": true
      },
      "description": "Column mapping; required for `generic` CSVs"
    }
  },
  "required": [
    "source",
    "file"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/import/csv' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"source":"toggl","file":"<base64 CSV>"}'

Responses

200
Parsed
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
200 response schema
{
  "type": "object",
  "properties": {
    "total": {
      "type": "integer"
    },
    "preview": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "clientName": {
            "type": "string"
          },
          "start": {
            "type": "string"
          },
          "stop": {
            "type": "string"
          },
          "durationSeconds": {
            "type": "number"
          },
          "billable": {
            "type": "boolean"
          },
          "tagNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "description",
          "projectName",
          "clientName",
          "start",
          "stop",
          "durationSeconds",
          "billable",
          "tagNames"
        ],
        "additionalProperties": false
      }
    },
    "allEntries": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "clientName": {
            "type": "string"
          },
          "start": {
            "type": "string"
          },
          "stop": {
            "type": "string"
          },
          "durationSeconds": {
            "type": "number"
          },
          "billable": {
            "type": "boolean"
          },
          "tagNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "description",
          "projectName",
          "clientName",
          "start",
          "stop",
          "durationSeconds",
          "billable",
          "tagNames"
        ],
        "additionalProperties": false
      }
    },
    "detectedColumns": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "newProjects": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "newClients": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": [
    "total",
    "preview",
    "allEntries",
    "detectedColumns",
    "newProjects",
    "newClients"
  ],
  "additionalProperties": false
}
POST
/api/v1/import/commit

Import parsed entries

Creates up to 500 entries for the token owner, creating missing clients, projects and tags. Creating clients or projects requires the admin or owner role; a member whose rows name a missing (or hidden) client or project gets 403. Re-importing the same rows is skipped (idempotent).

Plan: Solo, Team or Business

Request body

{
  "source": "toggl",
  "entries": [
    {
      "description": "Planning",
      "projectName": "Website",
      "clientName": "Acme",
      "start": "2026-05-01T14:00:00Z",
      "stop": "2026-05-01T15:00:00Z",
      "durationSeconds": 3600,
      "billable": true,
      "tagNames": [
        "planning"
      ]
    }
  ]
}
Body schema
{
  "type": "object",
  "properties": {
    "source": {
      "type": "string"
    },
    "entries": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "projectName": {
            "type": "string"
          },
          "clientName": {
            "type": "string"
          },
          "start": {
            "type": "string"
          },
          "stop": {
            "type": "string"
          },
          "durationSeconds": {
            "type": "number"
          },
          "billable": {
            "type": "boolean"
          },
          "tagNames": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "required": [
          "description",
          "projectName",
          "clientName",
          "start",
          "stop",
          "durationSeconds",
          "billable",
          "tagNames"
        ],
        "additionalProperties": false
      },
      "maxItems": 500
    }
  },
  "required": [
    "source",
    "entries"
  ],
  "additionalProperties": false
}

Example request

curl -X POST 'https://demo.trackingtabs.com/api/v1/import/commit' \
  -H 'Authorization: Bearer tta_YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{"source":"toggl","entries":[{"description":"Planning","projectName":"Website","clientName":"Acme","start":"2026-05-01T14:00:00Z","stop":"2026-05-01T15:00:00Z","durationSeconds":3600,"billable":true,"tagNames":["planning"]}]}'

Responses

201
Imported
400
Validation error (`validation_error`)
401
Missing, invalid, revoked or expired token (`unauthorized`)
403
Insufficient role (`forbidden`) or plan (`feature_gated`)
429
Rate limit exceeded (`rate_limited`)
201 response schema
{
  "type": "object",
  "properties": {
    "inserted": {
      "type": "integer"
    },
    "skipped": {
      "type": "integer"
    },
    "failed": {
      "type": "integer"
    }
  },
  "required": [
    "inserted",
    "skipped",
    "failed"
  ],
  "additionalProperties": false
}

Error codes

unauthorized401Missing, invalid, revoked or expired token, or the token owner left the workspace
forbidden403Your role cannot do this (admin or owner required)
feature_gated403Your plan does not include this (API access needs Solo, Team or Business)
not_found404No such record in this workspace (or not visible to you)
validation_error400Body or query failed validation; `details.issues` lists each problem
conflict409Duplicate name/number, overlapping entry, or a timer running elsewhere
rate_limited429Too many requests; wait for `Retry-After` seconds
internal_error500Unexpected server error

50 endpoints

API reference | Tracking Tabs