Skip to content

Request an integration

Tell us which system you want to connect. We review every request weekly and publish a step-by-step guide or build the connection.

Rate limits

The TimeTracking API allows 600 requests per minute per API key; how the window works, what a 429 looks like, and how to stay under the limit with batch endpoints and pagination.

Last updated: · Markdown version

Limit Value
Requests per API key 600 per minute
Window Fixed calendar minute (UTC), counted per key; resets at hh:mm:00
Applies to Requests authenticated with an API key (tt_live_…)
Batch size 500 items per batch request
Page size limit 1–500 (default 100)
Other limits Webhook delivery timeout 10 s; inbound email: max 10 attachments, 15 MB each, XLSX ≤ 5 MB and ≤ 20,000 rows; free orgs: 25 inbound emails per day

When you exceed it

HTTP/1.1 429 Too Many Requests
retry-after: 60
content-type: application/json

{"error":{"code":"rate_limited","message":"Rate limit of 600 requests per minute exceeded"}}

Retry-After is always 60 seconds. Requests that hit the limit still count. Wait for Retry-After, or until the next calendar minute, before retrying.

Staying under the limit

  • Batch writes. POST /people/batch, /clients/batch, /placements/batch and /claims/batch take up to 500 rows in one request and report results per row.
  • Paginate with large pages. GET /days?from=…&to=…&limit=500. Use updated_since for incremental syncs instead of re-reading everything (see pagination).
  • Use webhooks instead of polling. Fetch details only when an event arrives (see webhooks).
  • Use one key per integration. Each key has its own budget.
  • Spread scheduled jobs. Avoid starting every cron job at :00.

Retry pattern

import time, requests

def call(method, url, **kw):
    for attempt in range(6):
        r = requests.request(method, url, timeout=30, **kw)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "60")))
            continue
        if r.status_code >= 500:
            time.sleep(min(2 ** attempt, 30))
            continue          # pass the same Idempotency-Key on writes
        return r
    raise RuntimeError("gave up")

Planned: a dedicated bulk export feed for BI, so you do not need high-volume polling.