---
title: Import
description: The Import API bulk-loads your machine fleet and cutting-tool library from a file, with a preview call that validates every row before anything is written.
---

# Import

The Import API bulk-loads your machine fleet and cutting-tool library from a file, with a preview call that validates every row before anything is written.

> **Auto-generated** from the public OpenAPI spec — this page never
> drifts from the running API. Base URL `https://api.arcnm.io`. Authenticate with
> the `X-API-Key` header (see [Authentication](../authentication.md)).

## Import Machines

`POST /api/v1/import/machines`

Import your machine park from CSV. Machines are created with their
class's standard capabilities (your envelope / spindle columns
override them) at the rate you state, and attached to the target
environment. Re-uploading the same file is safe: existing machines
are recognised by name and never duplicated.

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `csv` | string | yes | The machine list as CSV text (headered; comma, semicolon or tab separated). Columns: name, class, hourly_rate_eur — plus optional operator_eur_per_h, envelope_x_mm / envelope_y_mm / envelope_z_mm, spindle_power_kw and priority. Header spellings are matched tolerantly; at most 500 data rows. |
| `env_id` | string | no | Costing environment the machines join; omit for your default environment. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/import/machines \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "csv": "string"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/import/machines",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "csv": "string"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/import/machines", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "csv": "string"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `applied` | boolean | False for a preview (nothing written); true for a commit. |
| `attached` | integer | Rows that attach (or would attach) an existing item. |
| `created` | integer | Rows that create (or would create) a new item. |
| `env_id` | string | The costing environment the import targets; null in a preview when your organization has no default environment yet. |
| `errored` | integer | Rows with errors; nothing happens for them. |
| `rows` | ImportRowPublic[] | Per-row detail, in file order. |
| `skipped` | integer | Rows skipped as already present. |
| `total_rows` | integer | Data rows read from the file. |

**Example response**

```json
{
  "applied": true,
  "attached": 0,
  "created": 0,
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "errored": 0,
  "rows": [
    {
      "action": "create+attach",
      "errors": [
        "string"
      ],
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "string",
      "parsed": {},
      "row": 0
    }
  ],
  "skipped": 0,
  "total_rows": 0
}
```

## Preview Machine Import

`POST /api/v1/import/machines/preview`

Preview a machine-park import: per row, the values as read, any
errors, and what the import would do — create the machine and attach
it, attach a machine you already have under that name, or skip one
already in the environment. Nothing is written.

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `csv` | string | yes | The machine list as CSV text (headered; comma, semicolon or tab separated). Columns: name, class, hourly_rate_eur — plus optional operator_eur_per_h, envelope_x_mm / envelope_y_mm / envelope_z_mm, spindle_power_kw and priority. Header spellings are matched tolerantly; at most 500 data rows. |
| `env_id` | string | no | Costing environment the machines join; omit for your default environment. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/import/machines/preview \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "csv": "string"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/import/machines/preview",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "csv": "string"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/import/machines/preview", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "csv": "string"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `applied` | boolean | False for a preview (nothing written); true for a commit. |
| `attached` | integer | Rows that attach (or would attach) an existing item. |
| `created` | integer | Rows that create (or would create) a new item. |
| `env_id` | string | The costing environment the import targets; null in a preview when your organization has no default environment yet. |
| `errored` | integer | Rows with errors; nothing happens for them. |
| `rows` | ImportRowPublic[] | Per-row detail, in file order. |
| `skipped` | integer | Rows skipped as already present. |
| `total_rows` | integer | Data rows read from the file. |

**Example response**

```json
{
  "applied": true,
  "attached": 0,
  "created": 0,
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "errored": 0,
  "rows": [
    {
      "action": "create+attach",
      "errors": [
        "string"
      ],
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "string",
      "parsed": {},
      "row": 0
    }
  ],
  "skipped": 0,
  "total_rows": 0
}
```

## Import Tools

`POST /api/v1/import/tools`

Import your tool crib from CSV. Tools are created with their
kind-specific properties validated (a saw blade needs its kerf and
construction; rotating cutters need nothing extra) and attached to
your default environment. Re-uploading the same file is safe:
existing tools are recognised by name and kind, never duplicated.

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `csv` | string | yes | The tool list as CSV text (headered). Columns: name, kind — plus optional diameter_mm, teeth, cutting_material, price_eur, tool_life_min, vendor, item_no, coating, notes, and any kind-specific property as its own column (e.g. kerf_mm, blade_type and format for a saw blade, life_quantity for non-rotating tools). At most 1000 data rows. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/import/tools \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "csv": "string"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/import/tools",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "csv": "string"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/import/tools", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "csv": "string"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `applied` | boolean | False for a preview (nothing written); true for a commit. |
| `attached` | integer | Rows that attach (or would attach) an existing item. |
| `created` | integer | Rows that create (or would create) a new item. |
| `env_id` | string | The costing environment the import targets; null in a preview when your organization has no default environment yet. |
| `errored` | integer | Rows with errors; nothing happens for them. |
| `rows` | ImportRowPublic[] | Per-row detail, in file order. |
| `skipped` | integer | Rows skipped as already present. |
| `total_rows` | integer | Data rows read from the file. |

**Example response**

```json
{
  "applied": true,
  "attached": 0,
  "created": 0,
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "errored": 0,
  "rows": [
    {
      "action": "create+attach",
      "errors": [
        "string"
      ],
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "string",
      "parsed": {},
      "row": 0
    }
  ],
  "skipped": 0,
  "total_rows": 0
}
```

## Preview Tool Import

`POST /api/v1/import/tools/preview`

Preview a tool-crib import: per row, the values as read (including
the kind-specific properties), any errors, and what the import would
do. Nothing is written.

**Request body** (`application/json`)

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `csv` | string | yes | The tool list as CSV text (headered). Columns: name, kind — plus optional diameter_mm, teeth, cutting_material, price_eur, tool_life_min, vendor, item_no, coating, notes, and any kind-specific property as its own column (e.g. kerf_mm, blade_type and format for a saw blade, life_quantity for non-rotating tools). At most 1000 data rows. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/import/tools/preview \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "csv": "string"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/import/tools/preview",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "csv": "string"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/import/tools/preview", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "csv": "string"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `409` | `conflict` | A conflicting change, or an `Idempotency-Key` reused with a different body. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `applied` | boolean | False for a preview (nothing written); true for a commit. |
| `attached` | integer | Rows that attach (or would attach) an existing item. |
| `created` | integer | Rows that create (or would create) a new item. |
| `env_id` | string | The costing environment the import targets; null in a preview when your organization has no default environment yet. |
| `errored` | integer | Rows with errors; nothing happens for them. |
| `rows` | ImportRowPublic[] | Per-row detail, in file order. |
| `skipped` | integer | Rows skipped as already present. |
| `total_rows` | integer | Data rows read from the file. |

**Example response**

```json
{
  "applied": true,
  "attached": 0,
  "created": 0,
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "errored": 0,
  "rows": [
    {
      "action": "create+attach",
      "errors": [
        "string"
      ],
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "string",
      "parsed": {},
      "row": 0
    }
  ],
  "skipped": 0,
  "total_rows": 0
}
```
