---
title: "List Documents"
method: GET
path: "/api/documents"
tags: ["documents"]
---

# List Documents

`GET /api/documents`

## Request

- Base URL: `https://zsign.io` (the document declares no server; this is the origin it was published from)
- URL: `https://zsign.io/api/documents`
- Auth: HTTP bearer

List documents (signing sessions) with filtering, search, sorting,
and status facets computed over the ENTIRE matching data set, not just
the current page.

Query params:
  offset, limit -- pagination over the status-filtered result set.
  status -- exact SessionStatus value, the legacy "cancelled" alias
    (widened to voided+declined; see _status_clause), or the
    list-only owner-draft keys `draft` / `ready_to_send`.
  recipient -- substring match against recipient email (legacy filter,
    unchanged scope: email only, not name).
  date_from, date_to -- ISO-8601, inclusive, filtering created_at. A
    value that fails to parse is a 422, not a silently-ignored filter.
  q -- substring search across document name, document filename,
    recipient name, and recipient email (case-insensitive; `%`/`_` in
    the term are escaped so they match literally rather than acting as
    SQL wildcards). Also matches against the session/document ids, but
    ONLY when `q` looks like it could plausibly be one -- see
    _looks_like_id -- since casting a uuid column to text to ILIKE it
    is unindexable: Postgres has to evaluate that cast-and-compare on
    every row the join produces, on every request, whether or not `q`
    could ever match an id. Dropping the two id branches for ordinary
    text queries measurably cuts per-request time (~45-50% in an
    EXPLAIN ANALYZE repro over 60k seeded sessions); it does NOT turn
    the underlying Seq Scans on `documents`/`session_recipients` into
    index scans -- those columns have no index that supports ILIKE
    substring search at all, id cast or not.
  sort -- one of _SORT_COLUMNS's keys (created_at, updated_at, name,
    status); unrecognized values default to created_at.
  direction -- "asc" or "desc"; unrecognized values default to desc.
    Every sort has SigningSession.id appended as a deterministic
    secondary key.

Response: DocumentListResponse (app/schemas/document.py) -- `items` is
the requested page; `total`, `status_facets`, and `needs_attention_count`
are all computed over the full status-filtered (total) or
status-UNfiltered (status_facets, needs_attention_count) matching set,
per D2's "same base query" requirement, not derived from `items`.
`status_facets` always includes every SessionStatus key plus the
list-only owner-draft keys `draft` and `ready_to_send`.

## Query parameters

- `offset` integer
- `limit` integer
- `status` string
- `recipient` string
- `date_from` string
- `date_to` string
- `q` string
- `sort` string
- `direction` string

## Response `200`

Successful Response

- DocumentListResponse: Response shape for GET /api/documents. `status_facets` and `needs_attention_count` are both computed over the same base query as `items`/`total` -- every filter EXCEPT `status` -- so switching status tabs never has to re-derive counts client-side from a truncated page. Facets always include every SessionStatus key plus the list-only owner-draft keys `draft` and `ready_to_send`. See list_documents()'s docstring in app/api/documents.py for exactly which filters that base query includes.
  - `items` DocumentSummary[], required
    - `session_id` string, uuid, nullable
    - `document_id` string, uuid, required
    - `name` string, required
    - `document_type` string, required
    - `status` string, required
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `completed_at` string, date-time, nullable
    - `recipient_count` integer, required
    - `completed_recipient_count` integer, required
    - `recipients` RecipientSummary[], required
      - `email` string, required
      - `name` string, required
      - `status` string, required
      - `role` string, nullable
      - `viewed_at` string, date-time, nullable
      - `recipient_id` string, uuid, nullable
      - `can_remind` boolean
    - `sender_name` string, nullable
    - `sender_email` string, nullable
    - `document_count` integer
    - `changes_requested` boolean
    - `revision_of` string, uuid, nullable
  - `total` integer, required
  - `offset` integer, required
  - `limit` integer, required
  - `status_facets` object, required
  - `needs_attention_count` integer, required

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/zsign/apis/zsign-api.md) · [All operations](https://skmtc.dev/zsign/apis/zsign-api/llms.txt) · [OpenAPI document](https://skmtc.dev/zsign/apis/zsign-api/revisions/a674ac4137ba?raw)
