| Daily View | Monthly Overview |
|---|---|
![]() |
![]() |
| Track your daily tasks with real-time timers | Visual monthly overview with calendar grid |
- OIDC Authentication: Secure login with any OIDC provider (Keycloak, Auth0, Google, etc.)
- Billable Tracking: Mark time entries as billable/non-billable with visual indicators
- Kimai JSON export: Times can be exported as Kimai-compatible JSON (see Importing to Kimai)
- Billable/Unbillable: Each time entry has a billable checkbox
- API keys & CLI: Personal API keys (profile page) unlock a JSON API and the
punchCLI for starting/stopping timers and exporting timesheets from the terminal or scripts
All time entries (both billable and non-billable) are included in exports with correct status. Unbillable don't count towards the monthly and weekly total in the UI.
Punchcard is configured via environment variables (.env file will be
automatically loaded).
PORT=8080 # Server port (default: 8080)
DATABASE_URL=punchcard.db # Database file path (default: punchcard.db)
OIDC_ISSUER_URL=https://your-provider.com # OIDC provider URL
OIDC_CLIENT_ID=your-client-id # OAuth client ID
OIDC_CLIENT_SECRET=your-client-secret # OAuth client secret
OIDC_REDIRECT_URL=http://domain.tld/callback # OAuth redirect URLFor local development you can bypass OIDC entirely:
PUNCHCARD_DEV_USER=dev@example.com # Log every request in as this user (never use in production)Create an API key on your profile page (API Keys section), then configure
the CLI once. The key is never passed as a command line argument; use a
password manager command or the PUNCHCARD_TOKEN environment variable:
punch setup --url https://punchcard.example.com --token-command "pass show punchcard"
# or: PUNCHCARD_TOKEN=pc_... punch setup --url https://punchcard.example.comDay-to-day usage:
punch start "write report" # start a timer (stops any other running timer)
punch status # show running timer and today's totals
punch stop # stop the running timer
punch entries --from 2026-03-01 --to 2026-03-31 # list entries (with IDs)
punch edit 42 --duration 1:30 --billable false # fix up an entry
punch delete 42 # delete an entry
punch export --month 2026-03 -o march.json # Kimai-compatible exportEvery command accepts --json for stable, machine-readable output, which also
makes the CLI easy to drive from scripts and LLM agents. Configuration lives in
~/.config/punchcard/config.json; the environment variables PUNCHCARD_URL
and PUNCHCARD_TOKEN override it.
All endpoints live under /api/v1 and authenticate with
Authorization: Bearer <api-key>:
| Endpoint | Description |
|---|---|
GET /api/v1/status |
Running timer and today's totals |
POST /api/v1/timers/start |
Start a timer: {"description": "task", "date": "2026-03-02"} (date optional) |
POST /api/v1/timers/stop |
Stop all running timers |
GET /api/v1/entries?from=YYYY-MM-DD&to=YYYY-MM-DD |
List entries (default: today) |
PATCH /api/v1/entries/{id} |
Update description, duration, billable, and/or date |
DELETE /api/v1/entries/{id} |
Delete an entry (returns it as a receipt) |
GET /api/v1/export?month=YYYY-MM |
Kimai-compatible JSON export (default: current month) |
Download the JSON export from the punchcard UI (daily or monthly), then import each entry to Kimai using curl:
export KIMAI_API_TOKEN="<your token>"
export KIMAI_INSTANCE="https://your-kimai-instance"
export PUNCHCARD_EXPORT="punchcard-export-2026-03.json"
jq -c '.[]' $PUNCHCARD_EXPORT | while IFS= read -r entry; do
curl -s -X POST "$KIMAI_INSTANCE/api/timesheets" \
-H "Authorization: Bearer $KIMAI_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d "$entry"
echo
doneTo create an API token, go to your Kimai profile page → API tab → create a new token.
Note: Kimai has no deduplication - importing the same file twice will create duplicate entries. The
billablefield requires theedit_billablepermission in Kimai; if your user lacks it, strip the field withjq -c '.[] | del(.billable)'instead.

