> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grdd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Vehicles

> Create, read, update and delete your vehicles and link them to fleets with the Connect API

A **vehicle** is one physical vehicle of your company, identified by its license plate. A vehicle can be linked to one or more [fleets](/connect-api/content/fleets) and to [documents](/connect-api/content/documents).

Read [Connect API conventions](/connect-api/content/api-conventions) first. It explains the base URL, authentication, `Idempotency-Key`, `If-Match`, pending operations and the error format. This page uses `$CONNECT_API_BASE_URL` for the base URL. Ask GRiDD support ([support@grdd.net](mailto:support@grdd.net)) for the Connect API base URL for your environment.

## What is available

The Connect API README describes Fleets and Vehicles as complete production implementations in code. The route handlers validate requests and responses with the contracts in `packages/api-contracts`. This page describes those contracts. It does not say that a given environment serves them.

All vehicle routes are owner routes. There is no route to read the vehicles of another company, and there is no published or public vehicle route.

## Endpoints

| Method | Path | Purpose | `Idempotency-Key` | `If-Match` |
| - | - | - | - | - |
| `GET` | `/api/vehicles` | List your vehicles. Optional exact filters `license_plate` and `vin_number`. | – | – |
| `POST` | `/api/vehicles` | Create a vehicle. | Required | – |
| `GET` | `/api/vehicles/{vehicleIdOrSlug}` | Read one vehicle by numeric ID or by slug. Returns an `ETag`. | – | – |
| `PATCH` | `/api/vehicles/{vehicleId}` | Update one vehicle. | Required | Required |
| `DELETE` | `/api/vehicles/{vehicleId}` | Delete one vehicle. | – | Required |
| `POST` | `/api/vehicles/operations/{operationId}` | Resume a pending operation. See [Pending operations](/connect-api/content/api-conventions#pending-operations). | – | – |
| `POST` | `/api/vehicles/links` | Link a vehicle to fleets. | – | – |
| `DELETE` | `/api/vehicles/links` | Remove links between a vehicle and fleets. | – | – |
| `POST` | `/api/vehicles/from-reservation` | Create or update a vehicle from reservation data. | Required | – |
| `GET` | `/api/vehicles/documents` | List links between your vehicles and your documents. | – | – |
| `DELETE` | `/api/vehicles/documents` | Remove document links from one vehicle. | Required | – |

## Authentication and access

* Send `Authorization: Bearer YOUR_ACCESS_TOKEN`. See [Authentication](/connect-api/content/api-conventions#authentication).
* The vehicle routes do not check a role in the token.
* **Subscription.** Creating a vehicle checks your subscription. In the subscription contract in source, no plan has a numeric limit for vehicles. The check refuses a cancelled subscription with `403 entitlement_denied`. An active trial passes. If the plan cannot be determined, the API returns `503 temporarily_unavailable`. Updates and deletes do not count against a limit.

## Vehicle fields

### Read record

| Field | Type | Notes |
| - | - | - |
| `id` | integer | |
| `slug` | string | |
| `griddid` | string | Your company. |
| `license_plate` | string or null | |
| `vin_number` | string or null | |
| `make`, `model`, `color`, `description` | string or null | |
| `year` | integer or null | |
| `mileage` | integer or null | At least 0. |
| `status` | `active` or `inactive` | |
| `image_url` | string or null | Always present. See [Image URLs](/connect-api/content/api-conventions#image-urls). |
| `extra_photos` | array of strings | Always present. |
| `display_order` | number or null | |
| `vehicle_id` | string or null | An identifier from the reservation system. |
| `documents` | array | Items are `{ "document": id, "id": string }`. |
| `fleets` | array | Items are `{ "fleet": id, "id": string }`. |
| `version` | integer | Use it in `If-Match`. |
| `createdAt`, `updatedAt` | string (ISO 8601) | |

### Write body

All write bodies are strict. Unknown keys, such as `id`, `griddid` or `version`, return `400 invalid_request`.

**Create** (`POST /api/vehicles`):

| Field | Type | Required | Notes |
| - | - | - | - |
| `vehicle` | object | Yes | The fields below. |
| `locale` | `en`, `es`, `de`, `fr` | No | |

Fields in `vehicle`:

| Field | Type | Create | Limits |
| - | - | - | - |
| `license_plate` | string | **Required** | At least 1 character. On update, 1 to 20 characters. |
| `slug` | string | Optional | |
| `vin_number` | string or null | Optional | Up to 17 characters. |
| `make`, `model` | string or null | Optional | 1 to 50 characters. |
| `year` | integer or null | Optional | 1900 up to the current year plus 2. |
| `color` | string or null | Optional | Up to 30 characters. |
| `status` | `active`, `inactive` or null | Optional | |
| `description` | string or null | Optional | |
| `image_url` | URL or null | Optional | |
| `extra_photos` | array of URLs | Optional | |
| `mileage` | integer or null | Optional | At least 0. |
| `display_order` | number | Optional | At least 0. |
| `vehicle_id` | string or null | Optional | |
| `documents` | array, or null | Optional | Each item is a positive document ID, or `{ "document": id }`. |
| `fleets` | array, or null | Optional | Each item is a fleet ID, or `{ "fleet": id }`. The API checks that each fleet belongs to your company. |

**Update** (`PATCH /api/vehicles/{vehicleId}`): the same shape. `vehicle` can hold any subset of the fields, and none is required. Send `If-Match` and `Idempotency-Key`.

* If you omit a field, the API does not change it.
* `fleets: []` removes all fleet links. `documents: []` or `null` removes all document links.

**Delete** (`DELETE /api/vehicles/{vehicleId}`): send `If-Match`. Optional query `locale`. The API also removes the links from fleets to the vehicle.

### Image rules

In addition to the [general image URL rules](/connect-api/content/api-conventions#image-urls), a vehicle image URL must belong to your company. The first path segment must be `shared`, `global` or your company ID. Or the first segment is `vehicle`, `vehicles` or `profile` and the second is your company ID. Unlike fleet images, the host `asset.grdd.dev` alone is not enough. Another URL returns `403 forbidden`.

## Create a vehicle

```bash theme={null}
curl -X POST "$CONNECT_API_BASE_URL/api/vehicles" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 9e4d1b70-5c2a-4f8e-a3d6-0b7f21c8d4e5" \
  -d '{
    "vehicle": {
      "license_plate": "ABC-123",
      "make": "Ford",
      "model": "Transit",
      "image_url": "https://asset.grdd.dev/vehicle/demo-company/abc-123.webp",
      "extra_photos": [],
      "fleets": [{ "fleet": 7 }]
    },
    "locale": "en"
  }'
```

`201 Created` with the header `ETag: "v1"`:

```json theme={null}
{
  "data": {
    "id": 11,
    "griddid": "demo-company",
    "slug": "abc-123",
    "license_plate": "ABC-123",
    "make": "Ford",
    "model": "Transit",
    "status": "active",
    "image_url": "https://asset.grdd.dev/vehicle/demo-company/abc-123.webp",
    "extra_photos": [],
    "documents": [],
    "fleets": [{ "fleet": 7 }],
    "version": 1,
    "updatedAt": "2026-08-30T12:00:00.000Z",
    "createdAt": "2026-08-30T12:00:00.000Z"
  },
  "invalidated": [],
  "status": "completed"
}
```

The save response has `data`, `invalidated` and `status` (`completed` or `pending`). It can also have `operationId` and `effects`. A `pending` result returns `202`.

If a vehicle with the same `slug` or `license_plate` exists, the response is `409 conflict`.

## Read vehicles

```bash theme={null}
curl "$CONNECT_API_BASE_URL/api/vehicles?license_plate=ABC-123" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```

Response `200`. This is the fixture `vehicle-list.json`:

```json theme={null}
{
  "items": [
    {
      "id": 11,
      "griddid": "demo-company",
      "slug": "abc-123",
      "license_plate": "ABC-123",
      "vin_number": "1FTBW2XG1JKA12345",
      "make": "Ford",
      "model": "Transit",
      "year": 2025,
      "color": "Black",
      "status": "active",
      "description": "Airport shuttle",
      "image_url": "https://asset.grdd.dev/vehicle/demo-company/abc-123.webp",
      "extra_photos": [
        "https://asset.grdd.dev/vehicle/demo-company/abc-123-interior.webp"
      ],
      "mileage": 12000,
      "display_order": 4,
      "vehicle_id": "reservation-vehicle-11",
      "documents": [{ "document": 21, "id": "doc-row-1" }],
      "fleets": [{ "fleet": 7, "id": "fleet-row-1" }],
      "version": 1,
      "updatedAt": "2026-08-30T12:00:00.000Z",
      "createdAt": "2026-08-30T12:00:00.000Z"
    }
  ]
}
```

The list has no pagination parameters. Query parameters are `locale`, `force`, `license_plate` and `vin_number`. The plate and VIN filters match exactly.

## Update a vehicle

```bash theme={null}
curl -X PATCH "$CONNECT_API_BASE_URL/api/vehicles/11" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "If-Match: \"v1\"" \
  -H "Idempotency-Key: 4f0c8a19-6e3b-4d52-9a7e-8c1d5b2e6f30" \
  -d '{ "vehicle": { "model": "Transit XL", "fleets": [] } }'
```

The response is `200` (or `202` if pending) with the same shape as the create response and a new `ETag`. A stale `If-Match` returns `412 version_conflict`.

## Link and unlink fleets

Use this route to change the fleet links of one vehicle without a full update. The body is the fixture `vehicle-links-request.json`:

```bash theme={null}
curl -X POST "$CONNECT_API_BASE_URL/api/vehicles/links" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "vehicleId": 11, "fleetIds": [7, 9], "locale": "en" }'
```

| Field | Type | Required | Notes |
| - | - | - | - |
| `vehicleId` | integer or string | Yes | Vehicle ID. |
| `fleetIds` | array | Yes | At least one fleet ID. |
| `locale` | `en`, `es`, `de`, `fr` | No | |

`POST` adds the links. `DELETE` removes only the fleets that you list. Both return the updated vehicle in the save response (`200`, or `202` if pending). The API checks that the vehicle and every fleet belong to your company. A relationship that is not valid returns `422 domain_rejected`.

## Create or update a vehicle from reservation data

`POST /api/vehicles/from-reservation` takes the vehicle data of a reservation. The body is the fixture `vehicle-reservation-request.json`:

```json theme={null}
{
  "vehicleData": {
    "plateNo": "ABC-123",
    "vin": "1FTBW2XG1JKA12345",
    "make": "Ford",
    "model": "Transit",
    "year": "2025",
    "vColor": "Black",
    "vehicleId": "reservation-vehicle-11"
  },
  "isDataDifferent": true,
  "locale": "en"
}
```

`vehicleData` is strict. Only `plateNo` is required. Send `Idempotency-Key`.

* If no vehicle with that plate exists, the API creates one with status `active`.
* If one exists and `isDataDifferent` is `true`, the API updates it.
* If one exists and `isDataDifferent` is not `true`, the response is `422 domain_rejected`.

The response is the save response (`200`, or `202` if pending).

## Delete a vehicle

```bash theme={null}
curl -X DELETE "$CONNECT_API_BASE_URL/api/vehicles/11" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "If-Match: \"v2\""
```

`204 No Content` when the delete is complete. If follow-up work is not finished, the response is `202`. This is the fixture `vehicle-delete-pending.json`:

```json theme={null}
{
  "data": null,
  "invalidated": [],
  "status": "pending",
  "operationId": "vehicle-11111111-1111-4111-8111-111111111111",
  "effects": [
    {
      "name": "fleet_links",
      "status": "failed",
      "retryable": true,
      "problemCode": "partial_success"
    },
    {
      "name": "legacy_vehicle_records",
      "status": "pending",
      "retryable": true
    },
    {
      "name": "vehicle_event",
      "status": "pending",
      "retryable": true
    },
    {
      "name": "vehicle_cache",
      "status": "pending",
      "retryable": true
    }
  ]
}
```

Resume it with `POST /api/vehicles/operations/vehicle-11111111-1111-4111-8111-111111111111`. The response has `operationId`, `status` and `effects`.

## Errors

Error bodies use the [problem format](/connect-api/content/api-conventions#errors). These codes come from the vehicle routes.

| Status | `code` | When |
| - | - | - |
| 400 | `invalid_json`, `invalid_request` | The body or query is not valid. Includes unknown keys, a bad `If-Match` or `Idempotency-Key` value, and an image URL that fails the URL rules. |
| 401 | `unauthenticated` | The token is missing, invalid or expired. |
| 403 | `entitlement_denied` | The subscription does not allow creating a vehicle. |
| 403 | `forbidden` | An image URL is outside your company namespace. |
| 404 | `not_found` | No vehicle with this ID or slug for your company, or no such operation. |
| 409 | `conflict` | A vehicle with the same slug or license plate exists. |
| 409 | `idempotency_conflict` | The key was used for a different request. |
| 412 | `version_conflict` | `If-Match` is stale. The body has `currentVersion`. |
| 413, 415 | `payload_too_large`, `unsupported_media_type` | Body size or media type. |
| 422 | `domain_rejected` | A link, image or reservation-import rule was violated. |
| 428 | `version_required`, `idempotency_key_required` | A required header is missing. |
| 202 | `partial_success` | An idempotent retry found the first request still running. Use `operationId`. |
| 503 | `temporarily_unavailable` | Core, the cache or the data store is not available, or the plan cannot be determined. |
| 500 | `internal_error` | Unexpected error. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.