Hourtick Time API
Timers and time entries. Link time to a task with `workItemId` on start, create or edit.
Timers and time entries. Link time to a task with `workItemId` on start, create or edit.
Every API here is available over the APIs.io API and to AI agents over MCP.
One button, every client — Claude, Cursor, VS Code and the rest.
https://apis.io/mcp
find_apisBrowse and filter every API in the catalog.get_api_artifactsOne API's artifacts, grouped by type.get_openapiThe primary OpenAPI for this API.find_similar_apisAPIs that look like this one.apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.resolveTurn a domain, URL or GitHub org into the provider it belongs to.find_cohortsEvery scored population of providers in the catalog.curl "https://apis.io/api/v1/apis/hourtick:hourtick-time-api"
curl "https://apis.io/api/v1/apis?limit=25"
Calls without X-API-Key need no key. Calls with it need a paid plan — get a key.
Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.
A second provider on the same verified email joins the account you already have.
openapi: 3.2.0
info:
title: Hourtick Time API
version: 1.0.0
description: 'The Hourtick REST API. Authenticate with a personal API token: create one in **Settings → API tokens**, then send `Authorization: Bearer ht_…`.
All timer changes go through **POST /api/v1/commands**, the same endpoint the web and Mac apps use. Commands are idempotent per `commandId` (use a new UUID per action, reuse it to retry safely).
Every response carries a `Server-Time` header (epoch ms). Errors use RFC 9457 problem details.'
servers:
- url: https://hourtick.com
security:
- bearer: []
tags:
- name: Time
description: Timers and time entries. Link time to a task with `workItemId` on start, create or edit.
paths:
/api/v1/time-entries:
get:
tags:
- Time
summary: Your time entries in a date range, plus the running entry
parameters:
- name: from
in: query
required: true
schema:
type: string
format: date
- name: to
in: query
required: true
schema:
type: string
format: date
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
entries:
type: array
items:
$ref: '#/components/schemas/TimeEntry'
running:
oneOf:
- $ref: '#/components/schemas/TimeEntry'
- type: 'null'
'401':
description: Missing or invalid token
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
'422':
description: Invalid request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
operationId: getApiV1TimeEntries
x-operation-id-source: derived
/api/v1/commands:
post:
tags:
- Time
summary: Start, stop, continue, create, edit, delete or restore time
description: '`command.type`: `start` (stops any running timer), `restart` (continue an entry on its own date), `stop`, `create` (finished entry with a duration), `edit` (needs `expectedVersion`), `delete`, `restore`. Locked entries return 423; stale versions return 409 with the current entry.'
requestBody:
required: true
content:
application/json:
schema:
$schema: https://json-schema.org/draft/2020-12/schema
type: object
properties:
commandId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
command:
oneOf:
- type: object
properties:
type:
type: string
const: start
entryId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
spentDate:
type: string
projectId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
taskId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
workItemId:
anyOf:
- type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
- type: 'null'
notes:
type: string
maxLength: 5000
billable:
type: boolean
required:
- type
- entryId
- projectId
- taskId
- notes
- billable
- type: object
properties:
type:
type: string
const: restart
entryId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
required:
- type
- entryId
- type: object
properties:
type:
type: string
const: stop
entryId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
required:
- type
- entryId
- type: object
properties:
type:
type: string
const: edit
entryId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
expectedVersion:
type: integer
minimum: 1
maximum: 9007199254740991
patch:
type: object
properties:
projectId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
taskId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
workItemId:
anyOf:
- type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
- type: 'null'
notes:
type: string
maxLength: 5000
billable:
type: boolean
spentDate:
type: string
durationSeconds:
type: integer
minimum: 0
maximum: 86400
additionalProperties: false
required:
- type
- entryId
- expectedVersion
- patch
- type: object
properties:
type:
type: string
const: delete
entryId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
expectedVersion:
type: integer
minimum: 1
maximum: 9007199254740991
required:
- type
- entryId
- expectedVersion
- type: object
properties:
type:
type: string
const: restore
entryId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
required:
- type
- entryId
- type: object
properties:
type:
type: string
const: create
entryId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
spentDate:
type: string
durationSeconds:
type: integer
minimum: 0
maximum: 86400
projectId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
taskId:
type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
workItemId:
anyOf:
- type: string
format: uuid
pattern: ^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$
- type: 'null'
notes:
type: string
maxLength: 5000
billable:
type: boolean
required:
- type
- entryId
- spentDate
- durationSeconds
- projectId
- taskId
- notes
- billable
clientTime:
type: integer
exclusiveMinimum: 0
maximum: 9007199254740991
required:
- commandId
- command
example:
commandId: 0192f1a0-7c1e-7cc0-9b8e-2f5f0b0a1c11
command:
type: start
entryId: 0192f1a0-7c1e-7cc0-9b8e-2f5f0b0a1c12
projectId: <project uuid>
taskId: <task uuid>
notes: Homepage copy
billable: true
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
entries:
type: array
items:
$ref: '#/components/schemas/TimeEntry'
noop:
type: boolean
'401':
description: Missing or invalid token
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
'409':
description: Version conflict (response includes the current entry)
'422':
description: Invalid request
content:
application/problem+json:
schema:
$ref: '#/components/schemas/Problem'
'423':
description: Entry or day is locked (invoiced, submitted or approved)
operationId: postApiV1Commands
x-operation-id-source: derived
/api/v1/stream:
get:
tags:
- Time
summary: 'Server-Sent Events: live entry changes for your account and task changes in the…'
responses:
'200':
description: 'text/event-stream with `hello`, `entries`, `work_items` (`workItemId`), `chat` (`channelId`, `messageId`, `parentId`, `change`: message, edit, delete, reaction, read, channel, typing or agent) and `presence` events. Private channels and DMs only reach their members'
operationId: getApiV1Stream
x-operation-id-source: derived
/api/v1/activity:
get:
tags:
- Time
summary: 'Your day: tracked entries and what you did (tasks created, moved or commented…'
parameters:
- name: date
in: query
schema:
type: string
format: date
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
operationId: getApiV1Activity
x-operation-id-source: derived
components:
schemas:
Problem:
type: object
properties:
type:
type: string
title:
type: string
status:
type: integer
detail:
type: string
entry:
$ref: '#/components/schemas/TimeEntry'
conflicts:
type: array
description: 'Bulk edits: entries that blocked the change.'
items:
type: object
properties:
id:
type: string
reason:
type: string
TimeEntry:
type: object
properties:
id:
type: string
format: uuid
workspaceId:
type: string
format: uuid
userId:
type: string
projectId:
type: string
format: uuid
taskId:
type: string
format: uuid
description: Task type
workItemId:
type:
- string
- 'null'
format: uuid
description: The task this time is for, if any
notes:
type: string
spentDate:
type: string
format: date
description: The day the time belongs to (user's time zone).
timezone:
type: string
examples:
- Europe/Copenhagen
accumulatedSeconds:
type: integer
description: Seconds banked from completed runs.
timerStartedAt:
type:
- integer
- 'null'
description: Epoch ms when the current run started; null when stopped.
billable:
type: boolean
lockedReason:
type:
- string
- 'null'
enum:
- null
- invoiced
- approved
- submitted
version:
type: integer
deletedAt:
type:
- integer
- 'null'
createdAt:
type: integer
updatedAt:
type: integer
description: Live duration = accumulatedSeconds + (now − timerStartedAt)/1000 while running.
securitySchemes:
bearer:
type: http
scheme: bearer
bearerFormat: ht_… personal API token