# Document API

With the Document API you upload documents to a channel, retrieve the processed data and tell Workist whether the import into your system worked. All document types offer the same operations.

The complete schemas of all requests and responses are in the [Swagger UI](https://api.workist.com/v1/swagger/). This page describes how to use the API.

## Basics {#grundlagen}

- **Base URL:** `https://api.workist.com/v1` (equivalent: `https://api.workist.com/api/v1`).
- **No trailing slash.** `GET /orders` works, `GET /orders/` returns `404`.
- **Authentication:** Every call carries the header `Authorization: Bearer <API key>`. The [Quickstart](/en/docs/api-documentation/quickstart.md#api-schluessel) explains how to create a key.
- **Channels:** An API key only sees the channels it has been granted. The API does not return documents of other channels.
- **Format:** Responses are JSON. Send uploads as `multipart/form-data` and all other requests as JSON.
- **Channel delivery method:** If your system pulls the results through the API, set the delivery method to **API** under **Upload** in the channel. Otherwise Workist also transfers the results via SFTP, and successfully transferred documents no longer appear under `imported=false`.

## Resources {#ressourcen}

<!-- generated by .claude/tasks/docs-audit/scripts/api_document_reference.py -->

| Document type | Resource |
|---|---|
| Orders | `/orders` |
| Invoices | `/invoices` |
| Order confirmations | `/order-confirmations` |
| Requests for quotation | `/rfq` |
| Delivery notes | `/delivery-notes` |
| Lists of services | `/list-of-services` |

## Operations {#operationen}

Every resource offers the same seven operations. Replace `{resource}` with the path from the table above, e.g. `/orders`.

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/{resource}` | List documents (paginated, filterable) |
| `GET` | `/{resource}/{id}` | Get one document |
| `POST` | `/{resource}` | Upload a file to a channel |
| `DELETE` | `/{resource}/{id}` | Delete a document |
| `PATCH` | `/{resource}/{id}/meta` | Set sender and subject |
| `PUT` | `/{resource}/{id}/mark_imported` | Report the result of the import into your system |
| `PUT` | `/{resource}/{id}/assign_user` | Assign the document to a user |
| `POST` | `/list-of-services/{id}/generate-x94` | Generate a GAEB X94 offer file from an X83 list of services |

## Typical flow {#ablauf}

1. Documents reach the channel by email, through the Workbench or via `POST /{resource}`.
2. Your system regularly requests all finished documents that have not been handed over yet, e.g. `GET /orders?finished=true&imported=false`, and reads every page (see [Pages](#seiten)).
3. For each document your system imports the data and reports the result with `PUT /orders/{id}/mark_imported`: `SUCCEEDED` with your document number, or `FAILED` with the error message.
4. Reported documents no longer appear in the request from step 2. Documents reported as `FAILED` come back once a user has corrected and finished them in the Workbench (see [Report the import](#mark-imported)).

Instead of polling, you can have a [webhook](/en/docs/data-exchange/webhooks.md) notify you when a document is finished and then retrieve it.

## Retrieve documents {#abrufen}

### Filters {#filter}

`GET /{resource}` accepts these query parameters:

| Parameter | Meaning |
|---|---|
| `channel` | Channel ID. Only documents of this channel. |
| `assistance_needed` | `true`: only documents waiting for assistance in the Workbench. `false`: all others. |
| `finished` | `true`: finished documents that were not discarded. `false`: documents not finished yet. |
| `imported` | `true`: handed over successfully (feedback `SUCCEEDED` or a successful transfer via SFTP, AS2 or ERP integration). `false`: not handed over successfully yet. Unless you set `import_failed`, `import_failed=false` applies. |
| `import_failed` | `true`: handover failed (e.g. feedback `FAILED`). `false`: all others. |
| `created_by` | Email address or ID of the user who uploaded the document. |
| `assisted_by` | Email address or ID of the user who assisted the document. |
| `date_from` | Created at or after this time, ISO 8601 (`YYYY-MM-DDTHH:MM:SSZ`). |
| `date_to` | Created at or before this time, ISO 8601. |
| `show_unfinished_data` | `true`: fill `data` for documents that are still being processed. Default: `false`. |
| `page` | Page number, starting at 1. |
| `page_size` | Entries per page, at most 10. |

`GET /{resource}/{id}` also accepts `show_unfinished_data`.

### Pages {#seiten}

The list returns **at most 10 documents per page**, oldest first. The response has this shape:

```json
{
  "total_count": 37,
  "next": "https://api.workist.com/v1/orders?finished=true&imported=false&page=2",
  "previous": null,
  "items": [ ... ]
}
```

- Call `next` until it is `null`. If you only read the first page, you miss documents.
- If you report documents while paging, they drop out of the `imported=false` filter and the following pages shift. In that case, request the first page again and again until `items` is empty.

### Record {#datensatz}

Every document in `items` and every single request has these fields:

| Field | Content |
|---|---|
| `id` | Document ID (UUID). |
| `channel` | Channel ID. |
| `status` | Processing status, see [Status](#status). |
| `sender` | Sender, e.g. the sender of the email. |
| `subject` | Email subject or file name. |
| `created_at` | Time the document was received. |
| `updated_at` | Time of the last change. |
| `created_by` | User who uploaded the document (`first_name`, `last_name`, `email`). |
| `assigned_to` | Assigned user. |
| `assisted_by` | User who assisted the document. |
| `assisted_at` | Time of assistance. |
| `delivered_at` | Time of the successful transfer via SFTP, AS2 or ERP integration. |
| `finished_at` | Time the document was finished. |
| `imported_at` | Time your system confirmed the import with `SUCCEEDED`. |
| `email_file_url` | Link to the original email, if the document came by email. |
| `input_file_url` | Link to the processed file. |
| `input_file_name` | Original file name. |
| `classified_files` | Other attachments of the same email, with `file_name`, `classification` and `url`. |
| `additional_file_urls` | Links to additional files, if any. |
| `workbench_url` | Link to the document in the Workbench. |
| `external_reference` | Reference from the last feedback, e.g. your document number. |
| `external_status` | Status from the last feedback. |
| `external_message` | Message from the last feedback, e.g. an error message. |
| `data` | The extracted document data, see [Document data](#dokumentdaten). |

File links are valid for at most 30 minutes. Download the files right after the request, or request the document again to get new links.

### Status {#status}

| `status` | Meaning |
|---|---|
| `PENDING` | Being processed. |
| `ON_HOLD` | Put on hold in the Workbench. |
| `ASSISTANCE_NEEDED` | Waiting for assistance in the Workbench. |
| `ASSISTED` | Assisted, processing continues. |
| `FINISHED` | Finished. |
| `DELIVERED` | Transferred via SFTP, AS2 or ERP integration. |
| `IMPORTED` | Your system confirmed the import with `SUCCEEDED`. |
| `FAILED` | Processing or handover failed, including after feedback `FAILED`. |
| `REJECTED` | The file could not be read. |
| `DISCARDED` | Discarded. |
| `DELETED` | Deleted. The API does not return deleted documents. |

### Document data {#dokumentdaten}

`data` holds the header fields of the document and the line items under `line_items` (for lists of services under `positions`). The [Swagger UI](https://api.workist.com/v1/swagger/) shows the field names per document type.

- Only fields enabled in the channel carry values. All other fields are `null`.
- As long as a document has been neither assisted nor finished, `data` is empty (`{}`). With `show_unfinished_data=true` you get the current intermediate state.
- Quantities, prices and dates are text, as shown in the Workbench.

## Upload a document {#hochladen}

`POST /{resource}` with `multipart/form-data`:

| Field | Required | Content |
|---|---|---|
| `channel` | Yes | ID of the channel that processes the document. |
| `file` | Yes | The file. |
| `user` | No | Email address or ID of a user who is recorded as the uploader. |

- **Channel ID:** It is in the Workbench address bar when the channel is open (`…/channels/<channel ID>`), and in the `channel` field of every document of that channel. The channel must match the document type of the resource.
- **File types:** BMP, CSV, DOC, DOCX, EML, HTML, JPEG, ODS, PDF, PNG, RTF, TIFF, TXT, XLS, XLSM, XLSX and XML. Workist unpacks ZIP archives, Outlook messages (MSG) and `winmail.dat` and processes their content.
- **Response:** `201` with the new document. If one file results in several documents (e.g. an email with several attachments or a ZIP archive), the response only contains the first. Find the others with `GET /{resource}`.

## Report the import {#mark-imported}

With `PUT /{resource}/{id}/mark_imported` you report whether your system took over the document. All fields are optional; without a body, `SUCCEEDED` applies.

| Field | Content |
|---|---|
| `status` | `SUCCEEDED` (default): import succeeded, final. `FAILED`: import rejected, the document goes back to the Workbench. `PENDING`: handover still open. |
| `external_reference` | Reference of the document in your system, e.g. the sales order number. |
| `external_status` | Status of the document in your system. |
| `external_message` | Free text, e.g. your system's error message. |

```json
{
  "status": "FAILED",
  "external_reference": "",
  "external_status": "rejected",
  "external_message": "Article 4711 is blocked"
}
```

Rules:

- Only finished documents can be reported. Otherwise the API answers `400` with `Record not finished yet`.
- `SUCCEEDED` is final. The API rejects any further feedback for the same document with `400` and `Record already successfully delivered`.
- `PENDING` keeps the document in the `imported=false` request, e.g. when your system has accepted it but not booked it yet.
- Each feedback overwrites `external_reference`, `external_status` and `external_message` of the previous one.

### What happens after `FAILED` {#failed}

1. The document gets the status `FAILED` and no longer counts as finished. It appears in the list of open documents in the Workbench again.
2. When users open it, they see the notice "Your system rejected the document with the following response:" with the reference, status and message from your feedback.
3. A user clicks **Reopen for assistance**, corrects the data and finishes the document, or discards it with **Discard**.
4. Once finished again, the document reappears in `GET /{resource}?finished=true&imported=false`, and your system can import it again.

So write an understandable cause into `external_message`. It is what your colleagues read in the Workbench.

## More operations {#weitere}

### Set sender and subject {#meta}

`PATCH /{resource}/{id}/meta` sets sender and subject, e.g. when you upload files from your own mailbox. Only the fields you send are changed.

| Field | Content |
|---|---|
| `sender` | Sender, e.g. the sender of the original email. |
| `subject` | Subject, e.g. the subject of the original email. |

### Assign a user {#assign-user}

`PUT /{resource}/{id}/assign_user` with `{"user": "<email address or ID>"}` assigns the document to a user of your organisation. The API rejects unknown users with `400` and `{"user": ["User does not exist."]}`.

### Delete a document {#loeschen}

`DELETE /{resource}/{id}` deletes the document and answers `204`. The API no longer returns the document afterwards.

### Generate GAEB X94 {#gaeb-x94}

For lists of services that arrived as a GAEB X83 file, `POST /list-of-services/{id}/generate-x94` generates an X94 offer file. You send your prices per position and your supplier data, and Workist returns the XML file.

- `items` (required): a list with one entry per position. `source_id` is the GAEB ID of the position and must match a position of the list of services. Optional: `article_number`, `article_characteristics` (`normal`, `alternate` or `provis`), `quantity`, `unit`, `vat`, `net_price`, `price_basis`, `offer_price`, `price_basis_unit`, `ean`, `article_description`.
- `supplier_info` (required): `id_no`, `tax_no`, `reg_no` and `address` with `name1`, `street`, `p_code`, `city`, `country`. All sub-fields are optional.
- **Response:** `200` with the X94 file (`application/xml`) as a download.
- **Errors:** `400` with `{"error": "...", "message": "..."}`, e.g. when the document is not a valid X83 file or a `source_id` does not match.

## Errors {#fehler}

| HTTP status | Cause | Response |
|---|---|---|
| `400` | Invalid request | `{"detail": "<message>"}` or per field `{"<field>": ["<message>"]}`, e.g. `{"channel": ["Channel does not exist"]}` or `{"file": ["Unsupported file type"]}` |
| `401` | API key missing, unknown or expired | `{"detail": "Authentication credentials were not provided."}`, `{"detail": "Invalid token"}` or `{"detail": "Integration has expired"}` |
| `404` | Document not found, channel not granted to the key, page out of range, or path with a trailing `/` | `{"detail": "<message>"}` |

An expired key only works again after you change or remove its expiration date in the Workbench.
