REST API

Base URL https://app.sumhour.com/api/v1. JSON in, JSON out, camelCase names. The full machine-readable description is openapi.json.

Authentication

Send a personal API token from API tokens in the app:

Authorization: Bearer sh_pat_...

A token acts as its owner in one workspace. It reaches what the owner's role allows, narrowed by the token's permissions. A missing permission is 403 insufficient_scope; a role that is too low is 403 forbidden.

Responses and errors

  • One result: { "data": { ... } }. A list with pages: { "data": [ ... ], "next_cursor": "…" | null }; pass cursor back to get the next page.
  • Errors: { "error": { "code": "...", "message": "...", "details": { "field": "why" } } } with status 400, 401, 403, 404, 405, 409, 422 or 429. The message is written for people; show it as is.
  • Every response has a Sumhour-Request-Id header. Quote it when asking for help.

Retries and limits

  • Send Idempotency-Key: <any unique string> on writes. Repeating the same request with the same key returns the first response (header Idempotent-Replayed: true) instead of doing it twice; the same key with a different body is 422. Keys are kept 24 hours.
  • 120 requests a minute per token. Over that you get 429 with Retry-After, plus RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset on every response.
  • Durations accept 1:30, 1.5h, 90m or seconds. Dates are YYYY-MM-DD in the person's timezone.

Account

Who am I

GET /api/v1/me

The signed-in person, the workspace, their role, timezone, week start and currency, and the scopes this caller has.

No parameters.

curl "https://app.sumhour.com/api/v1/me" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Projects and clients

List projects

GET /api/v1/projects · permission projects:read

Active projects the caller can log time to (everything for admins), each with its client, code, tasks and whether notes are required. Use the ids when logging time.

Name In Type Required Notes
q query string Filter by project, client or code
curl "https://app.sumhour.com/api/v1/projects" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

A project's tasks

GET /api/v1/projects/{projectId}/tasks · permission projects:read

The tasks of one project the caller can log time to, for picking a task when logging time. Make and other tools fill their task lists from it.

Name In Type Required Notes
projectId path id yes
curl "https://app.sumhour.com/api/v1/projects/<projectId>/tasks" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

List clients

GET /api/v1/clients · permission projects:read

Active clients with their billing currency.

No parameters.

curl "https://app.sumhour.com/api/v1/clients" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

List people

GET /api/v1/people · permission team:read

Everyone in the workspace with their role.

No parameters.

curl "https://app.sumhour.com/api/v1/people" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Add a client

POST /api/v1/clients · permission projects:write

Adds a client with its billing currency. Owners and admins only.

Name In Type Required Notes
name body string yes
currency body string Three-letter code; defaults to the workspace currency
billingEmail body string or null
curl -X POST "https://app.sumhour.com/api/v1/clients" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"…"}'

Add a project

POST /api/v1/projects · permission projects:write

Adds an hourly, fixed-fee or non-billable project for a client. Owners and admins only; retainers and budgets are set up in the app.

Name In Type Required Notes
clientId body id yes
name body string yes
code body string
type body hourly, fixed_fee, non_billable default "hourly"
hourlyRate body string One rate for the whole project, hourly projects
fixedFee body string The fee, fixed-fee projects
curl -X POST "https://app.sumhour.com/api/v1/projects" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"clientId":"<clientId>","name":"…"}'

Time

Running timer

GET /api/v1/timer · permission time:read

The caller's running timer, or null when nothing runs.

No parameters.

curl "https://app.sumhour.com/api/v1/timer" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Start a timer

POST /api/v1/timer/start · permission time:write

Start the caller's timer on a project (stopping any running one), or resume an existing entry from today by passing entryId.

Name In Type Required Notes
projectId body id
taskId body id or null
notes body string or null
entryId body id Resume this entry instead of starting a new one
billable body boolean Override the project's billable default
startedAt body string When the timer was started, for a start made offline (within the last 12 hours)
source body object
curl -X POST "https://app.sumhour.com/api/v1/timer/start" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"notes":"Kickoff call"}'

Stop the timer

POST /api/v1/timer/stop · permission time:write

Stop the caller's running timer. Returns the stopped entry, or null when nothing was running.

Name In Type Required Notes
entryId body id Stop only this entry; 409 timer_not_running if it already stopped elsewhere
stoppedAt body string When it was stopped, for a stop made offline
curl -X POST "https://app.sumhour.com/api/v1/timer/stop" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json"

List time entries

GET /api/v1/entries · permission time:read

Time entries between two dates (default: this week), newest first. Managers and admins can pass userId for someone else, or userId=all.

Name In Type Required Notes
from query date
to query date
userId query string A person's id, or "all"; defaults to the caller
projectId query id
limit query integer default 100
cursor query string
curl "https://app.sumhour.com/api/v1/entries" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Log time

POST /api/v1/entries · permission time:write

Log a finished block of time for the caller. Give a duration like "1:30" or "1.5h" (or seconds). Workspace rules apply: required notes or tasks, locked weeks, project membership.

Name In Type Required Notes
projectId body id yes
taskId body id or null
date body date Defaults to today in the caller's timezone
duration body string
seconds body integer
notes body string or null
billable body boolean Defaults to the project's setting
curl -X POST "https://app.sumhour.com/api/v1/entries" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"<projectId>","duration":"1:30","notes":"Kickoff call"}'

Change a time entry

PATCH /api/v1/entries/{entryId} · permission time:write

Change an entry's project, task, date, duration, notes or billable flag. Only the fields you pass change. Locked or approved time cannot change.

Name In Type Required Notes
entryId path id yes
projectId body id
taskId body id or null
date body date
duration body string
seconds body integer
notes body string or null
billable body boolean
curl -X PATCH "https://app.sumhour.com/api/v1/entries/<entryId>" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"duration":"1:30","notes":"Kickoff call"}'

Delete a time entry

DELETE /api/v1/entries/{entryId} · permission time:write · deletes data

Delete one of the caller's entries (admins: anyone's). Locked or invoiced time cannot be deleted.

Name In Type Required Notes
entryId path id yes
curl -X DELETE "https://app.sumhour.com/api/v1/entries/<entryId>" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

A week's timesheet

GET /api/v1/timesheets/{weekStart} · permission time:read

The caller's week: status (open, submitted, approved, rejected, locked), hours per day and the total. weekStart may be any date in the week.

Name In Type Required Notes
weekStart path date yes
curl "https://app.sumhour.com/api/v1/timesheets/2026-09-21" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Submit a week

POST /api/v1/timesheets/{weekStart}/submit · permission time:write

Submit the caller's week for approval. Stop any running timer first.

Name In Type Required Notes
weekStart path date yes
curl -X POST "https://app.sumhour.com/api/v1/timesheets/2026-09-21/submit" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json"

Expenses

Expense categories

GET /api/v1/expense-categories · permission expenses:read

Expense categories, some with a fixed unit price (mileage and similar).

No parameters.

curl "https://app.sumhour.com/api/v1/expense-categories" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

List expenses

GET /api/v1/expenses · permission expenses:read

Expenses between two dates (default: this month). People see their own; owners and admins can pass userId, or leave it out for everyone.

Name In Type Required Notes
from query date
to query date
projectId query id
userId query string
curl "https://app.sumhour.com/api/v1/expenses" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Add an expense

POST /api/v1/expenses · permission expenses:write

Add an expense for the caller on a project. Receipts are uploaded in the app. For a unit-priced category pass units instead of amount.

Name In Type Required Notes
projectId body id yes
categoryId body id or null
date body date
amount body string
units body string
notes body string or null
billable body boolean default true
reimbursable body boolean default false
curl -X POST "https://app.sumhour.com/api/v1/expenses" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"projectId":"<projectId>","amount":"42.00","notes":"Kickoff call"}'

Invoices

List invoices

GET /api/v1/invoices · permission invoices:read

Invoices, newest first, optionally by status (draft, sent, viewed, paid, void) or client.

Name In Type Required Notes
status query draft, sent, viewed, paid, void
clientId query id
curl "https://app.sumhour.com/api/v1/invoices" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

An invoice

GET /api/v1/invoices/{invoiceId} · permission invoices:read

One invoice with its lines and payments.

Name In Type Required Notes
invoiceId path id yes
curl "https://app.sumhour.com/api/v1/invoices/<invoiceId>" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Draft an invoice

POST /api/v1/invoices · permission invoices:write

Draft an invoice for a client from uninvoiced time (and expenses) up to a date. Nothing is sent; the draft opens in the app for review.

Name In Type Required Notes
clientId body id yes
projectIds body list of id Default: all the client's projects
from body date
to body date Default: today
groupBy body task, person, date, project, entry default "task"
includeExpenses body boolean default true
showNotes body boolean Print each time entry's note on the invoice. Notes are often internal.
curl -X POST "https://app.sumhour.com/api/v1/invoices" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"clientId":"<clientId>"}'

Record a payment

POST /api/v1/invoices/{invoiceId}/payments · permission invoices:write

Record a payment against a sent invoice. The invoice turns paid when fully covered.

Name In Type Required Notes
invoiceId path id yes
amount body string yes
paidOn body date
method body manual, bank, card, other default "bank"
reference body string or null
notes body string or null
curl -X POST "https://app.sumhour.com/api/v1/invoices/<invoiceId>/payments" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amount":"42.00","notes":"Kickoff call"}'

Reports

Run a report

GET /api/v1/reports/{kind} · permission reports:read

Run a report: time, profitability, utilization, invoicing, uninvoiced. Pick a preset (this_month, last_month, this_quarter, last_quarter, this_year, last_year, last_30, last_90) or from/to. Managers see only the projects they manage.

Name In Type Required Notes
kind path time, profitability, utilization, invoicing, uninvoiced yes
preset query this_month, last_month, this_quarter, last_quarter, this_year, last_year, last_30, last_90
from query date
to query date
groupBy query string client, project, task, person, date, week or month (depends on the report)
clientId query id
projectId query id
userId query string
billable query all, billable, nonbillable default "all"
curl "https://app.sumhour.com/api/v1/reports/time" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Approvals

Weeks waiting for review

GET /api/v1/approvals · permission approvals:read

Submitted weeks waiting for approval, with hours, plus people who have not submitted last week.

No parameters.

curl "https://app.sumhour.com/api/v1/approvals" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"

Approve a week

POST /api/v1/approvals/{userId}/{weekStart}/approve · permission approvals:write

Approve a person's submitted week (weekStart may be any date in the week). Approved time can be invoiced and no longer changes.

Name In Type Required Notes
userId path string yes
weekStart path date yes
comment body string or null
curl -X POST "https://app.sumhour.com/api/v1/approvals/…/2026-09-21/approve" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json"

Send a week back

POST /api/v1/approvals/{userId}/{weekStart}/reject · permission approvals:write

Send a submitted week back to the person with a comment saying what to change (weekStart may be any date in the week).

Name In Type Required Notes
userId path string yes
weekStart path date yes
comment body string yes
curl -X POST "https://app.sumhour.com/api/v1/approvals/…/2026-09-21/reject" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"comment":"…"}'

Other

Send events to a URL

POST /api/v1/events/subscriptions · permission events:write

Subscribes a URL to one workspace event (the same events as webhooks), for apps such as Zapier. The URL gets a signed POST each time. Owners and admins only.

Name In Type Required Notes
url body string yes
event body string yes An event name, e.g. "invoice.send"
curl -X POST "https://app.sumhour.com/api/v1/events/subscriptions" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url":"…","event":"…"}'

Stop sending events to a URL

DELETE /api/v1/events/subscriptions/{id} · permission events:write

Removes an event subscription made with events.subscribe.

Name In Type Required Notes
id path id yes
curl -X DELETE "https://app.sumhour.com/api/v1/events/subscriptions/<id>" \
  -H "Authorization: Bearer $SUMHOUR_TOKEN"