Skip to main content

clockster

Official Python SDK for the Clockster Company API.

Server-to-server client for a company's employees, structure, schedules, attendance, tasks and documents. Typed from the API's OpenAPI document. One dependency, httpx.

pip install clockster

Requires Python 3.10 or newer.

Quickstart

One token authenticates one company. Create it under Settings → API in the web application.

import os

from clockster import Clockster

clockster = Clockster(token=os.environ["CLOCKSTER_TOKEN"])

me = clockster.me()

locations = clockster.locations.upsert({"items": [{"external_id": "HQ", "title": "Head office"}]})

clockster.users.upsert(
    {
        "users": [
            {
                "external_id": "HR-1",
                "first_name": "Aisulu",
                "role": "employee",
                "location_id": locations["data"][0]["id"],
            }
        ]
    }
)

timesheets = clockster.timesheets.list(date_from="2026-08-01", date_to="2026-08-31")

A method answers the parsed body, so rows are response["data"]. Nothing is validated on the way in: the answer is the JSON as it arrived, and a field we add tomorrow reaches your code today.

Options

clockster = Clockster(
    token,
    base_url="https://demo.clockster.com",  # a demo stand instead of production
    timeout=60.0,                           # seconds, applied to each request
    user_agent="acme-hr/1.4",               # names your integration in our request log
    client=recording,                       # your own httpx client, yours to close
)

Requests carry clockster-python/<version> unless user_agent says otherwise, so our request log shows which client made a call. The token is read per request, so rotating it does not require a new client.

Refusals

A refusal is raised, never returned.

from clockster import ClocksterError, RateLimitError, ValidationError

try:
    clockster.users.upsert({"users": [{"first_name": "Aisulu"}]})
except ValidationError as error:
    print(error.code, error.errors)  # validation_failed {'users.0.role': [...]}
except RateLimitError as error:
    print(error.retry_after)  # seconds, from Retry-After
except ClocksterError as error:
    print(error.code, error.request_id)

code is what to branch on; message is for a log; quote request_id when asking us about a call. AuthenticationError, ForbiddenError, NotFoundError, ConflictError, ValidationError, RateLimitError and ServerError all descend from ClocksterError, so catching that one catches everything.

What is available

Group Operations
me() —
users list get upsert dismiss
locations, departments, positions, user_filters list get upsert delete
schedules create get delete
attendance list record
timesheets list
tasks list get upsert
documents list get upsert delete
files upload
payroll.payslips list
payroll.single_adjustments list create delete
user_requests list get
webhooks list get create update delete rotate_secret
webhooks.deliveries list get redeliver
webhooks.events list

Paging

Listings are cursor-paged. paginate walks the pages and yields the rows:

from clockster import paginate

for user in paginate(clockster.users.list, per_page=100):
    print(user["external_id"] or user["id"])

Filters go where you would put them anyway; the cursor is the helper's business:

for mark in paginate(clockster.attendance.list, date_from="2026-08-01", date_to="2026-08-31"):
    print(mark["id"])

A refused page raises where it was refused — a half-read listing is not a result. A cursor is bound to the filters it was issued under; change them and start again.

Listings answer oldest first, so a first call on a long-lived company lands years back. Ask with updated_since when you want recent activity rather than all of it.

Relations

A related object is absent unless include names it, and its type says so: location on an employee, user on a mark.

users = clockster.users.list(include=["location", "department"])

print(users["data"][0]["location"]["title"])

Every list parameter takes a list and travels comma-separated: include, ids, locations and the rest.

An absent key is not the same as a null one. null means we know the value is empty; absent means you did not ask.

Types

The shapes are TypedDicts, so a type checker sees the fields of a row while your code keeps plain dictionaries:

from clockster.models import UsersListRow

user: UsersListRow
for user in paginate(clockster.users.list):
    print(user["first_name"])

Sets of values

Where a field takes one of a fixed set, the set is a named Literal:

from clockster.models import UsersRole, UsersStatus

clockster.users.upsert({"users": [{
    "external_id": "HR-1",
    "first_name": "Aisulu",
    "role": "employee",     # a checker reads this against UsersRole
    "location_id": 3,
}]})

clockster.users.list(status="active", include=["location"])

A checker completes the value and refuses a typo, and nothing has to be unwrapped to send one — the values are the type. get_args(UsersRole) answers them all where a check against a file or a dropdown wants them.

Every set is on something you send, and none is in an answer. That is deliberate on the API's part: a status we start answering with next year reaches your code as the string it is, where a closed type would have refused it. So write against the set and read whatever arrives.

Async

AsyncClockster mirrors the whole surface.

from clockster import AsyncClockster, paginate_async

async with AsyncClockster(token=os.environ["CLOCKSTER_TOKEN"]) as clockster:
    timesheets = await clockster.timesheets.list(date_from="2026-08-01", date_to="2026-08-31")

    async for user in paginate_async(clockster.users.list, per_page=100):
        print(user["id"])

Webhooks

verify_webhook takes the body as received and answers the event, so the only path to the event runs through the check.

from clockster import WebhookVerificationError, verify_webhook


@app.post("/clockster")
async def receive(request: Request) -> Response:
    try:
        event = verify_webhook(
            body=await request.body(),
            signature=request.headers.get("X-Clockster-Signature"),
            timestamp=request.headers.get("X-Clockster-Timestamp"),
            secret=os.environ["CLOCKSTER_WEBHOOK_SECRET"],
        )
    except WebhookVerificationError:
        return Response(status_code=400)

    queue.put(event)

    return Response(status_code=202)
  • Pass the raw bytes. Re-serialising a parsed object does not reproduce what was signed.
  • Answer 2xx quickly and do the work afterwards; a timeout is retried.
  • Deduplicate on id. The same event may arrive twice.

Deliveries older than five minutes are refused as replays; tolerance_seconds changes that.

Versioning

Semver, independent of the API version. This package targets Company API v3; a new API version is a major release here, not a second package.

Examples

Two integrations of the shape most of them have, in examples: roster_sync.py writes a roster in from a CSV and dismisses whoever is no longer in it, timesheet_export.py reads a month out as CSV. Both are single files that use the package as published.

Development

src/clockster/_generated is produced from openapi/company-v3.json and committed, so an API change appears in review as the lines of the client it moves.

make spec        # refresh the specification from the deployed API
make generate    # regenerate the client from it
make check       # ruff, mypy and the tests

CI checks that the committed client is what the committed specification produces, and that every operation is reachable on it. Drift against the deployed API is checked nightly.

Licence

MIT. See LICENSE.

Release files for clockster 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for clockster 0.4.0
File Size Uploaded
clockster-0.4.0.tar.gz 121.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for clockster 0.4.0
File Interpreter ABI Platform
clockster-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 173.3 kB

Release files / clockster-0.4.0.tar.gz

Download URL clockster-0.4.0.tar.gz
Size 121.4 kB
Tags Source
SHA-256 checksum
How to use checksums
eaced87091b19dcd1809ef1a16bc42788b7f83ce2a737c9e829055a33a62da49
BLAKE2b-256 checksum
How to use checksums
41aac0c761bb6bd9e7d167308e531d1e99d793e44a410d5831ab8f3ed97c32e9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / clockster-0.4.0-py3-none-any.whl

Download URL clockster-0.4.0-py3-none-any.whl
Size 52.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ea83efee8fb821bbf4e3ab8355efb16e9de0b57a355787e3dc92ea1266d22d72
BLAKE2b-256 checksum
How to use checksums
4304abe30af4ef7342a51d36664f1882eb2d3a9d6edb11065738b90659a00324
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page