Download OpenAPI specification:
REST API v1, trigger API and outgoing webhooks of Horaka.
Horaka is time tracking for freelancers who bill their clients. This API lets an ERP, an automation tool or a hardware button read and write the data of one workspace.
Every endpoint except the specification itself needs a token, sent as
Authorization: Bearer <token>. Tokens are created under Settings, API in the app
(/app/einstellungen/api). The plain token is shown exactly once; the server only keeps a hash.
A revoked or expired token answers 401.
track, can be limited to certain projects
and are counted against the device limit of the plan (Free 1, Standard 3, Premium unlimited).| Ability | Allows | Plan |
|---|---|---|
read |
all GET endpoints |
Standard and Premium |
write |
creating, changing and archiving clients, projects, activities, time entries | Premium |
track |
trigger endpoints /track/* and GET /timer |
Premium (device tokens: every plan within the device limit) |
billing |
billing runs (owners and admins) | Premium |
webhooks |
webhook endpoints (owners and admins) | Premium |
The delta sync (/time-entries/changes) is part of Premium. A missing ability answers 403
with code insufficient_scope, a feature outside the plan answers 403 with code plan_required
and the plan that includes it in meta.required_plan. A deactivated account answers
account_deactivated, a suspended workspace workspace_suspended, a workspace whose deletion
the owner has requested workspace_deletion_pending (until the request is revoked).
Per token and minute: read 600, write 120, track 60, billing 30 (webhooks counts as
write). Every answer carries X-RateLimit-Limit and X-RateLimit-Remaining; above the limit
the answer is 429 with Retry-After in seconds.
{"data": …, "meta": …, "links": …}; errors are
{"errors": [{"status", "code", "title", "detail", "source", "meta"}]}. Codes are stable and
English, title and detail follow Accept-Language (en or de, default English).2026-10-14T08:00:00Z). Times you send need an offset or Z.
Calendar days (local_date, from, to) are YYYY-MM-DD in the time zone of the account.ended_at: null and no values yet.Lists use keyset pagination: page[size] (default 50, at most 200) and page[cursor] with
meta.next_cursor of the previous page. meta.has_more says whether another page follows,
links.next is the ready-made address. Lists accept updated_since (ISO 8601) for incremental
reads.
GET /time-entries/changes?cursor=<n> returns every time entry changed after cursor in the
order of the change (sync_version), deletions included as tombstones with deleted_at set.
Store meta.next_cursor and ask again with it; while meta.has_more is true, ask again at once.
Without new changes the cursor stays the same and If-None-Match with the last ETag answers
304. Tombstones are kept for 90 days; a cursor older than the oldest kept tombstone answers
410 with the code cursor_expired: start again without a cursor and replace your copy.
POST /track/start, /track/stop, /track/toggle and /track/switch take
{"project": <id or alias>, "activity": <id or name>, "note": "…"} and always answer with
the current timers. If the account allows parallel timers (personal setting), several timers may
run at once: start adds a timer (the same project and activity never runs twice). Otherwise a
start ends the running timers at the same moment (meta.action is switched). stop without a
project stops all running timers, switch stops all other running timers at the same moment. Send an Idempotency-Key header (any unique text, valid for 24 hours) to make
a press count exactly once; the same key returns the stored answer, the same key with a different
body answers 422. Without the header, a second press of the same account within two seconds is
ignored (meta.debounced: true). Aliases are maintained under Settings, API.
Owners and admins register https endpoints (no private networks) under Settings, Webhooks or with
POST /webhooks. Each delivery is a POST with a JSON body (see the webhook section) and the
headers X-Horaka-Event, X-Horaka-Delivery-Id and
X-Horaka-Signature: t=<unix time>,v1=<hex> where v1 is
HMAC-SHA256 of <t>.<raw body> with the endpoint secret. Answer with any 2xx within 10 seconds.
Failed deliveries are retried after 1 min, 5 min, 30 min, 2 h and 12 h; after 20 failures in a row
the endpoint is switched off and the owner gets an email. Deliveries can be replayed in the app.
Verify the signature against the raw body and reject timestamps older than five minutes:
$header = $_SERVER['HTTP_X_HORAKA_SIGNATURE'] ?? '';
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $header), $parts);
$expected = hash_hmac('sha256', $parts['t'].'.'.$body, getenv('HORAKA_WEBHOOK_SECRET'));
$valid = isset($parts['t'], $parts['v1'])
&& abs(time() - (int) $parts['t']) <= 300
&& hash_equals($expected, $parts['v1']);
// Node.js (Express with express.raw({ type: 'application/json' }))
import crypto from 'node:crypto';
function verify(req, secret) {
const parts = Object.fromEntries(
String(req.get('X-Horaka-Signature') || '').split(',').map((p) => p.split('=')),
);
const expected = crypto.createHmac('sha256', secret).update(`${parts.t}.${req.body}`).digest('hex');
return Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300
&& parts.v1?.length === expected.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
import hashlib, hmac, time
def verify(header: str, body: bytes, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + body, hashlib.sha256).hexdigest()
return abs(time.time() - int(parts["t"])) <= 300 and hmac.compare_digest(expected, parts["v1"])
Start the timer on project MUE-WEB with curl:
curl -X POST https://api.horaka.com/v1/track/start \
-H "Authorization: Bearer $HORAKA_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"project": "MUE-WEB", "note": "Workshop"}'
…/v1/track/toggle,
headers Authorization: Bearer <device token> and Content-Type: application/json, request body
JSON with the key project. Run it from Siri, the home screen or an NFC automation. For a
spoken status ("hours today"), call GET …/api/v1/track/status?format=text and pass the result
to "Speak Text". The app has a gallery with ready requests under Settings, Shortcuts.Authorization: Bearer …); node "Webhook" to
receive events, then verify the signature in a "Code" node.The version is part of the path (/v1). Additive changes (new fields, new endpoints, new events)
come without a new version, so ignore fields you do not know. Endpoints that will be replaced get
the headers Deprecation and Sunset well in advance.
Team members only see clients with a project they may see, without rates and notes.
| updated_since | string <date-time> Only records changed at or after this time (ISO 8601 with offset). |
| page[size] | integer [ 1 .. 200 ] Default: 50 Entries per page, 1 to 200. |
| page[cursor] | string
|
| archived | string Enum: "true" "false" "1" "0" Only archived ( |
| search | string <= 100 characters Part of the name. |
{- "data": [
- {
- "id": 0,
- "name": "string",
- "color": "string",
- "contact": "string",
- "contact_email": "string",
- "contact_phone": "string",
- "customer_number": "string",
- "street": "string",
- "address_extra": "string",
- "postal_code": "string",
- "city": "string",
- "country": "string",
- "email": "string",
- "phone": "string",
- "website": "string",
- "vat_id": "string",
- "tax_number": "string",
- "notes": "string",
- "default_rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "meta": {
- "page_size": 0,
- "next_cursor": "string",
- "has_more": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}Owners and admins. Ability write.
| name | string <= 191 characters |
| color | string (ProjectColor) Enum: "teal" "amber" "indigo" "red" "blue" "purple" "green" "pink" "slate" "orange" "olive" "brown" Key of the colour palette. |
| contact | string or null <= 2000 characters |
| contact_email | string or null <email> <= 191 characters |
| contact_phone | string or null <= 64 characters |
| customer_number | string or null <= 64 characters |
| street | string or null <= 191 characters |
| address_extra | string or null <= 191 characters |
| postal_code | string or null <= 32 characters |
| city | string or null <= 191 characters |
| country | string or null = 2 characters |
string or null <email> <= 191 characters | |
| phone | string or null <= 64 characters |
| website | string or null <= 191 characters |
| vat_id | string or null <= 64 characters |
| tax_number | string or null <= 64 characters |
| notes | string or null <= 5000 characters |
| default_rate_cents | integer or null >= 0 |
| rate_valid_from | string or null <date> Day from which a new rate applies, default today. |
{- "name": "Müller GmbH",
- "color": "teal",
- "default_rate_cents": 9000
}{- "data": {
- "id": 0,
- "name": "string",
- "color": "string",
- "contact": "string",
- "contact_email": "string",
- "contact_phone": "string",
- "customer_number": "string",
- "street": "string",
- "address_extra": "string",
- "postal_code": "string",
- "city": "string",
- "country": "string",
- "email": "string",
- "phone": "string",
- "website": "string",
- "vat_id": "string",
- "tax_number": "string",
- "notes": "string",
- "default_rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}{- "data": {
- "id": 0,
- "name": "string",
- "color": "string",
- "contact": "string",
- "contact_email": "string",
- "contact_phone": "string",
- "customer_number": "string",
- "street": "string",
- "address_extra": "string",
- "postal_code": "string",
- "city": "string",
- "country": "string",
- "email": "string",
- "phone": "string",
- "website": "string",
- "vat_id": "string",
- "tax_number": "string",
- "notes": "string",
- "default_rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Only the fields you send change. A new default_rate_cents applies from rate_valid_from (default today); the previous rate stays in the history.
| client required | integer >= 1 |
| name | string <= 191 characters |
| color | string (ProjectColor) Enum: "teal" "amber" "indigo" "red" "blue" "purple" "green" "pink" "slate" "orange" "olive" "brown" Key of the colour palette. |
| contact | string or null <= 2000 characters |
| contact_email | string or null <email> <= 191 characters |
| contact_phone | string or null <= 64 characters |
| customer_number | string or null <= 64 characters |
| street | string or null <= 191 characters |
| address_extra | string or null <= 191 characters |
| postal_code | string or null <= 32 characters |
| city | string or null <= 191 characters |
| country | string or null = 2 characters |
string or null <email> <= 191 characters | |
| phone | string or null <= 64 characters |
| website | string or null <= 191 characters |
| vat_id | string or null <= 64 characters |
| tax_number | string or null <= 64 characters |
| notes | string or null <= 5000 characters |
| default_rate_cents | integer or null >= 0 |
| rate_valid_from | string or null <date> Day from which a new rate applies, default today. |
{- "name": "string",
- "color": "teal",
- "contact": "string",
- "contact_phone": "string",
- "customer_number": "string",
- "street": "string",
- "address_extra": "string",
- "postal_code": "string",
- "city": "string",
- "country": "st",
- "phone": "string",
- "website": "string",
- "vat_id": "string",
- "tax_number": "string",
- "notes": "string",
- "default_rate_cents": 0,
- "rate_valid_from": "2019-08-24"
}{- "data": {
- "id": 0,
- "name": "string",
- "color": "string",
- "contact": "string",
- "contact_email": "string",
- "contact_phone": "string",
- "customer_number": "string",
- "street": "string",
- "address_extra": "string",
- "postal_code": "string",
- "city": "string",
- "country": "string",
- "email": "string",
- "phone": "string",
- "website": "string",
- "vat_id": "string",
- "tax_number": "string",
- "notes": "string",
- "default_rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Clients are never deleted; this sets archived_at. Their projects stay as they are.
| client required | integer >= 1 |
{- "data": {
- "id": 0,
- "name": "string",
- "color": "string",
- "contact": "string",
- "contact_email": "string",
- "contact_phone": "string",
- "customer_number": "string",
- "street": "string",
- "address_extra": "string",
- "postal_code": "string",
- "city": "string",
- "country": "string",
- "email": "string",
- "phone": "string",
- "website": "string",
- "vat_id": "string",
- "tax_number": "string",
- "notes": "string",
- "default_rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Team members see the projects assigned to them (or all with "all projects"), without amounts.
| updated_since | string <date-time> Only records changed at or after this time (ISO 8601 with offset). |
| page[size] | integer [ 1 .. 200 ] Default: 50 Entries per page, 1 to 200. |
| page[cursor] | string
|
| client_id | integer >= 1 |
| status | string (ProjectStatus) Enum: "active" "paused" "completed" "archived" |
| search | string <= 100 characters |
{- "data": [
- {
- "id": 0,
- "client_id": 0,
- "name": "string",
- "color": "string",
- "status": "active",
- "billing_mode": "hourly",
- "fixed_price_cents": 0,
- "budget_seconds": 0,
- "budget_cents": 0,
- "budget_warn_pct": 0,
- "currency": "string",
- "notes": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "meta": {
- "page_size": 0,
- "next_cursor": "string",
- "has_more": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}Owners and admins. The Free plan allows three active projects (403 project_limit_reached). Fixed price and non-billable need Standard.
| name | string <= 191 characters |
| client_id | integer |
ProjectColor (string) or null | |
| status | string (ProjectStatus) Enum: "active" "paused" "completed" "archived" |
| billing_mode | string (BillingMode) Enum: "hourly" "fixed" "non_billable" |
| fixed_price_cents | integer or null >= 0 |
| notes | string or null <= 5000 characters |
{- "name": "Website relaunch",
- "client_id": 4
}{- "data": {
- "id": 0,
- "client_id": 0,
- "name": "string",
- "color": "string",
- "status": "active",
- "billing_mode": "hourly",
- "fixed_price_cents": 0,
- "budget_seconds": 0,
- "budget_cents": 0,
- "budget_warn_pct": 0,
- "currency": "string",
- "notes": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}{- "data": {
- "id": 0,
- "client_id": 0,
- "name": "string",
- "color": "string",
- "status": "active",
- "billing_mode": "hourly",
- "fixed_price_cents": 0,
- "budget_seconds": 0,
- "budget_cents": 0,
- "budget_warn_pct": 0,
- "currency": "string",
- "notes": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Only the fields you send change. Setting a project back to active counts against the project limit.
| project required | integer >= 1 |
| name | string <= 191 characters |
| client_id | integer |
ProjectColor (string) or null | |
| status | string (ProjectStatus) Enum: "active" "paused" "completed" "archived" |
| billing_mode | string (BillingMode) Enum: "hourly" "fixed" "non_billable" |
| fixed_price_cents | integer or null >= 0 |
| notes | string or null <= 5000 characters |
{- "name": "string",
- "client_id": 0,
- "color": "teal",
- "status": "active",
- "billing_mode": "hourly",
- "fixed_price_cents": 0,
- "notes": "string"
}{- "data": {
- "id": 0,
- "client_id": 0,
- "name": "string",
- "color": "string",
- "status": "active",
- "billing_mode": "hourly",
- "fixed_price_cents": 0,
- "budget_seconds": 0,
- "budget_cents": 0,
- "budget_warn_pct": 0,
- "currency": "string",
- "notes": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Projects are never deleted; this sets the status archived.
| project required | integer >= 1 |
{- "data": {
- "id": 0,
- "client_id": 0,
- "name": "string",
- "color": "string",
- "status": "active",
- "billing_mode": "hourly",
- "fixed_price_cents": 0,
- "budget_seconds": 0,
- "budget_cents": 0,
- "budget_warn_pct": 0,
- "currency": "string",
- "notes": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}| updated_since | string <date-time> Only records changed at or after this time (ISO 8601 with offset). |
| page[size] | integer [ 1 .. 200 ] Default: 50 Entries per page, 1 to 200. |
| page[cursor] | string
|
| project_id | integer >= 1 Only activities you can choose in this project (its own and the workspace-wide ones). |
| archived | string Enum: "true" "false" "1" "0" |
{- "data": [
- {
- "id": 0,
- "project_id": 0,
- "name": "string",
- "rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "meta": {
- "page_size": 0,
- "next_cursor": "string",
- "has_more": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}Owners and admins, from the Standard plan. Without project_id the activity applies to the whole workspace.
| name | string <= 191 characters |
| project_id | integer or null Only when creating. |
| rate_cents | integer or null >= 0 |
| rate_valid_from | string or null <date> |
{- "name": "string",
- "project_id": 0,
- "rate_cents": 0,
- "rate_valid_from": "2019-08-24"
}{- "data": {
- "id": 0,
- "project_id": 0,
- "name": "string",
- "rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}{- "data": {
- "id": 0,
- "project_id": 0,
- "name": "string",
- "rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}| activity required | integer >= 1 |
| name | string <= 191 characters |
| project_id | integer or null Only when creating. |
| rate_cents | integer or null >= 0 |
| rate_valid_from | string or null <date> |
{- "name": "string",
- "project_id": 0,
- "rate_cents": 0,
- "rate_valid_from": "2019-08-24"
}{- "data": {
- "id": 0,
- "project_id": 0,
- "name": "string",
- "rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}{- "data": {
- "id": 0,
- "project_id": 0,
- "name": "string",
- "rate_cents": 0,
- "currency": "string",
- "archived_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}Tags are assigned to time entries by name; unknown names create new tags.
| updated_since | string <date-time> Only records changed at or after this time (ISO 8601 with offset). |
| page[size] | integer [ 1 .. 200 ] Default: 50 Entries per page, 1 to 200. |
| page[cursor] | string
|
| search | string <= 64 characters |
{- "data": [
- {
- "id": 0,
- "name": "string",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "meta": {
- "page_size": 0,
- "next_cursor": "string",
- "has_more": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}Owners and admins see the whole team, team members only themselves.
| updated_since | string <date-time> Only records changed at or after this time (ISO 8601 with offset). |
| page[size] | integer [ 1 .. 200 ] Default: 50 Entries per page, 1 to 200. |
| page[cursor] | string
|
{- "data": [
- {
- "id": 0,
- "name": "string",
- "role": "owner",
- "timezone": "string",
- "locale": "string",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
], - "meta": {
- "page_size": 0,
- "next_cursor": "string",
- "has_more": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}{- "data": {
- "id": 0,
- "name": "string",
- "role": "owner",
- "timezone": "string",
- "locale": "string",
- "active": true,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}
}The same filters as the time list in the app. Team members only see their own entries and no
amounts. Sorted by start, newest first (sort=-started_at) or oldest first (sort=started_at).
| updated_since | string <date-time> Only records changed at or after this time (ISO 8601 with offset). |
| page[size] | integer [ 1 .. 200 ] Default: 50 Entries per page, 1 to 200. |
| page[cursor] | string
|
| from | string <date> First calendar day ( |
| to | string <date> Last calendar day, inclusive. |
| client_id | integer >= 1 |
integer or string or Array of integers One project, a comma separated list or | |
| activity_id | integer >= 1 |
| user_id | integer >= 1 Owners and admins only. |
| status | string (EntryStatus) Enum: "open" "billed" "locked" |
| billable | string Enum: "true" "false" "1" "0" |
| running | string Enum: "true" "false" "1" "0" Only the running timer ( |
| tag_id | integer >= 1 |
| search | string <= 100 characters Part of the note. |
| sort | string Default: "-started_at" Enum: "started_at" "-started_at" |
{- "data": [
- {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
], - "meta": {
- "page_size": 0,
- "next_cursor": "string",
- "has_more": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}Creates a time entry for the account of the token (source api). Without ended_at and
duration_seconds it is a running timer that started at started_at, in addition to any
other running timers; if the same project and activity is already running, that entry is
returned (200). At most 24 hours, the break may not exceed the
duration. With a client_uuid that already exists the existing entry is returned (200).
| project_id | integer Required when creating. |
| activity_id | integer or null |
| started_at | string <date-time> ISO 8601 with offset or Z. Required when creating. |
| ended_at | string or null <date-time> Omit (and omit |
| duration_seconds | integer or null [ 0 .. 86400 ] Instead of |
| break_seconds | integer or null >= 0 |
| note | string or null <= 2000 characters |
| tags | Array of strings or null <= 20 items [ items <= 64 characters ] |
| billable | boolean or null Default from the billing mode of the project. |
| client_uuid | string or null <uuid> Makes creating idempotent. |
| rate_override_cents | integer or null >= 0 Rate for this entry only; owners and admins. |
{- "project_id": 12,
- "started_at": "2026-10-14T09:00:00+02:00",
- "ended_at": "2026-10-14T10:30:00+02:00",
- "note": "Workshop",
- "tags": [
- "onsite"
]
}{- "data": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}Every time entry changed after cursor, ordered by sync_version, with tombstones of
deleted entries (deleted_at set). Premium. Team members only get their own entries.
| cursor | string^\d{1,20}$
|
| limit | integer [ 1 .. 1000 ] Default: 500 |
| include_deleted | string Default: "1" Enum: "true" "false" "1" "0" |
| If-None-Match | string
|
{- "data": [
- {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
], - "meta": {
- "cursor": "string",
- "next_cursor": "string",
- "has_more": true,
- "limit": 0,
- "include_deleted": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}{- "data": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}Only the fields you send change; tags replaces all tags. A finished entry stays finished,
a running timer gets its end with ended_at. Billed or locked entries answer 423, entries
behind the edit lock of the workspace (team members) as well.
| timeEntry required | integer >= 1 |
| project_id | integer Required when creating. |
| activity_id | integer or null |
| started_at | string <date-time> ISO 8601 with offset or Z. Required when creating. |
| ended_at | string or null <date-time> Omit (and omit |
| duration_seconds | integer or null [ 0 .. 86400 ] Instead of |
| break_seconds | integer or null >= 0 |
| note | string or null <= 2000 characters |
| tags | Array of strings or null <= 20 items [ items <= 64 characters ] |
| billable | boolean or null Default from the billing mode of the project. |
| client_uuid | string or null <uuid> Makes creating idempotent. |
| rate_override_cents | integer or null >= 0 Rate for this entry only; owners and admins. |
{- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "duration_seconds": 86400,
- "break_seconds": 0,
- "note": "string",
- "tags": [
- "string"
], - "billable": true,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "rate_override_cents": 0
}{- "data": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}The entry disappears from lists and appears as a tombstone in the delta sync.
| timeEntry required | integer >= 1 |
{- "errors": [
- {
- "status": "401",
- "code": "unauthenticated",
- "title": "Not signed in",
- "detail": "Send a valid token as Bearer in the Authorization header."
}
]
}Totals of finished time entries, grouped by client, project, activity, account (owners and
admins) or day, with the same numbers as the reports, the dashboard and the exports. Without
from and to the current month in the time zone of the account.
| from | string <date> |
| to | string <date> |
| group_by | string Default: "project" Enum: "client" "project" "activity" "user" "day" |
| client_id | integer >= 1 |
integer or string or Array of integers | |
| activity_id | integer >= 1 |
| user_id | integer >= 1 |
| status | string (EntryStatus) Enum: "open" "billed" "locked" |
| billable | string Enum: "true" "false" "1" "0" |
| tag_id | integer >= 1 |
{- "data": {
- "total": {
- "entry_count": 0,
- "duration_seconds": 0,
- "rounded_seconds": 0,
- "amount_cents": 0,
- "billable_seconds": 0,
- "non_billable_seconds": 0
}, - "groups": [
- {
- "key": "string",
- "name": "string",
- "client_id": 0,
- "entry_count": 0,
- "duration_seconds": 0,
- "rounded_seconds": 0,
- "amount_cents": 0,
- "billable_seconds": 0,
- "non_billable_seconds": 0
}
]
}, - "meta": {
- "from": "2019-08-24",
- "to": "2019-08-24",
- "group_by": "client",
- "currency": "string",
- "amounts_visible": true
}
}Newest first. Owners and admins, ability billing.
| updated_since | string <date-time> Only records changed at or after this time (ISO 8601 with offset). |
| page[size] | integer [ 1 .. 200 ] Default: 50 Entries per page, 1 to 200. |
| page[cursor] | string
|
| client_id | integer >= 1 |
{- "data": [
- {
- "id": 0,
- "client_id": 0,
- "project_id": 0,
- "reference": "string",
- "invoiced_at": "2019-08-24",
- "until": "2019-08-24",
- "entry_count": 0,
- "rounded_seconds": 0,
- "amount_cents": 0,
- "currency": "string",
- "created_by": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "cancelled_at": "2019-08-24T14:15:22Z",
- "cancelled_by": 0
}
], - "meta": {
- "page_size": 0,
- "next_cursor": "string",
- "has_more": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}Marks time entries as billed with the invoice reference of your ERP and locks them. Either
entry_ids (exactly these entries of one client; already billed or locked ones answer 409
with their list, running or non-billable ones 422) or filter (all open, finished,
billable entries of a client up to a day, optionally of one project; others are skipped and
counted). With dry_run: true nothing is written and the answer shows what would happen.
Mixed currencies in one run answer 422.
| entry_ids | Array of integers [ 1 .. 1000 ] items |
object | |
| invoice_ref required | string <= 191 characters |
| invoiced_at | string or null <date> Invoice date, default today. |
| dry_run | boolean Default: false |
{- "entry_ids": [
- 101,
- 102,
- 103
], - "invoice_ref": "RE-2026-0042",
- "invoiced_at": "2026-10-31"
}{- "data": {
- "dry_run": true,
- "billing_run": {
- "id": 0,
- "client_id": 0,
- "project_id": 0,
- "reference": "string",
- "invoiced_at": "2019-08-24",
- "until": "2019-08-24",
- "entry_count": 0,
- "rounded_seconds": 0,
- "amount_cents": 0,
- "currency": "string",
- "created_by": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "cancelled_at": "2019-08-24T14:15:22Z",
- "cancelled_by": 0
}, - "entry_ids": [
- 0
], - "entry_count": 0,
- "rounded_seconds": 0,
- "totals": [
- {
- "currency": "string",
- "entry_count": 0,
- "rounded_seconds": 0,
- "amount_cents": 0
}
], - "skipped": {
- "locked": 0,
- "running": 0,
- "non_billable": 0
}
}
}{- "data": {
- "id": 0,
- "client_id": 0,
- "project_id": 0,
- "reference": "string",
- "invoiced_at": "2019-08-24",
- "until": "2019-08-24",
- "entry_count": 0,
- "rounded_seconds": 0,
- "amount_cents": 0,
- "currency": "string",
- "created_by": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "cancelled_at": "2019-08-24T14:15:22Z",
- "cancelled_by": 0
}, - "meta": {
- "entry_ids": [
- 0
]
}
}Its time entries are open again and recalculated with today's rules; the run stays with cancelled_at as a record.
| billingRun required | integer >= 1 |
{- "data": {
- "id": 0,
- "client_id": 0,
- "project_id": 0,
- "reference": "string",
- "invoiced_at": "2019-08-24",
- "until": "2019-08-24",
- "entry_count": 0,
- "rounded_seconds": 0,
- "amount_cents": 0,
- "currency": "string",
- "created_by": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "cancelled_at": "2019-08-24T14:15:22Z",
- "cancelled_by": 0
}, - "meta": {
- "reverted_entries": 0
}
}The running timers of the account of the token (several may run in parallel). Ability read or track (device tokens).
{- "data": {
- "running": true,
- "count": 0,
- "elapsed_seconds": 0,
- "entry": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}, - "entries": [
- {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
]
}
}Tracked time today (rounded, own entries, the same numbers as the reports) plus the running
timer, and a short sentence in the language of the account (not of the request), for iOS
Shortcuts ("Speak Text"), Siri and voice assistants. Ability read or track (device tokens).
With format=text the answer is only the sentence as text/plain.
| format | string Default: "json" Enum: "json" "text" |
{- "data": {
- "running": true,
- "running_count": 0,
- "project": "string",
- "activity": "string",
- "projects": [
- "string"
], - "elapsed_seconds": 0,
- "today_seconds": 0,
- "text": "string",
- "locale": "de"
}
}Everything a tracker such as Horaka Desktop needs to offer a choice: bookable projects
(active and paused, visible to the account, not of archived clients, for device tokens only the allowed ones) with
client and selectable activities, sorted by client and project, plus the favourites and
recently used combinations of the account and whether parallel timers are on. Never amounts.
Ability read or track (device tokens).
{- "data": {
- "projects": [
- {
- "id": 0,
- "name": "string",
- "client_id": 0,
- "client_name": "string",
- "color": "string",
- "status": "active",
- "activities": [
- {
- "id": 0,
- "name": "string"
}
]
}
], - "favorites": [
- {
- "project_id": 0,
- "activity_id": 0
}
], - "recent": [
- {
- "project_id": 0,
- "activity_id": 0
}
], - "parallel_timers": true
}
}Starts the project in addition to any running timers. If it is already running (with the same activity, if given), nothing changes.
| Idempotency-Key | string <= 191 characters Unique text per press (1 to 191 visible ASCII characters), valid for 24 hours per token. |
integer or string Project ID or alias. Required for start and switch. | |
integer or string or null Activity ID or name; an alias can bring its activity. | |
| note | string or null <= 2000 characters |
{- "project": "MUE-WEB"
}{- "data": {
- "running": true,
- "elapsed_seconds": 0,
- "entry": {
- "id": 931,
- "client_uuid": "2f1c7a3e-0b7f-4a52-9d55-6f3c9f0e1a24",
- "user_id": 1,
- "client_id": 4,
- "project_id": 12,
- "activity_id": null,
- "started_at": "2026-10-14T08:00:00Z",
- "ended_at": null,
- "timezone": "Europe/Berlin",
- "local_date": "2026-10-14",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": null,
- "break_deduction_seconds": null,
- "rounded_seconds": null,
- "billable": true,
- "rate_cents": null,
- "rate_source": null,
- "amount_cents": null,
- "currency": null,
- "status": "open",
- "billed_at": null,
- "billing_reference": null,
- "billing_run_id": null,
- "locked_at": null,
- "note": null,
- "tags": [ ],
- "source": "device",
- "device_id": 3,
- "sync_version": 5120,
- "created_at": "2026-10-14T08:00:00Z",
- "updated_at": "2026-10-14T08:00:00Z",
- "deleted_at": null
}
}, - "meta": {
- "action": "started",
- "debounced": false
}
}For trackers such as Horaka Desktop: sets the note of an own time entry that is running or
ended at most 24 hours ago, for example a summary right after stopping. An empty note removes
it. Ability track; device tokens only on their projects. Locked entries and older ones
answer 423, entries of others 404.
| timeEntry required | integer >= 1 |
| note required | string or null <= 2000 characters |
{- "note": "Workshop prepared, slides sent"
}{- "data": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}Stops all running timers, or with project only the running timers of that project; without a matching timer nothing happens (meta.action is none). Device tokens only stop timers on their projects.
| Idempotency-Key | string <= 191 characters Unique text per press (1 to 191 visible ASCII characters), valid for 24 hours per token. |
integer or string Project ID or alias. Required for start and switch. | |
integer or string or null Activity ID or name; an alias can bring its activity. | |
| note | string or null <= 2000 characters |
{- "project": 0,
- "activity": 0,
- "note": "string"
}{- "data": {
- "running": true,
- "elapsed_seconds": 0,
- "entry": {
- "id": 931,
- "client_uuid": "2f1c7a3e-0b7f-4a52-9d55-6f3c9f0e1a24",
- "user_id": 1,
- "client_id": 4,
- "project_id": 12,
- "activity_id": null,
- "started_at": "2026-10-14T08:00:00Z",
- "ended_at": null,
- "timezone": "Europe/Berlin",
- "local_date": "2026-10-14",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": null,
- "break_deduction_seconds": null,
- "rounded_seconds": null,
- "billable": true,
- "rate_cents": null,
- "rate_source": null,
- "amount_cents": null,
- "currency": null,
- "status": "open",
- "billed_at": null,
- "billing_reference": null,
- "billing_run_id": null,
- "locked_at": null,
- "note": null,
- "tags": [ ],
- "source": "device",
- "device_id": 3,
- "sync_version": 5120,
- "created_at": "2026-10-14T08:00:00Z",
- "updated_at": "2026-10-14T08:00:00Z",
- "deleted_at": null
}
}, - "meta": {
- "action": "started",
- "debounced": false
}
}If the given project is running, its timers stop; otherwise the given project starts in addition to running timers. Without a project, all running timers stop. Ideal for one button per project.
| Idempotency-Key | string <= 191 characters Unique text per press (1 to 191 visible ASCII characters), valid for 24 hours per token. |
integer or string Project ID or alias. Required for start and switch. | |
integer or string or null Activity ID or name; an alias can bring its activity. | |
| note | string or null <= 2000 characters |
{- "project": 0,
- "activity": 0,
- "note": "string"
}{- "data": {
- "running": true,
- "elapsed_seconds": 0,
- "entry": {
- "id": 931,
- "client_uuid": "2f1c7a3e-0b7f-4a52-9d55-6f3c9f0e1a24",
- "user_id": 1,
- "client_id": 4,
- "project_id": 12,
- "activity_id": null,
- "started_at": "2026-10-14T08:00:00Z",
- "ended_at": null,
- "timezone": "Europe/Berlin",
- "local_date": "2026-10-14",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": null,
- "break_deduction_seconds": null,
- "rounded_seconds": null,
- "billable": true,
- "rate_cents": null,
- "rate_source": null,
- "amount_cents": null,
- "currency": null,
- "status": "open",
- "billed_at": null,
- "billing_reference": null,
- "billing_run_id": null,
- "locked_at": null,
- "note": null,
- "tags": [ ],
- "source": "device",
- "device_id": 3,
- "sync_version": 5120,
- "created_at": "2026-10-14T08:00:00Z",
- "updated_at": "2026-10-14T08:00:00Z",
- "deleted_at": null
}
}, - "meta": {
- "action": "started",
- "debounced": false
}
}Ends all other running timers now and starts the given project at the same moment (if it is already running, it keeps running). Device tokens only stop timers on their projects.
| Idempotency-Key | string <= 191 characters Unique text per press (1 to 191 visible ASCII characters), valid for 24 hours per token. |
integer or string Project ID or alias. Required for start and switch. | |
integer or string or null Activity ID or name; an alias can bring its activity. | |
| note | string or null <= 2000 characters |
{- "project": 0,
- "activity": 0,
- "note": "string"
}{- "data": {
- "running": true,
- "elapsed_seconds": 0,
- "entry": {
- "id": 931,
- "client_uuid": "2f1c7a3e-0b7f-4a52-9d55-6f3c9f0e1a24",
- "user_id": 1,
- "client_id": 4,
- "project_id": 12,
- "activity_id": null,
- "started_at": "2026-10-14T08:00:00Z",
- "ended_at": null,
- "timezone": "Europe/Berlin",
- "local_date": "2026-10-14",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": null,
- "break_deduction_seconds": null,
- "rounded_seconds": null,
- "billable": true,
- "rate_cents": null,
- "rate_source": null,
- "amount_cents": null,
- "currency": null,
- "status": "open",
- "billed_at": null,
- "billing_reference": null,
- "billing_run_id": null,
- "locked_at": null,
- "note": null,
- "tags": [ ],
- "source": "device",
- "device_id": 3,
- "sync_version": 5120,
- "created_at": "2026-10-14T08:00:00Z",
- "updated_at": "2026-10-14T08:00:00Z",
- "deleted_at": null
}
}, - "meta": {
- "action": "started",
- "debounced": false
}
}Owners and admins, ability webhooks.
| updated_since | string <date-time> Only records changed at or after this time (ISO 8601 with offset). |
| page[size] | integer [ 1 .. 200 ] Default: 50 Entries per page, 1 to 200. |
| page[cursor] | string
|
{- "data": [
- {
- "id": 0,
- "url": "string",
- "events": [
- "time_entry.created"
], - "active": true,
- "failure_count": 0,
- "last_success_at": "2019-08-24T14:15:22Z",
- "last_failure_at": "2019-08-24T14:15:22Z",
- "disabled_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "secret": "string"
}
], - "meta": {
- "page_size": 0,
- "next_cursor": "string",
- "has_more": true
}, - "links": {
- "self": "string",
- "next": "string"
}
}The URL must use https and may not point into a private network. The secret (generated unless you send one) is only part of this answer.
| url required | string <uri> <= 2000 characters |
| events required | Array of strings (WebhookEventName) non-empty Items Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| secret | string [ 16 .. 191 ] characters Own secret; generated when missing. |
{- "events": [
- "time_entry.created",
- "time_entry.updated",
- "billing_run.created"
]
}{- "data": {
- "id": 0,
- "url": "string",
- "events": [
- "time_entry.created"
], - "active": true,
- "failure_count": 0,
- "last_success_at": "2019-08-24T14:15:22Z",
- "last_failure_at": "2019-08-24T14:15:22Z",
- "disabled_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "secret": "string"
}
}| webhook required | integer >= 1 |
{- "data": {
- "id": 0,
- "url": "string",
- "events": [
- "time_entry.created"
], - "active": true,
- "failure_count": 0,
- "last_success_at": "2019-08-24T14:15:22Z",
- "last_failure_at": "2019-08-24T14:15:22Z",
- "disabled_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "secret": "string"
}
}| webhook required | integer >= 1 |
{- "errors": [
- {
- "status": "401",
- "code": "unauthenticated",
- "title": "Not signed in",
- "detail": "Send a valid token as Bearer in the Authorization header."
}
]
}Also for a timer that starts (then timer.started follows).
| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}Also when the server recalculates rounding or amounts.
| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}Once per project, kind (time or money) and threshold (the warning threshold of the project and 100 percent).
| X-Horaka-Signature required | string
|
| X-Horaka-Event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| X-Horaka-Delivery-Id required | string <uuid> Unique per delivery attempt chain; a replay gets a new one. |
| id required | string ID of the event; stays the same on a replay. |
| event required | string (WebhookEventName) Enum: "time_entry.created" "time_entry.updated" "time_entry.deleted" "time_entry.locked" "timer.started" "timer.stopped" "billing_run.created" "billing_run.cancelled" "project.budget_threshold" |
| created_at required | string <date-time> |
| api_version required | string Value: "v1" |
required | object |
{- "id": "string",
- "event": "time_entry.created",
- "created_at": "2019-08-24T14:15:22Z",
- "api_version": "v1",
- "data": {
- "object": {
- "id": 0,
- "client_uuid": "c484e8ef-f25a-4dd9-ab89-f54a430ba3fc",
- "user_id": 0,
- "client_id": 0,
- "project_id": 0,
- "activity_id": 0,
- "started_at": "2019-08-24T14:15:22Z",
- "ended_at": "2019-08-24T14:15:22Z",
- "timezone": "string",
- "local_date": "2019-08-24",
- "running": true,
- "break_seconds": 0,
- "duration_seconds": 0,
- "break_deduction_seconds": 0,
- "rounded_seconds": 0,
- "billable": true,
- "rate_cents": 0,
- "rate_source": "entry",
- "amount_cents": 0,
- "currency": "string",
- "status": "open",
- "billed_at": "2019-08-24",
- "billing_reference": "string",
- "billing_run_id": 0,
- "locked_at": "2019-08-24T14:15:22Z",
- "note": "string",
- "tags": [
- "string"
], - "source": "web",
- "device_id": 0,
- "sync_version": 0,
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z",
- "deleted_at": "2019-08-24T14:15:22Z"
}
}
}