---
title: "List quotes"
method: GET
path: "/v1/quotes"
tags: ["Quotes"]
---

# List quotes

`GET /v1/quotes`

## Request

- Base URL: `https://api.officina.it`
- URL: `https://api.officina.it/v1/quotes`
- Auth: HTTP bearer

Returns quotes in the authenticated user's company, newest
first. Filter by status with `?status=`, search by customer
or title with `?q=`, and bound the result by `created_at`
with `?from=` / `?to=` / `?preset=`. Paginate with cursor-based
`?limit=` + `?starting_after=`.

The date window defaults to the last 12 months; set it with
`from`/`to`/`preset`. The applied range is echoed in
`date_filter`.

List rows omit `line_items` to keep the payload small —
fetch the detail endpoint when you need the full body.

## Query parameters

- `q` string
- `status` 'waiting' | 'accepted' | 'rejected'
- `sort` 'date' | 'number' | 'customer' | 'vehicle' | 'total'
- `direction` 'asc' | 'desc'
- `from` string, date
- `to` string, date
- `preset` 'rel30' | 'rel90' | 'rel365' | 'year' | 'Q1' | 'Q2' | 'Q3' | 'Q4' | 'month' | 'custom' | 'all'
- `limit` integer
- `starting_after` string

## Response `200`

Page of quotes (without line items).

- object
  - `object` 'list', required: Polymorphic discriminator.
  - `data` Quote[], required: Page of items. Each item carries its own `object` field.
    - `object` 'quote', required
    - `id` string, required
    - `seq_number` integer, required
    - `status` 'waiting' | 'accepted' | 'rejected', required
    - `title` string, nullable
    - `comments` string, nullable
    - `internal_comments` string, nullable
    - `customer_id` string, required
    - `customer_name` string, required
    - `vehicle_id` string, nullable
    - `vehicle_name` string, nullable
    - `vehicle_license` string, nullable
    - `work_category_id` string, nullable
    - `work_category_name` string, nullable
    - `work_category_ids` string[], required: Every work category ("categoria") of the document, in the order chosen. `work_category_id` (kept for clients that handle a single category) always equals the first. `[]` when the document has none, never null. Available since revision 2026-10-03.
    - `work_categories` WorkCategory[], required: The same list as `work_category_ids` with each category's name, same order. `work_category_name` is the first one's name. Available since revision 2026-10-03.
      - `object` 'work_category', required
      - `id` string, uuid, required
      - `name` string, required
    - `km` number, double, nullable
    - `worksheet_id` string, nullable
    - `worksheet_seq_number` integer, nullable
    - `worksheet_created_at` string, date-time, nullable
    - `total_before_tax` number, double
    - `total_tax` number, double
    - `total_after_tax` number, double
    - `total_discount` number, double
    - `total_discount_before_tax` number, double
    - `total_after_discount` number, double
    - `total_work` number, double
    - `total_articles` number, double
    - `hide_discounts` boolean
    - `line_items` QuoteLineItem[], nullable: Embedded line items. Populated on detail / write responses; list responses set this to `null` to keep payloads small.
      - `object` 'quote_line_item', required
      - `id` string, required
      - `line_type` 'item' | 'note' | 'separator', required: `item` is a priced line. `note` is a description-only line and `separator` an empty spacer row: both print without quantity, price and VAT and carry zero amounts (ignore their numeric fields). Lines from documents without types report `item`.
      - `article_id` string, nullable
      - `description` string, required
      - `unit_type` string, required
      - `quantity` number, double, required
      - `unit_price` number, double, required
      - `unit_discount_rate` number, double
      - `tax_rate` number, double
      - `vat_code` string, nullable
      - `total_price` number, double
      - `unit_cost` number, nullable: Internal cost per unit, ex-VAT, never printed on documents. Fixed when the line was written (the article's purchase price at that moment, or the `unit_cost` sent); converting the quote carries it to the worksheet's line. `null` when unknown.
      - `total_before_tax` number, double
      - `total_tax` number, double
      - `total_after_tax` number, double
      - `position` integer, required
      - `created_at` string, date-time, required
    - `delivered_at` string, date-time, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `archived_at` string, date-time, nullable
    - `deleted_at` string, date-time, nullable
  - `has_more` boolean, required: True if more results exist beyond this page. Use this for the "load more" UI — `data.length` alone is unreliable because a full page may still be the last page.
  - `next_starting_after` string: Opaque cursor for the next page. Present (and non-empty) when `has_more` is `true`; omitted otherwise. Pass the value verbatim as `?starting_after=` on the follow-up request. Clients MUST NOT parse it — today it encodes an offset, tomorrow it may encode a `(created_at, id)` tuple without a wire-visible change.
  - `url` string, required: The request path that produced this list.
  - `date_filter` DateFilter, required: The effective date range the server applied to a date-scoped list request, echoed on the list envelope so clients can render the active filter without re-deriving it. A request that sends no `from`/`to`/`preset` gets the default window, the last 12 months (`rel365`); nothing is carried over between requests. Relative presets are resolved here to concrete dates and roll forward over time.
    - `object` 'date_filter', required
    - `preset` 'rel30' | 'rel90' | 'rel365' | 'year' | 'Q1' | 'Q2' | 'Q3' | 'Q4' | 'month' | 'custom' | 'all', required: Effective selection label. Relative (`rel30`/`rel90`/`rel365`) and absolute (`year`/`Q1`-`Q4`/`month`/`custom`) carry `from`/`to`; `all` omits them.
    - `from` string, date: Inclusive lower bound (YYYY-MM-DD). Omitted when `preset` is `all`.
    - `to` string, date: Inclusive upper bound (YYYY-MM-DD). Omitted when `preset` is `all`.

## Other responses

- `401`: Missing, malformed, expired, or wrong-audience bearer token.
- `402`: The company has no active subscription, or its plan does not include API access. API access is included in the Business and Illimitato plans.
- `403`: The authenticated user lacks the required permission.
- `415`: Content-Type is not `application/json`, or Accept rejects JSON.
- `422`: A request field is missing or invalid (fast-fail).
- `500`: Unhandled server error. Detail is logged, not exposed.

---

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