---
title: "List customers"
method: GET
path: "/v1/customers"
tags: ["Customers"]
---

# List customers

`GET /v1/customers`

## Request

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

Returns customers in the authenticated user's company, newest
first. Search by name with `?q=`; paginate with cursor-based
`?limit=` + `?starting_after=`.

The cursor is opaque — clients MUST NOT parse it. Pass back
the value verbatim. Today it encodes an offset; tomorrow it
may encode a `(created_at, id)` tuple without a client-visible
change.

`has_more` is `true` when more results exist beyond this page.
Drive the "load more" UI off this flag, not off `data.length`
— the last page may still contain a full `limit` worth of
results.

## Query parameters

- `q` string
- `sort` 'date' | 'name' | 'vehicle' | 'spend'
- `direction` 'asc' | 'desc'
- `limit` integer
- `starting_after` string

## Response `200`

Page of customers.

- object
  - `object` 'list', required: Polymorphic discriminator.
  - `data` Customer[], required: Page of items. Each item carries its own `object` field.
    - `object` 'customer', required
    - `id` string, required
    - `customer_type` string, required: Tax-status discriminator (e.g. "private", "company").
    - `full_name` string, nullable
    - `email` string, email, nullable
    - `phone` string, nullable: Stored E.164 value, e.g. "+393331234567".
    - `phone_dial_code` string, nullable: `phone` split server-side for the edit form's dial-code selector (e.g. "+39"). Null when `phone` is null.
    - `phone_national` string, nullable: `phone` minus the dial code, for the edit form's national-number input. Null when `phone` is null.
    - `address` string, nullable
    - `city` string, nullable
    - `zip_code` string, nullable
    - `province` string, nullable
    - `tax_id` string, nullable: Partita IVA for an Italian customer; the VAT number without country prefix for a business elsewhere in the EU; free text otherwise. See "Partita IVA e numeri IVA UE".
    - `fiscal_code` string, nullable: Italian fiscal code (codice fiscale) for individuals.
    - `pec` string, nullable: Italian certified-email address for e-invoicing.
    - `destination_code` string, nullable: Italian SDI destination code for e-invoicing.
    - `payment_method` string, nullable
    - `comments` string, nullable
    - `birthday` string, date-time, nullable
    - `last_vehicle_name` string, nullable: Display string of the customer's most recent vehicle (e.g. "Fiat Panda"). Pre-computed on the entity for list-row rendering without a second request.
    - `last_vehicle_license` string, nullable
    - `total_spent` number, double: Lifetime spend (worksheet totals net of discounts), computed by the list query for row rendering. Money — render via the locale formatter; avoid arithmetic.
    - `documents` CustomerDocument[]: Attachments (Documenti Allegati). Present on the detail response only; omitted on lists.
      - `object` 'customer_document', required
      - `id` string, required
      - `name` string, required
      - `content_type` string, required
      - `description` string, nullable
      - `url` string, required
      - `file_size_bytes` integer, required
      - `created_at` string, date-time, required
    - `custom_fields` object, nullable: Single-customer responses only (GET, POST, PATCH). Raw stored values keyed by custom field definition `id` (see `CustomerCustomFieldValues` for the stored shapes). Includes hidden fields. Omitted on lists.
    - `custom_field_values` object[]: `GET /v1/customers/{id}` only. The company's visible custom field definitions (settings → Campi cliente) resolved against this customer: configured order, display-ready names, "" when the customer has no value.
      - `id` string, required
      - `name` string, required
      - `type` 'string' | 'integer' | 'datetime', required
      - `value` string, required
    - `field_visibility` object: `GET /v1/customers/{id}` only. The company's optional-field toggles (settings → Campi cliente): the edit form hides — and omits from its PATCH — exactly what the web form hides. All true when never configured.
      - `birthday` boolean
      - `comments` boolean
    - `next_appointment` string, date-time, nullable
    - `created_at` string, date-time, required
    - `updated_at` string, date-time, required
    - `deleted_at` string, date-time, nullable: Soft-deletion timestamp. Populated on the response from DELETE /v1/customers/{id}. Customers with a non-null deleted_at are not returned by list/detail endpoints — this field appears for the delete response only.
  - `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.

## 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)
