A document is a client timesheet produced by a billing run: one per run and format (html, pdf, csv), either combined (one per client, a section per project) or per project, according to the client's timesheet layout. Documents are immutable; a redaction supersedes them with a new version and a voided run voids them.
Who may download follows one rule for every door: Owner and Admin see all; a client user sees their own client's documents only when they are a Billing contact with a full login; a workspace user sees clients they hold a document grant for and projects where they are Project manager. A token restricted to named projects sees only project-scoped documents of those projects.
GET /api/w/{slug}/documents
The documents the caller may download. Token: yes, read.
| Query parameter | Type | Meaning |
|---|---|---|
clientId |
uuid | Only this client |
runId |
uuid | Only this billing run |
Request
curl "https://saq.no/api/w/acme/documents?clientId=01924c3e-0005-7000-8000-000000000001" \
-H 'Authorization: Bearer saq_YOUR_TOKEN'
Response 200 (DocumentList)
{
"documents": [
{
"id": "01924c3e-2b30-7c41-9d52-ae6f7a8b9c0d",
"runId": "01924c3e-2a29-7b40-8c51-9d5e6f7a8b9c",
"number": "ACME-2026-08-001",
"scope": "combined",
"projectId": null,
"projectName": null,
"clientId": "01924c3e-0005-7000-8000-000000000001",
"clientName": "Acme AS",
"periodStart": "2026-08-01",
"format": "pdf",
"version": 1,
"locale": "nb",
"checksum": "sha256:9f2c…",
"bytes": 84213,
"voided": false,
"redacted": false,
"createdAt": "2026-09-02T08:00:00.000Z",
"deliveries": [
{
"email": "invoice@acme.example",
"state": "sent",
"detail": null,
"attemptedAt": "2026-09-02T08:01:00.000Z"
}
]
}
],
"scope": "all"
}
| Field | Type | Meaning |
|---|---|---|
id, runId |
uuid | Document and its billing run |
number |
string | Document number |
scope |
combined or project |
Layout |
projectId, projectName |
uuid, string, or null | Set for project scope |
clientId, clientName |
The billing client | |
periodStart |
date | The billed period |
format |
html, pdf, csv |
Format of this document |
version |
integer | Increases when a redaction supersedes the document |
locale |
string | Language of the document (the client's locale) |
checksum |
string | Content checksum |
bytes |
integer | Size |
voided, redacted |
boolean | Voided with its run; superseded by a redaction |
createdAt |
datetime | |
deliveries[] |
object | email, state (pending, sent, failed, skipped, unknown), detail, attemptedAt per billing recipient |
scope (top level) |
all, client, projects, none |
The widest download rule in force for the caller |
The list is empty with scope: "none" when the caller may download nothing; that is not an error.
GET /api/w/{slug}/documents/{id}/download
The document's bytes. Token: yes, read.
Request
curl -o ACME-2026-08-001.pdf \
https://saq.no/api/w/acme/documents/01924c3e-2b30-7c41-9d52-ae6f7a8b9c0d/download \
-H 'Authorization: Bearer saq_YOUR_TOKEN'
Response 200 with the body and:
| Header | Value |
|---|---|
Content-Type |
text/html; charset=utf-8, application/pdf, or text/csv; charset=utf-8 |
Content-Disposition |
inline for HTML, otherwise attachment; filename="<number>.<format>" |
Cache-Control |
private, no-store |
X-Content-Type-Options |
nosniff |
Content-Security-Policy |
default-src 'none'; style-src 'unsafe-inline'; sandbox |
| Error | Status | details.reason |
|---|---|---|
forbidden |
403 | The caller may download nothing, not this document, or a project-restricted token asked for a combined document |
conflict |
409 | document_voided, document_redacted (download the current version instead) |
not_found |
404 | Unknown id, or the file is not available |
PDF rendering depends on the rendering service being available on the installation; when it is not, HTML and CSV are produced and no pdf document is listed.
Document grants
A grant lets a workspace user download every document of one client, regardless of projects. Token: no (browser session only). Owner or Admin.
| Operation | Body | Response |
|---|---|---|
GET /api/w/{slug}/clients/{id}/document-grants |
200 { "grants": [{ id, identityId, name, email }], "candidates": [{ identityId, name, email }] } |
|
PUT /api/w/{slug}/clients/{id}/document-grants |
{ "identityId": "…", "granted": true } |
200 { "ok": true } |
Errors: forbidden 403 (admin_only), not_found 404.
Time bank balances
There is no token-enabled HTTP endpoint for time bank balances; the bank endpoints under /billing/banks and /clients/{id}/banks are Owner or Admin, browser session only. An integration or assistant with a token reads balances through the MCP tool get_bank_balances; see MCP.