> This page is for version V1 (default).
> For other versions, use one of these documentation indexes:
> - V1 (default): https://docs.ottimate.com/v-1/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ottimate.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ottimate.com/_mcp/server.

# Line Items

## Overview

Line items are individual entries within an invoice that detail:

* Product or service descriptions
* Quantities
* Unit prices
* Line item costs

Each line item can be coded with its own dimensions, allowing for granular expense tracking and reporting.

## Reading line items

Line items are returned by the [Invoice Details](/api-reference/api-reference/invoices/get-invoices-id) endpoint — there is no standalone line-items endpoint:

```
GET /v1/invoices/{id}
```

Where `{id}` is the Ottimate generated invoice ID.

> **Info**
>
> `GET /v1/invoices` (the list endpoint) does not include line items. Fetch the invoice detail endpoint for each invoice whose items you need.

## Writing line items

Line items are created and updated through the `items` array on [`PATCH /v1/invoices/{id}`](/api-reference/api-reference/invoices/patch-invoices-id):

* **Update an existing item** — include its `id`
* **Create a new item** — omit `id`

```json
{
  "items": [
    {
      "id": 456,
      "quantity": 10,
      "price": 50.00
    },
    {
      "name": "New Line Item",
      "quantity": 5,
      "price": 20.00
    }
  ]
}
```

> **Warn**
>
> **The write field is `price`; the read field is `unit_price`.** `GET /v1/invoices/{id}` returns each item's per-unit cost as `unit_price`, but `PATCH /v1/invoices/{id}` expects it as `price`. Copying an item straight from a GET response into a PATCH body will silently leave the price unchanged, because `unit_price` is not a recognised write field.

### Item fields

| Field          | Write name      | Description                                                                          |
| -------------- | --------------- | ------------------------------------------------------------------------------------ |
| Item ID        | `id`            | Existing item ID to update. Omit to create a new item.                               |
| Inventory item | `item`          | Ottimate inventory item ID to link this line to.                                     |
| Description    | `name`          | Line item description. Max 200 characters. Required when creating a new item.        |
| SKU            | `sku`           | Vendor SKU or item code. Max 200 characters.                                         |
| Quantity       | `quantity`      | Number of units. Required when creating a new item.                                  |
| Unit price     | `price`         | Per-unit cost. Required when creating a new item. Returned as `unit_price` on reads. |
| Taxable        | `is_taxed`      | Whether this line is taxed.                                                          |
| Dimensions     | `dimensions`    | Item-level dimension mappings.                                                       |
| Custom fields  | `custom_fields` | Item-level custom metadata. Requires account-level custom field configuration.       |

### Item-level dimensions

Dimensions are passed as key-value pairs keyed by dimension type (e.g. `DEPARTMENT`, `CLASS`). Each value must be a **plain string** — the `erp_dimension_id` from [`GET /v1/dimensions`](/api-reference/api-reference/dimensions/get-dimensions-root):

```json
{
  "items": [
    {
      "id": 456,
      "dimensions": {
        "DEPARTMENT": "erp_dim_dept123",
        "CLASS": null
      }
    }
  ]
}
```

Item-level dimensions **merge** with what is already stored:

* Set a value to a string to add or replace that dimension
* Set a value to `null` to remove **only** that dimension
* Pass `{}` or omit the field entirely to leave all existing dimensions unchanged

> **Warn**
>
> **Item-level and header-level dimensions behave differently.** Item `dimensions` merge, but header `dimensions` on the same request **replace** the entire stored set — passing `{}` at the header level clears all header dimensions. See [Updating invoices](/updating-invoices).

### Custom fields

Item `custom_fields` **replace** the stored object — any key you omit is reset to its configured default value. Custom fields must be configured for your account before use; sending them without a configured spec, or sending an unrecognised key, returns a `400` error.

## Partial item failures

`PATCH /v1/invoices/{id}` still returns `200` when some items fail. Check `items_processing_result` in the response to see which succeeded:

```json
{
  "items_processing_result": {
    "success": { "0": 12345 },
    "failure": { "1": "Item with id 99999 not found" },
    "status": "partial_success"
  }
}
```

The keys in `success` and `failure` are the **positions of the items in your request array** (`"0"` is the first item you sent), and the values in `success` are the resulting Ottimate item IDs.

| `status`            | Meaning                                                                   |
| ------------------- | ------------------------------------------------------------------------- |
| `success`           | Every item in the request was applied.                                    |
| `partial_success`   | At least one item was applied and at least one failed.                    |
| `failure`           | Every item failed.                                                        |
| `no_items_provided` | The request contained no `items` array. Only seen on `POST /v1/invoices`. |

> **Info**
>
> **The field is named differently on create and update.** `POST /v1/invoices` returns this object as `items`; `PATCH /v1/invoices/{id}` returns it as `items_processing_result`. The contents are the same shape.

A common failure when creating items on an invoice whose vendor has not been mapped yet:

```json
{
  "items_processing_result": {
    "success": {},
    "failure": {
      "0": "Failed to create an item because the invoice vendor is unmapped.",
      "1": "Item with id 99999999 not found"
    },
    "status": "failure"
  }
}
```

> **Warn**
>
> **A `200` response does not mean every line item was written.** All three `items_processing_result` values — including `failure`, where nothing was applied — are returned with HTTP `200`. Always inspect `items_processing_result.status` rather than relying on the status code alone.