Skip to content
Hoursmith Docs
API

Common workflows

End-to-end Hoursmith API examples — create a client and project, log time, and read back invoices.

These walkthroughs stitch the endpoints together into real tasks. They assume a $TOKEN environment variable holding your API token and use the base URL https://my.hoursmith.app/api/v1.

Set up a client and project

# 1. Create a client (POST requires an Idempotency-Key)
curl https://my.hoursmith.app/api/v1/clients \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Globex", "email": "billing@globex.com", "currency": "USD" }'

# Response: { "data": { "id": "cl_...", "name": "Globex", ... } }

# 2. Create an hourly project for that client
curl https://my.hoursmith.app/api/v1/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "clientId": "cl_...", "name": "Website redesign" }'

See the exact accepted fields on Create a client and Create a project.

Create or reconcile a fixed-fee project

Fixed fee projects require a contract value, do not use hourlyRate, and may have an optional budgetHours effort cap:

curl https://my.hoursmith.app/api/v1/projects \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "cl_...",
    "name": "Brand launch",
    "rateType": "FIXED",
    "fixedFeeAmount": "12000.00",
    "budgetHours": "160.00"
  }'

New fixed-fee projects initialize with no prior billing. To make an older or imported fixed-fee project invoiceable, explicitly reconcile what was invoiced before the project-linked ledger began:

curl -X PATCH https://my.hoursmith.app/api/v1/projects/pr_... \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "fixedFeePriorInvoicedAmount": "2500.00" }'

Use "0.00" when nothing was invoiced previously. Member responses omit this field and other project money/reconciliation fields.

Log time

The canonical way to record time is durationSeconds. You can also send a human duration string like "1h30m" or "1:30".

curl https://my.hoursmith.app/api/v1/time-entries \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "pr_...",
    "entryDate": "2026-06-11",
    "durationSeconds": 5400,
    "note": "Homepage layout",
    "billable": true
  }'
await fetch('https://my.hoursmith.app/api/v1/time-entries', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.HOURSMITH_API_TOKEN}`,
    'Idempotency-Key': crypto.randomUUID(),
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    projectId: 'pr_...',
    entryDate: '2026-06-11',
    duration: '1h30m',
    note: 'Homepage layout',
  }),
});

billable: true is effective only on an Hourly project. Fixed fee and Non-billable project policy stores new task/time work as non-billable, and their list/get responses and filters report it that way even for immutable historical rows.

Log time by start and end instead

If you know when the work happened rather than how long it took, send a startTime and endTime pair as 24-hour "HH:mm" and skip the duration entirely — Hoursmith works it out:

curl https://my.hoursmith.app/api/v1/time-entries \
  -H "Authorization: Bearer $TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "projectId": "pr_...",
    "entryDate": "2026-07-29",
    "startTime": "09:00",
    "endTime": "10:30",
    "note": "Homepage layout"
  }'

A few rules worth knowing:

  • Send both or neither. A start with no end is what a running timer looks like, so it's rejected.
  • Don't also send a duration. With both times present the duration is derived from them; a duration that contradicts them is an error rather than a silent overwrite.
  • The times are wall-clock in the entry owner's timezone.
  • An end at or before the start means the work ran past midnight.
  • On PATCH, omit both to leave existing times alone, or send null for both to clear them.

Check the running timer

curl "https://my.hoursmith.app/api/v1/time-entries?running=true&limit=1" \
  -H "Authorization: Bearer $TOKEN"

Every time entry carries an isRunning flag — that, not stoppedAt === null, is how you tell a live timer from a finished entry. A manual entry can now carry a start and end time of its own, so a present startedAt no longer means "still running".

Pull a month of time for a project

curl "https://my.hoursmith.app/api/v1/time-entries?projectId=pr_...&entryDateFrom=2026-06-01&entryDateTo=2026-06-30&sort=-entryDate&limit=200" \
  -H "Authorization: Bearer $TOKEN"

Then page with the nextCursor until it's null.

Find work done in a specific window

entryDate is the day an entry is filed under; startedAt is when the work actually began. To ask "what did I work on yesterday morning?", filter on startedAt:

curl "https://my.hoursmith.app/api/v1/time-entries?startedAtFrom=2026-07-28T09:00:00Z&startedAtTo=2026-07-28T12:00:00Z&sort=startedAt" \
  -H "Authorization: Bearer $TOKEN"

Entries logged as a plain duration have no start time and are left out of any startedAt range.

Find unpaid invoices

Invoices are read-only over the API. List the sent ones and check each invoice's paymentStatus field (unpaid, partially_paid, paid, refunded):

curl "https://my.hoursmith.app/api/v1/invoices?status=sent&sort=-issueDate" \
  -H "Authorization: Bearer $TOKEN"
const res = await fetch('https://my.hoursmith.app/api/v1/invoices?status=sent', {
  headers: { Authorization: `Bearer ${token}` },
});
const { data } = await res.json();
const unpaid = data.filter((inv) => inv.paymentStatus !== 'paid');

Creating and sending invoices stays in the app (or happens automatically as you bill tracked time). The API gives you read access to the results so you can sync them into your own reporting.

Tips for robust integrations

  • Generate one Idempotency-Key per logical write and reuse it on retries.
  • Handle 429 with Retry-After, and 409 for locked records.
  • Store ids you create (client, project) so later calls can reference them.
Was this page helpful?

On this page