# Using the Master Data API

Workist requires the master data from your ERP system. Via the API you submit it:

- as a CSV file,
- as a link to a publicly reachable CSV file,
- as a list of objects in JSON format, in several batches for large volumes.

Every import replaces the entire data set of the lookup definition: Workist deletes rows that are missing from the new import. So always send the complete data set. Delta imports can be set up by Workist on request.

API Documentation (Swagger): [https://api.workist.com/v1/swagger/](https://api.workist.com/v1/swagger/)

## Basics {#grundlagen}

- **Base URL:** `https://api.workist.com/v1/master-data`
- **Authentication:** the same API key as for the Document API, in the header `Authorization: Bearer <API key>` (see [Quickstart](/en/docs/api-documentation/quickstart.md#api-schluessel)).
- **Lookup definition:** Every import belongs to exactly one lookup definition, given as `lookup_definition_id`. Where to find the ID is described under [Lookup Definition IDs](#lookup-definition-ids).
- **No trailing slash** on paths.

## Endpoints {#endpunkte}

Each master data type has its own endpoint that creates an import. The lookup definition must match the type of the endpoint. Each path links to the endpoint in Swagger.

| Master data | Endpoint | Lookup type | Columns |
|---|---|---|---|
| Articles and customer-specific articles | [`POST /master-data/articles/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/articles_imports_create) | `ARTICLE_LOOKUP` | [Description](/en/docs/master-data/articles.md) |
| Customers | [`POST /master-data/clients/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/clients_imports_create) | `CLIENT_LOOKUP` | [Description](/en/docs/master-data/customers.md) |
| Customer contacts | [`POST /master-data/contacts/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/contacts_imports_create) | `CONTACT_LOOKUP` | [Description](/en/docs/master-data/customer-contacts.md) |
| Delivery and invoice recipients | [`POST /master-data/addresses/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/addresses_imports_create) | `ADDRESS_LOOKUP` | [Description](/en/docs/master-data/delivery-and-invoice-recipients.md) |
| Unit conversion | [`POST /master-data/conversion-factors/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/conversion_factors_imports_create) | `CONVERSION_FACTOR_LOOKUP` | [Description](/en/docs/master-data/unit-conversion.md) |
| Framework agreements | [`POST /master-data/framework-contracts/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/framework_contracts_imports_create) | `FRAMEWORK_CONTRACT_LOOKUP` | [Description](/en/docs/master-data/framework-agreements.md) |
| Orders to be matched | [`POST /master-data/order-matching/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/order_matching_imports_create) | `ORDER_MATCHING_LOOKUP` | [Description](/en/docs/master-data/orders-to-be-matched.md) |
| Quotes | [`POST /master-data/offer-matching/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/offer_matching_imports_create) | `OFFER_MATCHING_LOOKUP` | [Description](/en/docs/master-data/quotes.md) |

In addition there are general endpoints for all types:

| Method | Path | Purpose |
|---|---|---|
| `GET` | [`/master-data/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/imports_list) | List imports (paginated, up to 1000 per page) |
| `POST` | [`/master-data/imports`](https://api.workist.com/v1/swagger/#/Master%20Data/imports_create) | Create an import, for any master data type |
| `GET` | [`/master-data/imports/{import_id}`](https://api.workist.com/v1/swagger/#/Master%20Data/imports_retrieve) | Get the status of an import |
| `POST` | [`/master-data/imports/{import_id}/data`](https://api.workist.com/v1/swagger/#/Master%20Data/imports_data_create) | Append a data batch to a batched import |
| `POST` | [`/master-data/imports/{import_id}/start`](https://api.workist.com/v1/swagger/#/Master%20Data/imports_start_create) | Start a batched import |

### Request fields {#anfragefelder}

An import contains exactly one of the fields `file`, `file_url` or `data`. Send the request as `multipart/form-data` or as JSON. With `multipart/form-data`, pass `data` as JSON text.

| Field | Content |
|---|---|
| `lookup_definition_id` | ID of the lookup definition (required). |
| `batched` | `true`: create a batched import that accepts more data batches. Default: `false` (import starts at once). |
| `file` | CSV file (extension `.csv`), at most 10 MB. |
| `file_url` | Publicly reachable `http`/`https` link to a CSV file. The path ends in `.csv`, the file has at most 10 MB. |
| `data` | List of objects (JSON), at most 10,000 rows. All objects need the same fields. |

The field names of the objects and CSV columns are the column names on the master data page of each type ("Columns" above). Workist converts JSON data into a comma-separated CSV file. If you have set up a [transformer configuration](/en/docs/master-data/transformer-configuration.md) for the lookup definition, e.g. column mappings or a different delimiter, it also applies to imports via the API.

## Response and import status {#import-status}

Workist processes imports in the background. Every call that creates an import, appends data or starts it answers `202` with the import ID:

```json
{"import_id": "0b7c6a52-5d0e-4f43-9a8e-3c2b1d0e9f11"}
```

The `Location` header holds the address of the status. Request it with `GET /master-data/imports/{import_id}` until `status` is `SUCCESS`, `FAILED` or `CANCELED`:

```python
import time

url = f"{base_url}/imports/{import_id}"
while True:
    run = requests.request("GET", url, headers=headers).json()
    if run["status"] in ("SUCCESS", "FAILED", "CANCELED"):
        break
    time.sleep(60)
# e.g. {"import_id": "...", "status": "SUCCESS", "lookup_type": "ARTICLE_LOOKUP", ...}
```

The response contains:

| Field | Content |
|---|---|
| `import_id` | Import ID. |
| `customer_id` | ID of your organisation. |
| `lookup_type` | Master data type, e.g. `ARTICLE_LOOKUP`. |
| `lookup_definition` | ID of the lookup definition. |
| `ingestion_file_url` | Location of the merged import file. |
| `ingestion_file_num_rows` | Number of imported rows. |
| `error_type` | Error type if the import failed. |
| `error_message` | Error message if the import failed. |
| `trigger` | Trigger, e.g. `PUBLIC_API`, `SFTP_FILE_UPLOAD` or `WORKBENCH_FILE_UPLOAD`. |
| `created_at` | Created at. |
| `updated_at` | Last changed at. |
| `started_at` | Processing started at. |
| `finished_at` | Processing finished at. |
| `status` | Status, see below. |
| `input_file_url` | Location of the first submitted file or data batch. |
| `input_file_num_rows` | Rows in that file. |

| `status` | Meaning |
|---|---|
| `PENDING` | Created, not started yet (a batched import is waiting for data or the start call). |
| `RUNNING` | Being processed. |
| `SUCCESS` | Finished successfully. |
| `FAILED` | Failed, see `error_type` and `error_message`. |
| `CANCELED` | Canceled. |

You also see all imports, including those via the API, in the [Master Data Hub](/en/docs/master-data/master-data-hub.md) of the Workbench.

## Limits {#grenzen}

- **Per request:** `data` with at most 10,000 rows, `file` and `file_url` with at most 10 MB. Transfer larger data sets as a [batched import](#massen-importe).
- **Per hour:** If more than 12 imports were created for a lookup definition in the last hour, the API rejects further ones with `400` (`Too many imports scheduled for this lookup definition recently`). Imports from all sources count, including SFTP uploads. An SFTP upload over the limit is skipped without notice.
- **`file_url`:** only `http` and `https`. The API rejects addresses that point to internal or private networks. Workist downloads the file during the request.

## Batched imports {#massen-importe}

For data sets over 10,000 rows or 10 MB, create a batched import and transfer the data in batches:

1. Create a batched import with `"batched": true`, e.g. via `POST /master-data/articles/imports`. The response contains the `import_id`.
2. Append data batches with `POST /master-data/imports/{import_id}/data`, as often as needed. CSV batches all need the same header row.
3. Start the import with `POST /master-data/imports/{import_id}/start`. After that the import accepts no more data.

```python
import requests
import json

def chunk(it, size):
    from itertools import islice
    it = iter(it)
    return iter(lambda: tuple(islice(it, size)), ())

base_url = "https://api.workist.com/v1/master-data"
lookup_definition_id = "<Article Lookup Definition ID>"
token = "<Your Auth Token>"
headers = {"Authorization": f"Bearer {token}"}

articles = [
    {"article_id1": "123", "description": "abc", "order_units": ["PCS", "KG"]},
    {"article_id1": "456", "description": "def", "order_units": ["KG"]},
    {"article_id1": "789", "description": "def", "order_units": ["M"]}
]
articles_chunked = list(chunk(articles, 10000))

url = f"{base_url}/articles/imports"
response = requests.request("POST", url, headers=headers, data={
    "lookup_definition_id": lookup_definition_id,
    "batched": True,
})
import_id = response.json().get("import_id")

for articles_batch in articles_chunked:
    url = f"{base_url}/imports/{import_id}/data"
    response = requests.request("POST", url, headers=headers, data={"data": json.dumps(articles_batch)})

url = f"{base_url}/imports/{import_id}/start"
response = requests.request("POST", url, headers=headers)
```

## Customers {#kunden}

```python
clients = [
    {"client_id1": "C-001", "company_name": "Test SE", "address1": "Test street 1", ...},
    {"client_id1": "C-002", "company_name": "Muster GmbH&Co. KG", ...}
]
url = f"{base_url}/clients/imports"
payload = {"lookup_definition_id": "<your lookup definition>", "data": json.dumps(clients)}
response = requests.request("POST", url, headers=headers, data=payload)
```

## Delivery Addresses {#lieferadressen}

Linked to customers via `partition_id`. The delivery address field should contain company names only.

```python
delivery_addresses = [
    {"partition_id": "C-001", "address_id1": "L-001", "name": "Tester 2 GmbH", "address1": "Test street 2", ...}
]
url = f"{base_url}/addresses/imports"
payload = {"lookup_definition_id": "<your lookup definition>", "data": json.dumps(delivery_addresses)}
response = requests.request("POST", url, headers=headers, data=payload)
```

## Articles {#artikel}

`order_units` must be submitted as an array (e.g. `["KG", "STK"]`).

```python
articles = [
    {"article_id1": "ART-001", "description": "Sensor Module", "order_units": ["PCE", "KG"]},
]
url = f"{base_url}/articles/imports"
payload = {"lookup_definition_id": "<your lookup definition>", "data": json.dumps(articles)}
response = requests.request("POST", url, headers=headers, data=payload)
```

## Customer-Specific Articles {#kundenspezifische-artikel}

Each entry requires `partition_id` (customer reference) and `article_id1` (internal article). `article_id2` contains the customer article number.

```python
client_articles = [
    {"partition_id": "C-001", "article_id1": "ART-001", "article_id2": "CUST-ART-555"},
]
url = f"{base_url}/articles/imports"
payload = {"lookup_definition_id": "<your client-specific lookup definition>", "data": json.dumps(client_articles)}
response = requests.request("POST", url, headers=headers, data=payload)
```

## Errors {#fehler}

| HTTP status | Cause | Response |
|---|---|---|
| `400` | Invalid request, e.g. missing `lookup_definition_id`, file is not a CSV file, too many rows, lookup definition doesn't match the endpoint, import no longer in status `PENDING`, too many imports per hour | `{"detail": "<message>"}` or `["<message>"]` |
| `401` | API key missing | `{"detail": "Authentication credentials were not provided or invalid."}` |
| `503` | The request took longer than 30 seconds | `{"detail": "Please try again later"}` |

## Postman Collection {#postman-collection}

A Postman collection for the master data API is available on request. Contact your Workist representative or email [support@workist.com](mailto:support@workist.com).

## Lookup Definition IDs {#lookup-definition-ids}

Users with the Manager, IT or Admin role create lookup definitions in the [Master Data Hub](/en/docs/master-data/master-data-hub.md). You find the ID in two places:

- in the Master Data Hub: in the overview in the **ID** column and on the detail page of the lookup definition,
- in the channel under **Manage channel** → **Master Data**, below the selected lookup definition.

For more details: [Retrieve Lookup IDs](/en/docs/data-exchange/api.md#lookup-ids)
