Authentication

Every request sends an API key as a bearer token. Keys are made by your own administrators in TempClock, one per system, each with only the access that system needs.

Sending the key

Send the key in the Authorization header of every request, over HTTPS:

Authorization: Bearer tc_live_4f0c9a2e7b1d58c3a6e9f07b3d2c1a54e8f6b9d0

A key is tc_live_ followed by 40 characters. Bearer can be any case; the key itself is case sensitive. Never put a key in a URL, a web page or a mobile app: anyone who can read it can use it until it is revoked.

Switching API access on

API access is off for a new client account. A TempClock administrator switches it on once, in the agency console under Setup > API > Client API Access. From then on the account's own administrators make and manage their keys themselves: nobody has to ask TempClock for a key.

Switching API access off deactivates every key of the account at once: they then answer 401 key_revoked. Switching access back on does not bring them back, so create new keys. Webhook subscriptions made with those keys stop receiving events, and each waiting delivery is recorded as refused in the delivery log.

Creating a key

  1. Open Setup > API keys in the client dashboard (account admins). In the agency console, use Setup > API > Generate API Key for Client.
  2. Name the key after the system that will use it, for example Payroll export or Site board, North depot. Use one key per system so you can revoke one without stopping the others.
  3. Under What this key can do, choose a level for each resource: No access, Read only or Read and write. Pay, charges, rates, on site now, availability, absences and clock start at No access. Payroll starts at Read only for people who may grant it: set it to No access unless the system needs payroll lines.
  4. Optionally open Expiry, rate limit, sites and browser origins to set an expiry date, a rate limit or browser origins (see below).
  5. Select Create key. The full key is shown once: copy it into your system's secret store straight away. TempClock keeps only a hash and the first 16 characters.

The step by step guide with screenshots is API keys and logs.

Testing a key

GET /me.php needs no scope. It answers which account and sites the key sees, its scopes (as stored, and as they really apply), its rate limit and its expiry, and never shows the key itself.

Test a key: who it belongs to, its sites, scopes, limit and expiry 200

Key used: a key holding every scope.

Request

curl -s "$TC_BASE/me.php" \
  -H "Authorization: Bearer $TC_KEY"

Real reply (local test copy, fake demo data)

HTTP 200

{
  "data": {
    "api_key_id": 30,
    "key_name": "Docs examples 77E88",
    "key_prefix": "tc_live_7d1f46d1",
    "kind": "client",
    "client_id": 21,
    "client_ids": [
      21
    ],
    "client_name": "Apex Staffing Solutions",
    "site_limited": false,
    "location_ids": null,
    "locations": [
      {
        "id": 39,
        "name": "Birmingham Office",
        "active": true
      },
      {
        "id": 37,
        "name": "London HQ",
        "active": true
      },
      {
        "id": 38,
        "name": "Manchester Warehouse",
        "active": true
      }
    ],
    "scopes": [
      "workers:read",
      "workers:write",
      "timesheets:read",
      "payroll:read",
      "pay:read",
      "charges:read",
      "rates:write",
      "shifts:read",
      "shifts:write",
      "onsite:read",
      "locations:read",
      "departments:read",
      "cost_codes:read",
      "availability:read",
      "availability:write",
      "absences:read",
      "clock:write",
      "webhooks:read",
      "webhooks:write"
    ],
    "effective_scopes": [
      "absences:read",
      "availability:read",
      "availability:write",
      "charges:read",
      "clock:write",
      "cost_codes:read",
      "departments:read",
      "locations:read",
      "onsite:read",
      "pay:read",
      "payroll:read",
      "rates:write",
      "shifts:read",
      "shifts:write",
      "timesheets:read",
      "webhooks:read",
      "webhooks:write",
      "workers:read",
  ... (12 more lines)

Scopes

A scope is <resource>:read or <resource>:write. Write includes read, and payroll:read includes pay:read (payroll lines are pay). A request without the scope it needs gets 403 with error_code: insufficient_scope and the scope in required_scope. This table is generated from the same catalogue the key screens and the API use.

ScopeShown asWhat it opensWho may grant itIn the v1.1 set
workers:readWorkersWorker records. Seeing pay and charge rates needs the Pay and Charges scopes. Read: list and view workers.NoneYes
workers:writeWorkersWorker records. Seeing pay and charge rates needs the Pay and Charges scopes. Write: also create and update workers. Setting or clearing pay and charge rates (hourly_rate, charge_rate) also needs the Set rates scope, rates:write. Includes read.NoneYes
timesheets:readTimesheetsClock records with approval status and paid minutes, plus who is on site now.NoneYes
payroll:readPayrollApproved, priced hours per worker per pay week, split by pay element. Includes pay. Includes pay:read.See pay rates and pay totalsYes
pay:readPayPay rates, pay amounts and pay elements wherever a resource shows them.See pay rates and pay totalsYes
charges:readChargesCharge rates, charge amounts and billing fields (PO, Sage account, VAT).See pay rates and pay totalsYes
rates:writeSet ratesSet or clear pay and charge rates on worker records.Change pay and charge ratesNo
shifts:readShiftsScheduled shifts and open demand. Read: list and view shifts, open demand and offers.NoneYes
shifts:writeShiftsScheduled shifts and open demand. Write: also book, change and cancel shifts and add open demand. Includes read.NoneYes
onsite:readOn site nowWho is on site right now (no pay data).NoneNo
locations:readLocationsSites and their geofence settings.NoneYes
departments:readDepartmentsDepartment list.NoneYes
cost_codes:readCost codesCost code list.NoneYes
availability:readAvailabilityWorker availability rules, and who is free for a time. Read: list availability rules and see who is free.NoneNo
availability:writeAvailabilityWorker availability rules, and who is free for a time. Write: also add and retire availability rules. Includes read.NoneNo
absences:readAbsencesHoliday and sickness requests.NoneNo
clock:writeClock in/outClock workers in and out from another system.NoneYes
webhooks:readWebhooksWebhook subscriptions. Read: list subscriptions, the event list and the delivery log.NoneYes
webhooks:writeWebhooksWebhook subscriptions. Write: also create, change, delete and test subscriptions and rotate the signing secret. Includes read.NoneYes

Who may grant it: a person can only give a key pay, charges or payroll access if they hold the See pay rates and pay totals permission themselves, and rate changes only with Change pay and charge rates. The key screens show those levels as locked for anyone without the permission, and the server refuses them whatever is posted. In the client dashboard, tenant managers and admins hold both.

Pay and charge figures appear only to keys that may see them: hourly_rate on a worker needs pay:read, charge_rate needs charges:read (the fields are left out otherwise), and setting either rate needs rates:write.

The same list with a workers:read key: no rates 200

Key used: a key with workers:read only.

Request

curl -s "$TC_BASE/workers.php?active=true&limit=1" \
  -H "Authorization: Bearer $TC_KEY"

Real reply (local test copy, fake demo data)

HTTP 200

{
  "data": [
    {
      "id": 351,
      "name": "Aisha Rahman",
      "employee_id": "AX025",
      "location_id": 39,
      "location_name": "Birmingham Office",
      "department_id": 42,
      "department_name": "Customer Service",
      "cost_code_id": null,
      "cost_code": null,
      "email": null,
      "phone": null,
      "job_role": "Customer Service Agent",
      "max_hours_per_week": 48,
      "status": "active",
      "active": true,
      "dormant": false,
      "dormant_since": null,
      "dormant_since_utc": null,
      "start_date": "2026-03-18",
      "end_date": null,
      "leave_reason": null,
      "preferred_language": null,
      "has_face": false,
      "has_pin": true,
      "created_at": "2026-09-30 10:58:11",
      "created_at_utc": "2026-09-30T09:58:11Z",
      "updated_at": "2026-09-30 10:58:11",
      "updated_at_utc": "2026-09-30T09:58:11Z"
    }
  ],
  "pagination": {
    "total": 25,
    "limit": 1,
    "offset": 0,
    "has_more": true
  }
}
A workers:read key asking for payroll 403

Key used: a key with workers:read only.

Request

curl -s "$TC_BASE/payroll.php" \
  -H "Authorization: Bearer $TC_KEY"

Real reply (local test copy, fake demo data)

HTTP 403

{
  "error": true,
  "message": "Insufficient permissions. This key does not have the 'payroll:read' scope.",
  "status": 403,
  "error_code": "insufficient_scope",
  "required_scope": "payroll:read"
}

Least privilege recipes

IntegrationGive the keyLeave at No access
Payroll import (Sage or another payroll system)payroll:read (includes pay). Add webhooks:write to be told when a week is locked.Everything else. Add charges:read only if it needs charge totals.
Invoicing or billingcharges:read, payroll:read for the pay week statusWorkers write, rates, clock
ATS or CRM worker syncworkers:write, locations:read, departments:read, cost_codes:readrates:write, pay, charges, payroll
Booking systemshifts:write, availability:read, absences:read, workers:read, locations:readPay, charges, payroll, rates
On-site board or fire register displayonsite:read onlyEverything else (no pay, no worker records)
Turnstile or third-party clocking terminalclock:write only, one key per terminal systemEverything else
BI or reporting tooltimesheets:read, workers:read, lookupspay:read and charges:read unless it must show money

What a key can see

  • One client account. A key belongs to one client and only ever sees that client's data. Records of other clients answer 404, the same as a record that does not exist.
  • The site's client owns the shift. A time record, booking or payroll line belongs to the client that owns the site where it was worked, else the worker's home client. This is the rule payroll and the Sage export use, so a worker pooled from another client who works at your site appears in your timesheets and payroll, and your worker at another client's site does not.
  • Every site of the account. A key sees every site of its client. Limiting a key to some sites is not available yet: the key screens do not offer it and the server refuses it. The reply 403 location_not_allowed is reserved for site-limited keys, so no key you can make today gets it.
  • Several client accounts. An agency-wide key that covers several client accounts at once is planned and not available in v1.3. Today, use one key per client account. In a single-client set-up one client key already covers every site.
  • Who did it. Workers, bookings, requirements, availability rules and webhook subscriptions created or changed with a key are written to the audit log as API key #<id> (client #<id>), never as a person. A clock sent through the API is recorded on the time entry with the clock method api.

Expiry, rotation and revoking

SettingWhat it does
Expiry dateThe key stops working at the end of that day (401 key_expired). Leave it empty for no expiry.
Rate limitRequests a minute for this key: 60 to 120, or up to 600 when set by a TempClock super admin. See Rate limits.
Sites this key can seeEvery site of the account. Limiting a key to some sites is not available yet.
Browser originsOnly needed if a web page calls the API straight from the browser (CORS). One origin per line, for example https://reports.example.com. Leave it empty for server to server use: then no browser can call the API with the key.

Rotate makes a new key with the same access and settings, and keeps the old key working for the overlap you choose (none, 1, 3, 7, 14 or 30 days; 7 by default), so you can swap the secret in without downtime. Once the overlap ends the old key answers 401 key_expired. Webhook subscriptions made with the old key stop when its overlap ends: create them again with the new key before then (each gets a new signing secret). Rotating a key that has pay access needs the pay permission, because it hands out a new secret. A key that has been rotated or has expired can no longer be edited.

Revoke stops a key at once (401 key_revoked). Revoke a key as soon as the system that used it is retired or the key may have leaked, then make a new one.

A revoked key 401

Key used: a revoked key.

Request

curl -s "$TC_BASE/workers.php" \
  -H "Authorization: Bearer $TC_KEY"

Real reply (local test copy, fake demo data)

HTTP 401

{
  "error": true,
  "message": "This API key has been revoked. Create a new key.",
  "status": 401,
  "error_code": "key_revoked"
}

Older keys

Keys made before scopes existed are fixed to the v1.1 scope set: workers:read, workers:write, timesheets:read, payroll:read, shifts:read, shifts:write, locations:read, departments:read, cost_codes:read, clock:write, webhooks:read, webhooks:write, pay:read, charges:read. They keep exactly what they could do and never gain a scope added later. They show as Legacy key on the key list; use Edit access to give them an explicit, smaller set. When v1.3 split pay and charges into their own scopes, keys that could read workers or timesheets were given pay:read and charges:read so nothing stopped working; remove them from keys that do not need pay data. Each change the upgrade made is in the audit log as System (API v1.3 upgrade).

API logs and audit

Setup > API logs lists every request made with your keys, including refused ones: time, key, method, path, status, error code, response time and the request id. Filter Status Code to Any failure to see only problems, or pick one key. Every reply carries an X-Request-Id header with the same id: quote it when you ask for help.

Creating a key, changing its access or settings, rotating and revoking it are written to the audit log with the values before and after.

Refused requests

Statuserror_codeWhen
401missing_api_keyNo Authorization: Bearer header.
401invalid_api_keyThe key is not recognised (often a key cut short when it was copied).
401key_revokedThe key was revoked, or API access was switched off for the account (every key is deactivated then, and switching access back on does not reactivate them: create new keys).
401key_expiredThe key passed its expiry date, or it was rotated and its overlap has ended.
403api_access_disabledAPI access is off for the account but the key was not deactivated (rare).
403account_suspendedThe account is suspended or cancelled, or its trial has ended. Every key stops working until the account is active again.
403insufficient_scopeThe key lacks the scope in required_scope.
403location_not_allowedA site-limited key named another site. Only a key limited to some sites gets it, and such keys are not available yet.
429rate_limitedOver the key's limit, or 20 unknown keys from one address in 5 minutes.
No Authorization header 401

Key used: no key.

Request

curl -s "$TC_BASE/workers.php"

Real reply (local test copy, fake demo data)

HTTP 401

{
  "error": true,
  "message": "Missing or invalid Authorization header. Use: Authorization: Bearer <api_key>",
  "status": 401,
  "error_code": "missing_api_key"
}
A key that does not exist 401

Key used: a key that does not exist.

Request

curl -s "$TC_BASE/workers.php" \
  -H "Authorization: Bearer tc_live_0000000000000000000000000000000000000000"

Real reply (local test copy, fake demo data)

HTTP 401

{
  "error": true,
  "message": "Invalid API key.",
  "status": 401,
  "error_code": "invalid_api_key"
}
A key past its expiry date 401

Key used: an expired key.

Request

curl -s "$TC_BASE/workers.php" \
  -H "Authorization: Bearer $TC_KEY"

Real reply (local test copy, fake demo data)

HTTP 401

{
  "error": true,
  "message": "This API key has expired. Please create a new key.",
  "status": 401,
  "error_code": "key_expired"
}