Updating invoices
Once an invoice exists in Ottimate — whether it was extracted from an uploaded document or pushed in through the API — you can correct it with PATCH /v1/invoices/{id}. A single request can update the header, create and update line items, and replace attached images atomically.
You cannot change an invoice’s status with this endpoint. Invoice status is derived by Ottimate from the invoice’s underlying state. To move an invoice through the lifecycle, use the approval endpoints, POST /v1/invoices/mark-exported, or POST /v1/invoices/{id}/flag. See Invoice lifecycle.
Sending status here is accepted and then silently ignored, which is easy to mistake for success:
{"status": "archived"}or{"status": "verified"}returns200and leaves the invoice completely unchanged- Any other value returns
400—"exported" is not a valid choice.
So the field is validated against the ingestion-only archived/verified enum but has no effect on an update. That enum is only meaningful on creation — see Bulk invoice operations.
Header fields
Every field is optional on PATCH — send only what you want to change.
Dimensions replace at the header, merge on items
This is the most common source of surprise on this endpoint:
Values must be plain strings — the erp_dimension_id from GET /v1/dimensions, not the rich dimension object returned on reads.
Custom fields
Both header and item custom_fields replace the stored object — any key you omit is reset to its configured default value, not left alone. Custom fields must be configured for your account first; sending them with no configured spec, or sending an unrecognised key, returns a 400.
Replacing images
There are three ways to change an invoice’s images, and they differ in transport:
All three replace every existing image on the invoice. Passing an empty array to image_urls or raw_images deletes all images; omitting the field entirely leaves them unchanged.
Supported formats are PDF, JPG, JPEG, and PNG.
The multipart route is capped at 10 MiB per request (10,485,760 bytes for the whole body, across all files). That limit is enforced at the API gateway and returns a plain-text 413, not JSON. image_urls avoids it entirely, since only the URLs travel in the request body. See Extraction Overview for details.
Per-file failures do not fail the request
Image uploads are validated per file. A file that is rejected — unsupported format, oversized, or an S3 error — is reported in image_errors rather than failing the whole request.
Always check image_errors — the status code will not tell you. Both endpoints return 200 whether every file was attached or some were rejected. A partial failure is not signalled as 207 Multi-Status. And because all three approaches replace the existing set first, an upload where every file fails validation leaves the invoice with no images at all, still under a 200.
A rejected file looks like this — note the response was HTTP 200:
image_errors is not always an array. On PATCH /v1/invoices/{id}, the field is only populated when the request actually carried image_urls or raw_images. When it did not, the field comes back as an empty string ("image_errors": "") rather than an empty array. Treat a non-array value as “no image work was requested”.
Line item results
When your request includes items, the response carries an items_processing_result object keyed by the position of each item in your request array. A 200 is returned even when every item failed, so inspect items_processing_result.status. See Line Items for the full breakdown.
Flagging an invoice for review
To send an invoice back for manual review rather than correcting it yourself:
flagging_reason is optional and accepts @-mentions of Ottimate users; an unrecognised user ID returns a 400.
Flagging sets the invoice’s flag and records a FLAGGED entry in its history. A flagged invoice that has not been exported is returned as needs-attention and is held out of the export path, because ready-for-export requires the invoice to be unflagged.
Flagging is idempotent. Re-flagging an already-flagged invoice is a no-op — it returns 200 but records no new history entry and does not overwrite the original flagging_reason. Clear the existing flag before applying a new reason.
Flagging an invoice you do not have permission to edit returns a 403 with the same generic INV4030 envelope as any other permission failure.
Status codes
A validation error returns the top-level message and an INV4000 code:
The response does not say which field failed. Sending image_urls over plain HTTP, an unrecognised custom field, and a dimension value that is not a string all return the same "Invalid invoice data" body. Validate the request on your side, and change one thing at a time when narrowing down a 400.
Approved and exported invoices are protected. Editing one requires the “edit approved invoices” or “edit exported invoices” permission on your API user. Without it the request returns a 403 carrying the generic INV4030 envelope — the specific reason (approved vs. exported) is not surfaced to API clients:
Check the invoice’s status to tell the two cases apart: ready-for-export means it is approved, exported means it has been exported.

