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
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"])

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.1.1

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.1.1
File Size Uploaded
clockster-0.1.1.tar.gz 110.0 kB Details

Built distribution (wheel)

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

Total release size: 158.5 kB

Release files / clockster-0.1.1.tar.gz

Download URL clockster-0.1.1.tar.gz
Size 110.0 kB
Tags Source
SHA-256 checksum
How to use checksums
3e862024734a697f589b2b646d2e7e0fdd83e18ebab8b70d9857074850820633
BLAKE2b-256 checksum
How to use checksums
21d04450181fe306cb03ad5daafed55547eae13bafe34e10369c8906e6107c7b
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 Aug 16, 2026.

Transparency log

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

Download URL clockster-0.1.1-py3-none-any.whl
Size 48.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
03372144761f0db696f1a3949019d98b7bbe2d7b3b99ea3ee6a448f29992e729
BLAKE2b-256 checksum
How to use checksums
8e046377b30cc64762d75bd123c03482a3060164f234a570d530bfcc94d1c695
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 Aug 16, 2026.

Transparency log

Release history Release notifications | RSS feed

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

This release

0.1.1 This release

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