---
title: "Update a vehicle"
method: PATCH
path: "/v1/vehicles/{id}"
tags: ["Vehicles"]
---

# Update a vehicle

`PATCH /v1/vehicles/{id}`

## Request

- Base URL: `https://api.officina.it`
- URL: `https://api.officina.it/v1/vehicles/{id}`
- Auth: HTTP bearer

Partial update following the design philosophy:

- **Omitted JSON keys** leave the corresponding entity fields
  untouched.
- **Explicit JSON `null`** clears the field (SQL NULL).
- **Concrete values** overwrite the entity field.

`model` and `manufacturer_id` are NOT NULL in the schema;
sending either as `null` is a 422 `parameter_invalid` error.

`custom_fields` is patched as a whole-object replacement
(omit = unchanged, null = clear, object = overwrite). Per-key
merging is intentionally not supported. Keys and values are
validated against the company's definitions — see
`VehicleCustomFieldValues`.

Deadline dates keep their agenda events in step: moving a date
moves its event, clearing one removes it, and a request that
does not change a date touches no event. This covers
`next_inspection_at` and every custom date field whose
definition sets `reminder_days_before`. Because `custom_fields`
is replaced wholesale, omitting a visible field's key that held
a date clears it — and removes that event. Hidden fields keep
their values (and events) unless their key is sent; `null` for
the whole object clears visible fields only.

Rental fields are read-only here — use the rental setup flow
to change them.

Returns the fully materialized post-update vehicle.

Foreign-key references in the body (`customer_id`, etc.) must
resolve to resources owned by the authenticated user's company.
Unreachable refs return 422 `parameter_invalid` pointing at the
offending field.

## Request body

- VehiclePatchRequest: Body of `PATCH /v1/vehicles/{id}`. Every field is optional on the wire and carries three-state semantics: - Field omitted → leave the entity field untouched. - Field present with value `null` → clear the entity field. - Field present with a value → set the entity field. Fields not listed here are intentionally NOT patchable: `id`, `company_id`, `created_by`, system timestamps (`created_at`, `updated_at`, `deleted_at`), rental fields (`is_rental`, `rental_*`), and computed/joined fields (`next_appointment`, `manufacturer_name`, `customer_name`). `model` and `manufacturer_id` are NOT NULL at the entity layer; sending either as explicit `null` is rejected with 422 `parameter_invalid`. `custom_fields` is replaced wholesale — per-key merging is not supported. Keys and values are validated against the company's definitions; see `VehicleCustomFieldValues`.
  - `manufacturer_id` string
  - `model` string
  - `license` string, nullable
  - `variant` string, nullable
  - `vin` string, nullable
  - `transmission` string, nullable
  - `engine_power_kw` integer, nullable
  - `engine_displacement_ccm` integer, nullable
  - `engine_code` string, nullable: Codice motore. Stored trimmed and upper case (max 64 characters); an empty string or `null` clears it. When empty, officina.it may fill it from the vehicle's registration data. Shown in the app when `field_visibility.engine_code` is on.
  - `vehicle_weight_kg` integer, nullable
  - `engine_category` string, nullable
  - `registered_at` string, date-time, nullable: Nullable client-supplied RFC 3339 timestamp. Same 1900-01-01 … 2100-12-31 window as `WritableTimestamp`; `null` clears the field.
  - `comments` string, nullable
  - `customer_id` string, nullable
  - `km` number, nullable
  - `km_registered_at` string, date-time, nullable: Nullable client-supplied RFC 3339 timestamp. Same 1900-01-01 … 2100-12-31 window as `WritableTimestamp`; `null` clears the field.
  - `last_inspection_at` string, date-time, nullable: Nullable client-supplied RFC 3339 timestamp. Same 1900-01-01 … 2100-12-31 window as `WritableTimestamp`; `null` clears the field.
  - `next_inspection_at` string, date-time, nullable: Nullable client-supplied RFC 3339 timestamp. Same 1900-01-01 … 2100-12-31 window as `WritableTimestamp`; `null` clears the field.
  - `custom_fields` VehicleCustomFieldValues, nullable: The vehicle's custom field values, keyed by definition `id` from `GET /v1/vehicle-field-settings` (e.g. `cf_3f9b1c`). Every key is checked against the company's definitions and each value against its field's `type`: - `string` — a JSON string, trimmed. - `integer` — a JSON integer or a string holding one (`90` or `"90"`, so values echoed from `custom_field_values` are accepted); stored as a JSON integer. - `datetime` — a `YYYY-MM-DD` date or an RFC 3339 timestamp, stored as `YYYY-MM-DD` (the calendar day as written, in the timestamp's own offset). Years must fall between 1900 and 2100. A field whose definition sets `reminder_days_before` gets an agenda event on that date. A `null` or empty value clears that field. An unknown key or a value that does not fit its type is a 422 `parameter_invalid` with `param` pointing at it (`/custom_fields/<id>`). A value sent back exactly as the vehicle stores it is never refused: it is kept as is even if it predates these rules or a change of its field's type, and a key of a deleted field sent back unchanged is dropped. The object replaces the stored one as a whole: a visible field left out is cleared. A hidden field (`visible: false` in the settings) keeps its stored value unless its key is sent — `custom_field_values` lists visible fields only, so a client rebuilding this object from it never erases data it could not see. A stored value that no longer fits its field's type (text left under a field later turned into a date or number) is likewise kept unless its key is sent — clients cannot show it in a typed input, so leaving it out is not a request to clear it. Send the key with `null` to clear it.

## Response `200`

Vehicle updated. Returns the full post-update entity.

- Vehicle: A vehicle owned (or managed) by a workshop client. Multi-tenant: belongs to one company. `manufacturer_name` and `customer_name` are joined-in convenience fields populated by the repository so list rows can render `Fiat Panda — Mario Rossi` without a follow-up request. Rental fields are read-only on this resource. Mutate them via the dedicated rental setup flow.
  - `object` 'vehicle', required
  - `id` string, required
  - `license` string, nullable: License plate (regional format, free-form string).
  - `manufacturer_id` string, required
  - `manufacturer_name` string, required: Joined from the manufacturers table for display. Read-only; mutate via `manufacturer_id`.
  - `model` string, required
  - `variant` string, nullable
  - `vin` string, nullable: Vehicle Identification Number.
  - `transmission` string, nullable
  - `engine_power_kw` integer, nullable
  - `engine_displacement_ccm` integer, nullable
  - `engine_code` string, nullable: Codice motore. Stored trimmed and upper case (max 64 characters); an empty string or `null` clears it. When empty, officina.it may fill it from the vehicle's registration data. Shown in the app when `field_visibility.engine_code` is on.
  - `vehicle_weight_kg` integer, nullable
  - `engine_category` string, nullable
  - `registered_at` string, date-time, nullable
  - `comments` string, nullable
  - `customer_id` string, nullable
  - `customer_name` string, nullable: Joined from the owning customer's `full_name` for display. Read-only; mutate via `customer_id`.
  - `km` number, nullable: Most recently recorded odometer reading.
  - `km_registered_at` string, date-time, nullable
  - `last_inspection_at` string, date-time, nullable
  - `next_inspection_at` string, date-time, nullable
  - `next_appointment` string, date-time, nullable: Earliest upcoming calendar appointment for this vehicle, computed at read time.
  - `custom_fields` object, nullable: Raw stored values keyed by custom field definition `id` (see `VehicleCustomFieldValues` for the stored shapes). Includes hidden fields, and on older records keys of deleted definitions (dropped on the next PATCH that sends `custom_fields`).
  - `custom_field_values` object[]: Detail responses only. The company's visible custom field definitions (settings → Campi veicolo) resolved against this vehicle: configured order, display-ready names, "" when the vehicle has no value. Clients render these directly — no definition lookup needed.
    - `id` string, required
    - `name` string, required
    - `type` 'string' | 'integer' | 'datetime', required
    - `value` string, required
  - `documents` VehicleDocument[]: Detail responses only — the vehicle's Documenti Allegati. `url` is the authenticated download route (`/v1/vehicles/{id}/documents/{documentID}/download`).
    - `object` 'vehicle_document', required
    - `id` string, required
    - `name` string, required: Original file name.
    - `content_type` string, required
    - `description` string, nullable, required
    - `url` string, required
    - `file_size_bytes` integer, required
    - `created_at` string, date-time, required
  - `field_visibility` object: Detail responses only. The company's optional-field toggles (settings → Campi veicolo): the edit form hides — and omits from its PATCH — exactly what the web form hides. All true when never configured.
    - `km` boolean
    - `numero_telaio` boolean
    - `transmission` boolean
    - `engine_category` boolean
    - `engine_power_kw` boolean
    - `engine_displacement_ccm` boolean
    - `engine_code` boolean
    - `vehicle_weight_kg` boolean
    - `registration_date` boolean
    - `last_inspection_date` boolean
    - `next_inspection_date` boolean
  - `is_rental` boolean, required: Whether this vehicle is part of the workshop's rental fleet. Read-only here — managed by the rental setup flow.
  - `rental_daily_price_before_tax` number, nullable
  - `rental_daily_price_tax` number, nullable
  - `rental_daily_price_after_tax` number, nullable
  - `rental_vat_code` string, nullable
  - `rental_daily_discount_rate` number, 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/vehicles/{id}. Vehicles with a non-null deleted_at are not returned by list/detail endpoints — this field appears for the delete response only.

## Other responses

- `400`: Request body is unparseable or X-API-Version is missing.
- `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.
- `404`: Resource does not exist OR belongs to a different company. The two cases are intentionally indistinguishable.
- `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)
