> 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.

# Bulk invoice operations

Two endpoints handle invoices in bulk rather than one at a time: `POST /v1/invoices/import` for pushing many invoices into Ottimate, and `GET /v1/invoices/receiver-export` for pulling catalog-matched line data back out. Bulk approval is covered in [Invoice approvals](/invoice-approvals).

## Importing invoices in bulk

[`POST /v1/invoices/import`](/api-reference/api-reference/invoices/post-invoices-import) creates many invoices asynchronously. The request body is a **bare JSON array** of invoice objects — not an object wrapping an array:

```json
[
  {
    "ottimate_location_id": 456,
    "upload_id": "my-system-invoice-0001",
    "erp_vendor_id": "SYS-001",
    "invoice_number": "INV-2024-001",
    "invoice_date": "2024-01-15",
    "total_amount": 1500.00
  },
  {
    "ottimate_location_id": 457,
    "upload_id": "my-system-invoice-0002",
    "erp_vendor_id": "SYS-001",
    "invoice_number": "INV-2024-002",
    "invoice_date": "2024-01-16",
    "total_amount": 980.50
  }
]
```

Each object takes the same fields as [`POST /v1/invoices`](/api-reference/api-reference/invoices/post-invoices-root), and each carries its own `ottimate_location_id` — a single request can span several locations.

* Maximum **100 invoices** per request
* Every invoice requires `ottimate_location_id` and a unique `upload_id`
* An empty array, or a body that is not an array, returns a `400`
* `raw_images` is **not supported** in bulk mode — use `image_urls` instead

> **Warn**
>
> **Validation is all-or-nothing; creation is not.** Every invoice in the array is validated up front, and one invalid entry rejects the whole request with a `400` — nothing is created. Once validation passes the batch is queued, and individual invoices can still fail during processing. Poll the batch results to confirm what was actually created.

### Response

The endpoint returns `202 Accepted` immediately:

```json
{
  "status": "processing",
  "batch_id": "batch_550e8400e29b41d4a716446655440000",
  "batch_url": "/v1/batch/batch_550e8400e29b41d4a716446655440000/progress",
  "results_url": "/v1/batch/batch_550e8400e29b41d4a716446655440000/results",
  "summary": {
    "invoice_count": 2
  }
}
```

Poll [`GET /v1/batch/{id}/progress`](/api-reference/api-reference/batch/get-batch-id-progress) for status and [`GET /v1/batch/{id}/results`](/api-reference/api-reference/batch/get-batch-id-results) for per-invoice outcomes.

### Access errors are per-item

If your API user cannot access one of the locations in the array, the whole request is rejected with a `400`. The body carries one entry per invoice you sent, positionally, so an empty object means that invoice passed validation:

```json
{
  "message": [
    {},
    { "ottimate_location_id": ["Access denied to location 999"] }
  ],
  "code": "INV4000",
  "request_id": "e7234e86-fead-48f1-bb73-cd17a71bef58",
  "timestamp": "02/Sep/2026:10:13:01 +0000"
}
```

Here the second invoice in the array referenced a location the API user cannot reach. Nothing was created.

## Marking already-processed invoices on ingestion

Both `POST /v1/invoices` and `POST /v1/invoices/import` accept an optional, **write-only** `status` field. It exists for backfilling invoices that were already approved and paid — or already approved and exported — in your own system, so they do not re-enter Ottimate's approval workflow.

| Value      | Effect                                                                                                                                                                              |
| ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `archived` | The invoice is treated as **already paid**. It is filed as historical and returned as `status: "inactive"` with `sub_status: "archived"`, and is excluded from billpay and autopay. |
| `verified` | The invoice is treated as **approved and exported but not yet paid**. It stays an active invoice and remains payable through billpay and autopay.                                   |

Omit the field entirely for normal invoices — they will route through extraction, coding, and approval as usual.

> **Warn**
>
> **Sending `verified` for an invoice you have already paid risks a double payment.** `verified` leaves the invoice active and eligible for billpay and autopay, so Ottimate may pay it again. Use `archived` for anything already paid, and `verified` only for invoices that are approved in your system but still genuinely outstanding. If you are unsure which applies, omit the field.

> **Info**
>
> **This is not the same `status` you read back.** The write-only ingestion `status` accepts only `archived` or `verified`. The `status` returned on an invoice is the derived lifecycle status described in [Invoice lifecycle](/invoice-lifecycle), and it cannot be set through the API.

## Receiver export

[`GET /v1/invoices/receiver-export`](/api-reference/api-reference/invoices/get-invoices-receiver-export) returns exported invoices together with their catalog-matched line items, in the shape a receiving or inventory system expects.

> **Info**
>
> This endpoint is in **beta**.

The result set is deliberately narrow. An invoice appears only if:

* It is in the `exported` state
* Its vendor has item validation enabled
* It has at least one qualifying line after filtering — invoices left with zero lines are omitted entirely

Fringe catalog entries and lines that did not match the catalog are excluded from `receiver_lines`.

### Filters

| Parameter                                    | Description                                     |
| -------------------------------------------- | ----------------------------------------------- |
| `ottimate_company_id`                        | Restrict to one company                         |
| `ottimate_location_id`                       | Restrict to one location                        |
| `vendor_id`                                  | Restrict to one vendor                          |
| `exported_after` / `exported_before`         | Export-date window in the location's local time |
| `exported_after_utc` / `exported_before_utc` | Export-date window in UTC                       |
| `page` / `limit`                             | Pagination                                      |

### Response

```json
{
  "version": "1.0.0",
  "count": 1,
  "page": 1,
  "limit": 20,
  "results": [
    {
      "ottimate_invoice_id": "124426270",
      "ottimate_company_id": 15196,
      "ottimate_location_id": 456,
      "invoice_number": "INV-2024-001",
      "vendor_name": "Sysco Foods Corp",
      "vendor_id": "SYS-001",
      "invoice_date": "2024-01-15",
      "exported_at": "2024-01-16T01:15:00-08:00",
      "exported_at_utc": "2024-01-16T09:15:00.000000Z",
      "receiver_status": "success",
      "receiver_lines": [
        {
          "catalog_unique_key": "SKU-001",
          "upc": "001234567890a",
          "description": "Canola Oil 1gal",
          "uom": "CS",
          "case_pack": 6,
          "quantity_received": 2,
          "unit_price": 45.00,
          "extended_price": 90.00,
          "pack_size": "6/1GAL",
          "ottimate_catalog_id": "12345678"
        }
      ]
    }
  ]
}
```

> **Info**
>
> **An empty result set does not mean nothing was exported.** Because unmatched lines and non-item-validated vendors are filtered out, an invoice can be exported and still never appear here. Use [`GET /v1/invoices?status=exported`](/api-reference/api-reference/invoices/get-invoices-root) to see everything that was exported.