Periods and reports

List periods, preview and close a period, and run time reports through the SAQ REST API; these operations are browser-session only.

Written for
integration-developer
Roles
owner, admin, member
Requires
Browser session (no API token reaches these paths); Periods: Owner or Admin; Close: recent re-authentication and typed confirmation; Reports: any workspace user
Feature
reports

A period is a calendar month or a Monday-based week in the workspace timezone. Closing a period is the only lock on time: there is no submit-and-approve workflow. None of the four operations on this page is in the API token allow-list; a token gets 403 forbidden ("This operation is not available to API keys") on all of them, and the MCP close_period tool always refuses. Reports are available to every workspace user in the app; integrations that need report figures read time entries instead.

GET /api/w/{slug}/periods

Periods from a start date onward, created on demand with gaps filled. Token: no (browser session only). Owner or Admin.

Query parameter (PeriodQuery) Type Meaning
from YYYY-MM-DD Optional start of the range

Response 200 (PeriodList)

{
  "periods": [
    {
      "id": "01924c3e-0008-7000-8000-000000000008",
      "periodStart": "2026-08-01",
      "periodEnd": "2026-08-31",
      "closedAt": "2026-09-02T07:40:00.000Z",
      "closedBy": "01924c3e-0004-7000-8000-000000000001"
    },
    {
      "id": "01924c3e-0008-7000-8000-000000000009",
      "periodStart": "2026-09-01",
      "periodEnd": "2026-09-30",
      "closedAt": null,
      "closedBy": null
    }
  ],
  "firstPeriodStart": "2026-08-01",
  "suggestedFirstPeriodStart": "2026-08-01"
}

firstPeriodStart is null until the first close fixes it; suggestedFirstPeriodStart is the earliest calendar boundary that includes all logged work.

GET /api/w/{slug}/periods/{id}/preview

The pre-close checklist for a period. Token: no (browser session only). Owner or Admin. The checks are advisory; a close is never blocked by them.

Response 200 (ClosePreview)

Field Type Meaning
period Period The period
firstPeriodStart date The first period start in force, or the suggestion for a first close
firstClose boolean Whether this would be the workspace's first close
checks[] object key, available, items[] (label, optional ticketRef, actual, expected)
checks[].key What it lists
targets People below their weekly target
moved Tickets moved or re-attributed after time was logged
wip Older billable work on open on_close tickets
banks Open on_close tickets that resolve to a time bank
rates Entries with no applicable rate

Errors: forbidden 403, not_found 404.

POST /api/w/{slug}/periods/{id}/close

Close the period. Token: no (browser session only). Owner or Admin with a re-authentication in the last ten minutes. Periods close in order, the close is irreversible, and the first close fixes firstPeriodStart and freezes the workspace timezone and period length.

Body field (CloseInput) Type Required Meaning
confirmation string (≤64) yes Must equal the period's periodStart
firstPeriodStart YYYY-MM-DD first close only Must be a calendar boundary that does not exclude logged work
curl -X POST https://saq.no/api/w/acme/periods/01924c3e-0008-7000-8000-000000000009/close \
  -H 'Cookie: <browser session>' \
  -H 'Content-Type: application/json' \
  -d '{ "confirmation": "2026-09-01" }'

Response 200 (Period) with closedAt and closedBy set.

Error Status details.reason
reauth_required 403 No recent re-authentication
forbidden 403 Not Owner or Admin, or an API token
invalid 400 confirm_period_start, period_boundary, period_excludes_work, period_start_range
conflict 409 period_already_closed; close_earlier_first (with nextPeriodStart)
not_found 404 Unknown period

GET /api/w/{slug}/reports

Aggregate time over a range. Token: no (browser session only). Any workspace user; rows only include tickets the caller may see. Budget: 30 requests per minute per identity.

Query parameter (ReportQuery) Type Default Meaning
from, to YYYY-MM-DD required Inclusive range, at most 366 days
groupBy person, project, ticket, client, period, category, outcome project Row grouping; ticket rolls up through visible ancestors
billable all, yes, no all Filter by the billable flag
view current, closed current Live facts, or the facts as snapshotted at period close
wip true, false false Include work in progress on open on_close tickets

Response 200 (Report)

{
  "rows": [
    {
      "id": "01924c3e-6b2a-7d11-9d0e-3a1f4b5c6d7e",
      "label": "Website",
      "minutes": 1260,
      "adjustments": -30,
      "billableMinutes": 1140,
      "unbilledMinutes": 300,
      "estimateMinutes": 1500,
      "includesDescendants": false,
      "chargedMinutes": 720,
      "bankMinutes": 120,
      "freeMinutes": 0,
      "money": [
        {
          "currency": "NOK",
          "chargedAmount": 1080000,
          "notionalAmount": 1260000
        }
      ]
    }
  ],
  "totalMinutes": 1260
}
Field Meaning
minutes, adjustments Logged minutes and the signed sum of corrections
billableMinutes, unbilledMinutes Billable minutes, and those no billing run has allocated yet
estimateMinutes Estimate for the group, or null
includesDescendants The ticket grouping folded children into this row
chargedMinutes, bankMinutes, freeMinutes Billed minutes by outcome
money[] Per currency: chargedAmount and notionalAmount (every billed minute at its rate) in integer minor units; never summed across currencies
Error Status details.reason
invalid 400 range_limit
forbidden 403 Client user, or an API token
rate_limited 429 More than 30 requests per minute

There is no report export endpoint; download client timesheets from Documents instead.