Developers

Build on LoggerIQ

A REST API with scoped keys, signed webhooks and a read-only connector for AI assistants, so payroll, HR and reporting systems stay in step with the hours your team actually worked.

Last updated
On this page
  1. Quickstart
  2. Authentication and scopes
  3. Pages, limits and errors
  4. Endpoints
  5. Webhooks
  6. Verifying signatures
  7. AI assistants

Quickstart

  1. A workspace admin turns on API and webhooks in Settings, Modules. It is included in Pro.
  2. In Settings, API and webhooks, they create a key with the scopes your integration needs and copy it. It is shown once.
  3. Call the API with the key as a bearer token.
curl https://app.loggeriq.io/api/v1/me \
  -H "Authorization: Bearer liq_sk_..."

curl "https://app.loggeriq.io/api/v1/clock-entries?from=2026-10-01T00:00:00Z&limit=50" \
  -H "Authorization: Bearer liq_sk_..."

Every endpoint lives under https://app.loggeriq.io/api/v1. The full description is an OpenAPI 3.1 document at /api/v1/openapi.json, ready for code generators and API clients.

Authentication and scopes

Send the key in the Authorization header as Bearer liq_sk_.... A key belongs to one workspace and can only ever see that workspace. Keys start with liq_sk_ so a leaked one is easy to spot in a scan. We store only a hash of each key, so a lost key cannot be shown again: revoke it and make a new one.

Each key holds one or more scopes. An endpoint needs exactly one of them.

ScopeGrants
people:readPeople in the workspace and its sites.
time:readClock entries, approved timesheet weeks and published shifts.
clock_entries:writeCreate clock entries. They arrive for review, as they carry no location proof.
leave:readLeave requests and leave balances.
pay:readPay run totals. Never tax file numbers or bank details.
webhooks:manageCreate, list and remove webhook endpoints.

Pages, limits and errors

Pagination

Lists return data and nextCursor. Pass the cursor back as cursor for the next page; it is null on the last one. limit is 1 to 100 and defaults to 50. Cursors are stable: rows added while you page never shift a later page.

{
  "data": [ { "id": "...", "name": "Sam Lee", "status": "active" } ],
  "nextCursor": "eyJsIjoicGVvcGxlIi..."
}

Rate limits

Each key may make 120 requests a minute. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset. Over the limit you get 429 with Retry-After in seconds.

Errors

Every error is JSON with a sentence for people and a stable code for your code. Validation errors add fields.

{
  "error": "This key lacks the people:read scope.",
  "code": "insufficient_scope",
  "requiredScope": "people:read"
}

Idempotent writes

Send an Idempotency-Key header on every write (it is required when creating a clock entry). A retry with the same key and body within 24 hours returns the first response with Idempotent-Replayed: true. The same key with a different body is refused with 422.

Shapes

Fields are only ever added, never renamed or removed. Money is integer cents, instants are ISO 8601 in UTC and calendar days are YYYY-MM-DD in the workspace time zone.

Endpoints

RequestWhat it doesScope
get /meCheck a key. The workspace a key belongs to and the scopes it holds. Any valid key may call it.Any key
get /peopleList people.people:read
get /people/{id}Get a person.people:read
get /sitesList sites.people:read
get /clock-entriesList clock entries. Newest first. Locations are never included.time:read
post /clock-entriesCreate a clock entry. Records a clock in or out for a person at a site. Entries from the API carry no location proof, so they arrive for review. Send an `Idempotency-Key` header: a retry with the same key and body returns the first result.clock_entries:write
get /timesheetsList approved timesheet weeks. Approved weeks only, newest first, with the week’s totals in minutes.time:read
get /shiftsList published shifts.time:read
get /leave-requestsList leave requests. Newest first. Reasons and attached documents are never included.leave:read
get /pay-runsList pay run summaries. Finalised and reversed runs, newest first. Totals only: no payslips, tax file numbers or bank details.pay:read
get /webhook-endpointsList webhook endpoints.webhooks:manage
post /webhook-endpointsCreate a webhook endpoint. The response carries the signing secret once. Store it then.webhooks:manage
delete /webhook-endpoints/{id}Delete a webhook endpoint.webhooks:manage

Creating a clock entry

Entries made through the API carry no location proof, so they are recorded for a manager to review. externalId is your own id: sending it again returns the first entry instead of a second one. recordedAt may be up to seven days in the past.

curl -X POST https://app.loggeriq.io/api/v1/clock-entries \
  -H "Authorization: Bearer liq_sk_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1c2b7e-door-17-0830" \
  -d '{
    "personId": "usr_...",
    "siteId": "3f2a...",
    "type": "in",
    "recordedAt": "2026-10-03T08:30:00Z",
    "externalId": "door-17-0830"
  }'

Pay run summaries carry totals only. No tax file numbers, bank details or payslips ever leave through the API.

Webhooks

Add an endpoint in Settings, API and webhooks (or with the webhook-endpoints API) and choose its events. We send a POST with a JSON body to your https address when one happens. Answer with any 2xx within ten seconds.

EventSent when
clock_entry.createdSomeone clocked in or out, from any device or the API.
timesheet.approvedA person’s week was approved by a manager or approved itself.
leave.decidedA leave request was approved, declined or cancelled.
expense.decidedAn expense claim was approved or declined.
pay_run.finalisedA pay run was finalised. Totals only.
person.joinedSomeone joined the workspace.
person.leftSomeone left the workspace.
{
  "id": "0b5f9f8e-6c1d-4f8e-9a51-2d1f0c7e9b11",
  "type": "leave.decided",
  "createdAt": "2026-10-03T09:12:44.120Z",
  "workspaceId": "11111111-...",
  "data": {
    "id": "...", "personId": "...", "type": "annual",
    "startDate": "2026-10-20", "endDate": "2026-10-24",
    "status": "approved", "decidedAt": "2026-10-03T09:12:44.000Z"
  }
}

data has the same shape the matching API endpoint returns. Each delivery also carries LoggerIQ-Event, LoggerIQ-Event-Id and LoggerIQ-Delivery headers.

Retries

A delivery that fails or times out is retried after about 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours: eight attempts over about a day and a half. Redirects are not followed. The same event can arrive more than once, so dedupe on the event id. Each endpoint keeps a delivery log you can read in settings, where you can also send a test event.

Verifying signatures

Every delivery is signed with the endpoint’s signing secret, shown once when the endpoint is made. The LoggerIQ-Signature header looks like t=1759482764,v1=5d41..., where v1 is the hex HMAC-SHA256 of the timestamp, a full stop and the raw body. Check it against the raw bytes before you parse the JSON, compare in constant time and refuse a timestamp more than 5 minutes from your clock.

Node.js

import crypto from 'node:crypto';

// rawBody: the request body exactly as received, before any JSON parsing.
export function verifyLoggerIQ(rawBody, header, secret, toleranceSeconds = 300) {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSeconds) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  const want = Buffer.from(expected, 'hex');
  return given.length === want.length && crypto.timingSafeEqual(given, want);
}

Python

import hashlib, hmac, time

def verify_loggeriq(raw_body: bytes, header: str, secret: str, tolerance: int = 300) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    t = int(parts.get("t", "0"))
    if abs(time.time() - t) > tolerance:
        return False
    signed = f"{t}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

If a secret leaks, replace it from settings. The old one stops working at once.

AI assistants

AI assistants that support the Model Context Protocol can read a workspace through https://app.loggeriq.io/api/mcp, authenticated with an API key. The connection is read-only and an assistant sees only the tools its key’s scopes allow.

ToolWhat it answersScope
list_peopleList people in the workspace with their role, status and sites.people:read
get_timesheet_weekOne person’s week: minutes worked each day, overtime, shortfall and whether it is approved.time:read
list_needs_youWhat is waiting on a manager: timesheet weeks with exceptions, leave and expense claims to decide.time:read
clock_summaryWho is clocked in now and how many clock entries a day had, by site.time:read
leave_balancesA person’s leave balances: entitlement, used and still bookable, in days.leave:read

Most assistants take a configuration like this:

{
  "mcpServers": {
    "loggeriq": {
      "type": "http",
      "url": "https://app.loggeriq.io/api/mcp",
      "headers": { "Authorization": "Bearer liq_sk_..." }
    }
  }
}

Questions about the API? Talk to us.