---
title: Job Nests
description: The Job Nests API nests a backlog of sheet-metal parts together on shared sheets: preview what a job nest would take, start one, read each order's and each…
---

# Job Nests

The Job Nests API nests a backlog of sheet-metal parts together on shared sheets: preview what a job nest would take, start one, read each order's and each part's share of the sheets, export its sheets as cutting files (DXF, SVG or the JSON nest plan), and cancel it.

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

## List job nests

`GET /api/v1/nest-runs`

The job nests your organization started, newest first. The nest an
assembly calculation runs for its own sheet components belongs to that
calculation and is not listed.

A plain array: the page position travels in the `Link`,
`X-Next-Cursor` and `X-Has-More` response headers.

> **Paginated.** Pass `cursor` (from the previous response) to fetch the next page; `limit` caps the page size.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `env_id` | query | string | no | Only this environment's job nests. A filter narrows the list: an id that matches no environment of your organization lists nothing. |
| `cursor` | query | string | no | Opaque position token from the previous page's `next_cursor` (or the `Link` / `X-Next-Cursor` response header). Omit it for the first page. Keep every other query parameter identical for the whole walk — a cursor replayed against different filters is rejected. |
| `limit` | query | integer | no | Maximum rows to return in one page. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/nest-runs \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/nest-runs",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
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. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


## Start a job nest

`POST /api/v1/nest-runs`

Nest a backlog selection together, sheet by sheet.

Every line is read off its calculation: the part's shape as its
single-part price nested it, the thickness it is bought in, the margin
and spacing, the material and its price. Lines are grouped by
material, thickness and allowances — one nest never mixes them — and
the job nest is queued: poll `GET /nest-runs/{run_id}` until it ends.
It cuts from the environment's remnants of each material and thickness
before it buys a sheet — a remnant it cuts from leaves stock, and the
reusable rests its sheets leave go in. Nothing here changes a
calculation's price.

One job nest at a time: while one of your organization's is queued or
running, a second is refused — wait until it ends, or cancel it.

When the month's runs are already used, the job nest is kept as a
`draft`: stored, not queued, spending no run. Start it with `POST
/nest-runs/{run_id}/start` once the allowance has a run for it, or
cancel it. Your organization keeps at most 20 drafts.

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `allocation_rule` | `area_proportional` \| `equal_per_sheet` | no | How a sheet's cost is shared by the parts on it: by their area, or equally. |
| `batch_id` | string | no | Nest every calculation of this batch priced in the environment, each at its lot size. |
| `env_id` | string | yes | The costing environment the run nests in: its stock and remnants. |
| `folder_id` | string | no | Nest the newest calculation priced in the environment of every part filed in this folder or below it, each at its lot size. |
| `group_budget_s` | number | no | The longest each material and thickness may be nested for, in seconds of computing time: longer can use less sheet. |
| `items` | NestRunItemIn[] | no |  |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/nest-runs \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/nest-runs",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `404` | The environment, batch or folder is not your organization's (`nest_run_selection_not_found`). |
| `409` | A job nest of your organization is still queued or running (`nest_run_in_flight`): wait until it ends, or cancel it. Or the month's runs are used and your organization keeps the most drafts it may (`nest_run_too_many_drafts`): start one or cancel one. |
| `422` | An item can't be nested — `details.items` names each with its reason (`nest_run_items_unusable`) — or the batch or folder holds nothing that can (`nest_run_nothing_to_nest`), or more calculations than one run takes (`nest_run_selection_too_large`), or a line asks for more copies than one line takes (`nest_run_line_too_large`), or the lines together more than one run takes (`nest_run_too_many_copies`), or `group_budget_s` is past what your organization may ask for (`nest_run_budget_too_long`). |

**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** `201`

| Field | Type | Description |
| --- | --- | --- |
| `allocation_rule` | `area_proportional` \| `equal_per_sheet` | How each sheet's cost is shared by the parts on it. |
| `as_of` | string | The day its stock and prices are read at. |
| `copies` | integer | Copies it nests, over all its lines. |
| `counted_at` | string | When it was released; null while a simulation. |
| `counts_toward_savings` | boolean | True once the job nest was released: its stock is booked and what it saves adds to the total saved by nesting. False: a simulation, whose saving is potential only. |
| `created_at` | string |  |
| `created_by_user_id` | string |  |
| `env_id` | string | The costing environment it nests in. |
| `error` | string | Why it stopped, when it did not finish. |
| `finished_at` | string |  |
| `group_budget_s` | number | The longest each material and thickness may be nested for, in seconds of computing time. |
| `groups_done` | integer | Of those, the ones finished so far: nested, or not nestable. |
| `groups_total` | integer | Materials and thicknesses it nests — each is nested on its own. |
| `id` | string |  |
| `lines` | integer | Lines it nests. |
| `savings` | NestRunSavings | What it saves, once it ends; null before, or where no line compares. |
| `sheets` | integer | Sheets and remnants the nested materials and thicknesses are cut from; set once the job nest ends. |
| `started_at` | string |  |
| `status` | `draft` \| `queued` \| `running` \| `solved` \| `failed` \| `cancelled` | `draft`: kept, not queued — it was started while the month's runs were used, so it spends no run until `POST /nest-runs/{run_id}/start` starts it. `queued`: waiting to start. `running`: nesting, several materials and thicknesses at a time. `solved`: every material and thickness is nested. `failed`: it stopped, `error` says why; what it nested before stands. `cancelled`: stopped on request; what it nested before stands. |

**Example response**

```json
{
  "allocation_rule": "area_proportional",
  "as_of": "2026-06-01",
  "copies": 0,
  "counted_at": "2026-06-01T12:00:00Z",
  "counts_toward_savings": false,
  "created_at": "2026-06-01T12:00:00Z",
  "created_by_user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "error": "string",
  "finished_at": "2026-06-01T12:00:00Z",
  "group_budget_s": 184,
  "groups_done": 0,
  "groups_total": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "lines": 0,
  "savings": {
    "alone": 0,
    "alone_kg": 0,
    "alone_sheets": 0,
    "by_order": [
      {
        "alone": 0,
        "alone_kg": 0,
        "alone_sheets": 0,
        "copies": 0,
        "currency": "EUR",
        "lines": 0,
        "lines_compared": 0,
        "nested": 0,
        "nested_kg": 0,
        "nested_sheets": 0,
        "order_ref": "string",
        "rectangular": 0,
        "rectangular_kg": 0,
        "rectangular_sheets": 0,
        "saved": 0,
        "saved_combining": 0,
        "saved_combining_kg": 0,
        "saved_kg": 0,
        "saved_true_shape": 0,
        "saved_true_shape_kg": 0,
        "split_lines": 0
      }
    ],
    "copies": 0,
    "currency": "EUR",
    "lines": 0,
    "lines_compared": 0,
    "nested": 0,
    "nested_kg": 0,
    "nested_sheets": 0,
    "rectangular": 0,
    "rectangular_kg": 0,
    "rectangular_sheets": 0,
    "saved": 0,
    "saved_combining": 0,
    "saved_combining_kg": 0,
    "saved_kg": 0,
    "saved_true_shape": 0,
    "saved_true_shape_kg": 0,
    "split_lines": 0
  },
  "sheets": 0,
  "started_at": "2026-06-01T12:00:00Z",
  "status": "draft"
}
```

## Read a job nest

`GET /api/v1/nest-runs/{run_id}`

One job nest: each material and thickness with its sheets and
utilisation, each line's and each order's share of the sheets, and the
remnants it used, holds and created.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | Identifier of the run. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/nest-runs/{run_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `402` | An assembly calculation ran this job nest for its own sheet components, and your organization has no active Nesting add-on (`addon_required`). |
| `404` | No run with this id in your organization (`nest_run_not_found`). |
| `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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `allocation_rule` | `area_proportional` \| `equal_per_sheet` | How each sheet's cost is shared by the parts on it. |
| `as_of` | string | The day its stock and prices are read at. |
| `copies` | integer | Copies it nests, over all its lines. |
| `counted_at` | string | When it was released; null while a simulation. |
| `counts_toward_savings` | boolean | True once the job nest was released: its stock is booked and what it saves adds to the total saved by nesting. False: a simulation, whose saving is potential only. |
| `created_at` | string |  |
| `created_by_user_id` | string |  |
| `env_id` | string | The costing environment it nests in. |
| `error` | string | Why it stopped, when it did not finish. |
| `finished_at` | string |  |
| `group_budget_s` | number | The longest each material and thickness may be nested for, in seconds of computing time. |
| `groups` | NestRunGroup[] |  |
| `groups_done` | integer | Of those, the ones finished so far: nested, or not nestable. |
| `groups_total` | integer | Materials and thicknesses it nests — each is nested on its own. |
| `id` | string |  |
| `items` | NestRunLine[] |  |
| `left_out` | LeftOutLine[] | Calculations of the batch or folder it does not take, each with why. |
| `lines` | integer | Lines it nests. |
| `orders` | OrderTotal[] | Each order's lines totalled, once the job nest ends. |
| `remnants_created` | RemnantPublic[] | The reusable rests its sheets left, now in stock. |
| `remnants_held` | RemnantPublic[] | Remnants it holds while it runs, kept from other job nests. |
| `remnants_used` | RemnantPublic[] | Remnants from stock its sheets were cut from. |
| `savings` | NestRunSavings | What it saves, once it ends; null before, or where no line compares. |
| `sheets` | integer | Sheets and remnants the nested materials and thicknesses are cut from; set once the job nest ends. |
| `started_at` | string |  |
| `status` | `draft` \| `queued` \| `running` \| `solved` \| `failed` \| `cancelled` | `draft`: kept, not queued — it was started while the month's runs were used, so it spends no run until `POST /nest-runs/{run_id}/start` starts it. `queued`: waiting to start. `running`: nesting, several materials and thicknesses at a time. `solved`: every material and thickness is nested. `failed`: it stopped, `error` says why; what it nested before stands. `cancelled`: stopped on request; what it nested before stands. |

**Example response**

```json
{
  "allocation_rule": "area_proportional",
  "as_of": "2026-06-01",
  "copies": 0,
  "counted_at": "2026-06-01T12:00:00Z",
  "counts_toward_savings": false,
  "created_at": "2026-06-01T12:00:00Z",
  "created_by_user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "error": "string",
  "finished_at": "2026-06-01T12:00:00Z",
  "group_budget_s": 184,
  "groups": [
    {
      "active_revision": 0,
      "error": "string",
      "finished_at": "2026-06-01T12:00:00Z",
      "gauge_mm": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "material_category": "string",
      "material_grade_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "position": 0,
      "revisions": 0,
      "sheets": 0,
      "started_at": "2026-06-01T12:00:00Z",
      "status": "pending",
      "utilisation_net": 0
    }
  ],
  "groups_done": 0,
  "groups_total": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "items": [
    {
      "allocation": {
        "copies": 0,
        "currency": "EUR",
        "net": 0,
        "net_per_copy": 0,
        "purchased": 0,
        "remnant_credit": 0,
        "scrap_credit": 0
      },
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "due": "2026-06-01",
      "group_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "order_ref": "string",
      "position": 0,
      "priority": 0,
      "quantity": 0
    }
  ],
  "left_out": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string"
    }
  ],
  "lines": 0,
  "orders": [
    {
      "copies": 0,
      "currency": "EUR",
      "net": 0,
      "net_per_copy": 0,
      "order_ref": "string",
      "purchased": 0,
      "remnant_credit": 0,
      "scrap_credit": 0
    }
  ],
  "remnants_created": [
    {
      "consumed_at": "2026-06-01T12:00:00Z",
      "created_at": "2026-06-01T12:00:00Z",
      "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "gauge_mm": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "length_mm": 0,
      "material_category": "string",
      "material_grade_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "origin_group_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "quantity": 0,
      "reserved_by_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "status": "available",
      "width_mm": 0
    }
  ],
  "remnants_held": [
    {
      "consumed_at": "2026-06-01T12:00:00Z",
      "created_at": "2026-06-01T12:00:00Z",
      "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "gauge_mm": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "length_mm": 0,
      "material_category": "string",
      "material_grade_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "origin_group_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "quantity": 0,
      "reserved_by_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "status": "available",
      "width_mm": 0
    }
  ],
  "remnants_used": [
    {
      "consumed_at": "2026-06-01T12:00:00Z",
      "created_at": "2026-06-01T12:00:00Z",
      "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "gauge_mm": 0,
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "length_mm": 0,
      "material_category": "string",
      "material_grade_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "origin_group_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "quantity": 0,
      "reserved_by_run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "status": "available",
      "width_mm": 0
    }
  ],
  "savings": {
    "alone": 0,
    "alone_kg": 0,
    "alone_sheets": 0,
    "by_order": [
      {
        "alone": 0,
        "alone_kg": 0,
        "alone_sheets": 0,
        "copies": 0,
        "currency": "EUR",
        "lines": 0,
        "lines_compared": 0,
        "nested": 0,
        "nested_kg": 0,
        "nested_sheets": 0,
        "order_ref": "string",
        "rectangular": 0,
        "rectangular_kg": 0,
        "rectangular_sheets": 0,
        "saved": 0,
        "saved_combining": 0,
        "saved_combining_kg": 0,
        "saved_kg": 0,
        "saved_true_shape": 0,
        "saved_true_shape_kg": 0,
        "split_lines": 0
      }
    ],
    "copies": 0,
    "currency": "EUR",
    "lines": 0,
    "lines_compared": 0,
    "nested": 0,
    "nested_kg": 0,
    "nested_sheets": 0,
    "rectangular": 0,
    "rectangular_kg": 0,
    "rectangular_sheets": 0,
    "saved": 0,
    "saved_combining": 0,
    "saved_combining_kg": 0,
    "saved_kg": 0,
    "saved_true_shape": 0,
    "saved_true_shape_kg": 0,
    "split_lines": 0
  },
  "sheets": 0,
  "started_at": "2026-06-01T12:00:00Z",
  "status": "draft"
}
```

## Cancel a job nest

`POST /api/v1/nest-runs/{run_id}/cancel`

Stop a job nest that has not ended.

The materials and thicknesses already nested keep their result; the
others are cancelled, and every remnant the job nest holds goes back
to stock. A job nest cancelled before any material and thickness was
nested gives its run back to the month's allowance; once one is nested,
it counts as a run, as a job nest that stops on an error does. A draft
is discarded the same way.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | Identifier of the run. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/nest-runs/{run_id}/cancel \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/cancel",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/cancel", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `402` | An assembly calculation ran this job nest for its own sheet components, and your organization has no active Nesting add-on (`addon_required`). |
| `404` | No run with this id in your organization (`nest_run_not_found`). |
| `409` | The run has already ended (`nest_run_not_cancellable`), or it is an assembly calculation's own nest, which only that assembly changes (`nest_run_internal`). |
| `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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `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 |
| --- | --- | --- |
| `allocation_rule` | `area_proportional` \| `equal_per_sheet` | How each sheet's cost is shared by the parts on it. |
| `as_of` | string | The day its stock and prices are read at. |
| `copies` | integer | Copies it nests, over all its lines. |
| `counted_at` | string | When it was released; null while a simulation. |
| `counts_toward_savings` | boolean | True once the job nest was released: its stock is booked and what it saves adds to the total saved by nesting. False: a simulation, whose saving is potential only. |
| `created_at` | string |  |
| `created_by_user_id` | string |  |
| `env_id` | string | The costing environment it nests in. |
| `error` | string | Why it stopped, when it did not finish. |
| `finished_at` | string |  |
| `group_budget_s` | number | The longest each material and thickness may be nested for, in seconds of computing time. |
| `groups_done` | integer | Of those, the ones finished so far: nested, or not nestable. |
| `groups_total` | integer | Materials and thicknesses it nests — each is nested on its own. |
| `id` | string |  |
| `lines` | integer | Lines it nests. |
| `savings` | NestRunSavings | What it saves, once it ends; null before, or where no line compares. |
| `sheets` | integer | Sheets and remnants the nested materials and thicknesses are cut from; set once the job nest ends. |
| `started_at` | string |  |
| `status` | `draft` \| `queued` \| `running` \| `solved` \| `failed` \| `cancelled` | `draft`: kept, not queued — it was started while the month's runs were used, so it spends no run until `POST /nest-runs/{run_id}/start` starts it. `queued`: waiting to start. `running`: nesting, several materials and thicknesses at a time. `solved`: every material and thickness is nested. `failed`: it stopped, `error` says why; what it nested before stands. `cancelled`: stopped on request; what it nested before stands. |

**Example response**

```json
{
  "allocation_rule": "area_proportional",
  "as_of": "2026-06-01",
  "copies": 0,
  "counted_at": "2026-06-01T12:00:00Z",
  "counts_toward_savings": false,
  "created_at": "2026-06-01T12:00:00Z",
  "created_by_user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "error": "string",
  "finished_at": "2026-06-01T12:00:00Z",
  "group_budget_s": 184,
  "groups_done": 0,
  "groups_total": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "lines": 0,
  "savings": {
    "alone": 0,
    "alone_kg": 0,
    "alone_sheets": 0,
    "by_order": [
      {
        "alone": 0,
        "alone_kg": 0,
        "alone_sheets": 0,
        "copies": 0,
        "currency": "EUR",
        "lines": 0,
        "lines_compared": 0,
        "nested": 0,
        "nested_kg": 0,
        "nested_sheets": 0,
        "order_ref": "string",
        "rectangular": 0,
        "rectangular_kg": 0,
        "rectangular_sheets": 0,
        "saved": 0,
        "saved_combining": 0,
        "saved_combining_kg": 0,
        "saved_kg": 0,
        "saved_true_shape": 0,
        "saved_true_shape_kg": 0,
        "split_lines": 0
      }
    ],
    "copies": 0,
    "currency": "EUR",
    "lines": 0,
    "lines_compared": 0,
    "nested": 0,
    "nested_kg": 0,
    "nested_sheets": 0,
    "rectangular": 0,
    "rectangular_kg": 0,
    "rectangular_sheets": 0,
    "saved": 0,
    "saved_combining": 0,
    "saved_combining_kg": 0,
    "saved_kg": 0,
    "saved_true_shape": 0,
    "saved_true_shape_kg": 0,
    "split_lines": 0
  },
  "sheets": 0,
  "started_at": "2026-06-01T12:00:00Z",
  "status": "draft"
}
```

## Export a job nest's sheets for cutting

`GET /api/v1/nest-runs/{run_id}/export`

The sheets of a job nest as cutting files.

Every solved material and thickness contributes its sheets: each
distinct layout once, with how many identical sheets it stands for.
A sheet's DXF holds the sheet's rectangle, the usable area, the reusable
rest it leaves, every copy's outer contour and holes, and a label per
copy with its part reference and order; the header and a title state
the job nest, the sheet, the material and thickness, the format or
remnant, the count and the utilisation. Contours are within 0.2 mm of
the unfolded flat pattern; arcs are polylines. The JSON nest plan
carries the same geometry as data. A job nest that is still running
exports what is solved so far. The sheets are each material and
thickness's ACTIVE revision — the layout the job nest reads — unless
`revision` names another; then every exported material and thickness
must have that revision. A file still being built after a minute is
answered 503 with `Retry-After`; `/export-url` never waits.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | Identifier of the run. |
| `format` | query | `dxf` \| `svg` \| `json` | no | `dxf` (R2010, millimetres: one block per part and one insert per copy, on the layers SHEET, MARGIN, REMNANT, CUT_OUTER, CUT_INNER and LABEL), `svg` (the same geometry, the view box in millimetres) or `json` (the nest plan: every part's contour and every sheet's placements). |
| `sheet` | query | string | no | `all`: every sheet — a zip of one DXF or SVG per sheet with `plan.json` and `manifest.csv`, or the whole nest plan as JSON. Or one sheet's key, `<group>:<index>` as the plan names it, for that sheet's file. |
| `exploded` | query | boolean | no | DXF only: write each copy's contours as plain closed polylines in place, no blocks — for a cutting system that does not read block references. |
| `revision` | query | integer | no | Export this revision of every material and thickness's sheets instead of the active one; 1 is the job nest's own layout. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/nest-runs/{run_id}/export \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/export",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/export", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | The file — `Content-Disposition` names it — or, for `format=json`, the nest plan. |
| `402` | An assembly calculation ran this job nest for its own sheet components, and your organization has no active Nesting add-on (`addon_required`). |
| `404` | No job nest with this id in your organization (`nest_run_not_found`), or `sheet` names no sheet of it (`nest_run_sheet_not_found`). |
| `409` | No material and thickness of the job nest is solved yet (`nest_run_nothing_to_export`). |
| `422` | Validation Error |
| `503` | The file is still being built (`nest_export_timeout`): ask again after `Retry-After` seconds. |

**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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `parts` | NestPlanPart[] |  |
| `sheets` | NestPlanSheet[] |  |
| `source` | NestPlanSource |  |
| `tolerance` | NestPlanTolerance |  |
| `units` | string |  |
| `v` | integer | The schema version. |

**Example response**

```json
{
  "parts": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "holes": [
        []
      ],
      "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "string",
      "net_area_mm2": 0,
      "order_ref": "string",
      "outline": [
        []
      ],
      "placed": 0,
      "quantity": 0,
      "ref": "string"
    }
  ],
  "sheets": [
    {
      "container": {
        "key": "string",
        "kind": "format",
        "length_mm": 0,
        "width_mm": 0
      },
      "count": 0,
      "gauge_mm": 0,
      "group": 0,
      "index": 0,
      "key": "string",
      "margin_mm": 0,
      "material": "string",
      "placements": [
        {}
      ],
      "remnant": {
        "length_mm": 0,
        "width_mm": 0,
        "x_mm": 0,
        "y_mm": 0
      },
      "repeat": 0,
      "spacing_mm": 0,
      "utilisation": 0
    }
  ],
  "source": {
    "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "created_at": "2026-06-01T12:00:00Z",
    "environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
    "finished_at": "2026-06-01T12:00:00Z",
    "kind": "run",
    "run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  },
  "tolerance": {
    "arcs": "string",
    "contour_mm": 0,
    "note": "string"
  },
  "units": "string",
  "v": 0
}
```

## Link to a job nest's cutting files

`GET /api/v1/nest-runs/{run_id}/export-url`

The file `GET /nest-runs/{run_id}/export` answers, as a short-lived
download link — without waiting for it to be built.

200: the file is ready; `url` downloads it (no credentials, until
`expires_in_s` runs out) under `file_name`. 202: it is being built;
ask again after `Retry-After` seconds. A build that was refused is
answered with its own code and status; `fresh=true` builds it again.
Same parameters, scope and refusals as the export.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | Identifier of the run. |
| `format` | query | `dxf` \| `svg` \| `json` | no | `dxf` (R2010, millimetres: one block per part and one insert per copy, on the layers SHEET, MARGIN, REMNANT, CUT_OUTER, CUT_INNER and LABEL), `svg` (the same geometry, the view box in millimetres) or `json` (the nest plan: every part's contour and every sheet's placements). |
| `sheet` | query | string | no | `all`: every sheet — a zip of one DXF or SVG per sheet with `plan.json` and `manifest.csv`, or the whole nest plan as JSON. Or one sheet's key, `<group>:<index>` as the plan names it, for that sheet's file. |
| `exploded` | query | boolean | no | DXF only: write each copy's contours as plain closed polylines in place, no blocks — for a cutting system that does not read block references. |
| `revision` | query | integer | no | Export this revision of every material and thickness's sheets instead of the active one; 1 is the job nest's own layout. |
| `fresh` | query | boolean | no | Build the file again when its last build was refused and nothing is building it now. Without it, a refusal is answered again for a few minutes. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/nest-runs/{run_id}/export-url \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/export-url",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/export-url", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `202` | The file is being built: ask again after `Retry-After` seconds. |
| `402` | An assembly calculation ran this job nest for its own sheet components, and your organization has no active Nesting add-on (`addon_required`). |
| `404` | No job nest with this id in your organization (`nest_run_not_found`), or `sheet` names no sheet of it (`nest_run_sheet_not_found`). |
| `409` | No material and thickness of the job nest is solved yet (`nest_run_nothing_to_export`). |
| `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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `content_type` | string |  |
| `expires_in_s` | integer | Seconds until the link expires. |
| `file_name` | string |  |
| `size_bytes` | integer | The file's size in bytes. |
| `status` | string |  |
| `url` | string | GET it, without credentials, before it expires; the response names the file. |

**Example response**

```json
{
  "content_type": "string",
  "expires_in_s": 0,
  "file_name": "string",
  "size_bytes": 204800,
  "status": "string",
  "url": "string"
}
```

## Check a layout of a job nest's sheets

`POST /api/v1/nest-runs/{run_id}/groups/{group_id}/layout-check`

What the cutter would find in this layout, before anything is stored.

Send the whole layout of one material and thickness: every sheet with
the stock format or remnant it is cut from and its placements. The
answer names every pair of parts closer than the spacing with the gap
between them, every part in the margin or off the sheet, every line
short or over its quantity, every container the job nest cannot cut
from, every copy turned against the environment's grain direction or
mirrored where it allows no mirroring (as the job nest read its shop
practice), and each sheet's utilisation and rest. `ok` is what
`POST …/revisions` accepts; `complete` says every line is placed
exactly as often as ordered. Nothing is stored.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | The job nest. |
| `group_id` | path | string | yes | The material and thickness. |

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `note` | string | no | Why this layout (kept with the revision; ignored by the check). |
| `sheets` | NestSheetIn[] | yes |  |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/layout-check \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sheets": [
      {
        "container_id": "string",
        "placements": [
          {
            "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "mirrored": false,
            "rot_deg": 0,
            "x": 0,
            "y": 0
          }
        ],
        "repeat": 1
      }
    ]
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/layout-check",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "sheets": [
            {
                "container_id": "string",
                "placements": [
                    {
                        "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                        "mirrored": False,
                        "rot_deg": 0,
                        "x": 0,
                        "y": 0
                    }
                ],
                "repeat": 1
            }
        ]
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/layout-check", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "sheets": [
      {
        "container_id": "string",
        "placements": [
          {
            "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "mirrored": false,
            "rot_deg": 0,
            "x": 0,
            "y": 0
          }
        ],
        "repeat": 1
      }
    ]
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `404` | No job nest with this id in your organization (`nest_run_not_found`), or no material and thickness with this id in it (`nest_run_group_not_found`). |
| `409` | This material and thickness is not nested yet (`nest_run_not_editable`). |
| `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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `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 |
| --- | --- | --- |
| `complete` | boolean | Every line is placed exactly as often as its quantity: `missing` and `extra` are empty. |
| `containers_unknown` | string[] |  |
| `extra` | NestLineCount[] | Lines with more copies placed than demanded (`placed` = the surplus). |
| `lines` | NestLineCount[] |  |
| `missing` | NestLineCount[] | Lines with fewer copies placed than demanded (`placed` = the shortfall). |
| `ok` | boolean | Every sheet can be cut, every copy keeps the environment's shop practice, and no remnant is over-used: what `POST …/revisions` accepts. A line short of its quantity or over it does not make a layout unacceptable — `missing` and `extra` state it, and a revision is priced on the copies it cuts. |
| `remnants_over` | NestRemnantOver[] |  |
| `sheets` | NestSheetCheck[] |  |
| `sheets_total` | integer | Sheets, every one as often as it repeats. |
| `utilisation_net` | number | The parts' area over the sheet area used, reusable rests not counted. |

**Example response**

```json
{
  "complete": true,
  "containers_unknown": [
    "string"
  ],
  "extra": [
    {
      "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "placed": 0,
      "quantity": 0
    }
  ],
  "lines": [
    {
      "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "placed": 0,
      "quantity": 0
    }
  ],
  "missing": [
    {
      "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "placed": 0,
      "quantity": 0
    }
  ],
  "ok": true,
  "remnants_over": [
    {
      "available": 0,
      "container_id": "string",
      "pieces": 0
    }
  ],
  "sheets": [
    {
      "against_shop_practice": [
        {}
      ],
      "clashes": [
        {}
      ],
      "clashes_total": 0,
      "container_id": "string",
      "container_kind": "string",
      "container_unknown": true,
      "count": 0,
      "index": 0,
      "length_mm": 0,
      "net_area_mm2": 0,
      "ok": true,
      "outside": [
        0
      ],
      "repeat": 0,
      "rest": {
        "length_mm": 0,
        "reusable": true,
        "width_mm": 0,
        "x_mm": 0,
        "y_mm": 0
      },
      "unknown_lines": [
        "3fa85f64-5717-4562-b3fc-2c963f66afa6"
      ],
      "utilisation": 0,
      "width_mm": 0
    }
  ],
  "sheets_total": 0,
  "utilisation_net": 0
}
```

## List a material and thickness's revisions

`GET /api/v1/nest-runs/{run_id}/groups/{group_id}/revisions`

Every revision of this material and thickness's sheets, oldest
first — the job nest's own result is revision 1 — with what each saves;
the active one is marked. Layouts are read per revision.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | The job nest. |
| `group_id` | path | string | yes | The material and thickness. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `402` | An assembly calculation ran this job nest for its own sheet components, and your organization has no active Nesting add-on (`addon_required`). |
| `404` | No job nest with this id in your organization (`nest_run_not_found`), or no material and thickness with this id in it (`nest_run_group_not_found`). |
| `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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


## Post a layout as the next revision

`POST /api/v1/nest-runs/{run_id}/groups/{group_id}/revisions`

Store this layout as the material and thickness's next revision and
make it the active one.

The body is the one `POST …/layout-check` takes; a layout the check
finds `ok` — every sheet cuttable, no remnant over-used — is accepted.
A line short of its quantity or over it is accepted as posted and
priced on the copies the layout cuts. It is priced as the job nest's
own result is (the run's allocation rule, the material's price and
credits), and the group's sheets, every line's share, the run's order
totals and what it saves follow the new revision. Earlier
revisions stay as they are: a calculation priced on a line keeps the
revision it read. To go back to an earlier layout, post it again. A
released job nest is frozen.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | The job nest. |
| `group_id` | path | string | yes | The material and thickness. |

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `note` | string | no | Why this layout (kept with the revision; ignored by the check). |
| `sheets` | NestSheetIn[] | yes |  |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sheets": [
      {
        "container_id": "string",
        "placements": [
          {
            "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "mirrored": false,
            "rot_deg": 0,
            "x": 0,
            "y": 0
          }
        ],
        "repeat": 1
      }
    ]
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "sheets": [
            {
                "container_id": "string",
                "placements": [
                    {
                        "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
                        "mirrored": False,
                        "rot_deg": 0,
                        "x": 0,
                        "y": 0
                    }
                ],
                "repeat": 1
            }
        ]
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "sheets": [
      {
        "container_id": "string",
        "placements": [
          {
            "line_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
            "mirrored": false,
            "rot_deg": 0,
            "x": 0,
            "y": 0
          }
        ],
        "repeat": 1
      }
    ]
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `404` | No job nest with this id in your organization (`nest_run_not_found`), or no material and thickness with this id in it (`nest_run_group_not_found`). |
| `409` | The job nest has not ended yet, or this material and thickness is not nested (`nest_run_not_editable`, `details.reason`); the job nest was released and its stock booked, so its sheets are frozen (`nest_run_released`); or it is an assembly calculation's own nest, whose layout prices that assembly's components and only that assembly changes (`nest_run_internal`). |
| `422` | The layout cannot be cut as posted, or breaks the environment's shop practice (`nest_layout_invalid`): `details` is the check `POST …/layout-check` answers. A line short of its quantity or over it is no refusal. |

**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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `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** `201`

| Field | Type | Description |
| --- | --- | --- |
| `active` | boolean | The one the job nest reads its sheets from. |
| `created_at` | string |  |
| `created_by_user_id` | string |  |
| `note` | string |  |
| `revision` | integer |  |
| `savings` | object | What the lines save under this revision: rectangular blanks, each part nested alone, the parts nested together, what nesting saved and its split, summed and per order (`by_order`), and per line (`by_line`) — see the job nest's `savings`. |
| `sheets` | integer | Sheets and remnants it cuts. |
| `source` | string | `nest` — the job nest's own layout; `edit` — one posted here. |
| `utilisation_net` | number |  |

**Example response**

```json
{
  "active": true,
  "created_at": "2026-06-01T12:00:00Z",
  "created_by_user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "note": "string",
  "revision": 0,
  "savings": {},
  "sheets": 0,
  "source": "string",
  "utilisation_net": 0
}
```

## Read a revision with its sheets

`GET /api/v1/nest-runs/{run_id}/groups/{group_id}/revisions/{revision}`

One revision with every sheet's placements — the body
`POST …/revisions` takes, as stored, to adjust and post again.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | The job nest. |
| `group_id` | path | string | yes | The material and thickness. |
| `revision` | path | integer | yes | The revision number. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions/{revision} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions/{revision}",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/groups/{group_id}/revisions/{revision}", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `402` | An assembly calculation ran this job nest for its own sheet components, and your organization has no active Nesting add-on (`addon_required`). |
| `404` | No job nest, material and thickness, or revision with this id (`nest_run_not_found`, `nest_run_group_not_found`, `nest_run_revision_not_found`). |
| `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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `active` | boolean | The one the job nest reads its sheets from. |
| `created_at` | string |  |
| `created_by_user_id` | string |  |
| `group_id` | string |  |
| `length_width_mm` | object | Each container's length and width, by container id. |
| `note` | string |  |
| `revision` | integer |  |
| `savings` | object | What the lines save under this revision: rectangular blanks, each part nested alone, the parts nested together, what nesting saved and its split, summed and per order (`by_order`), and per line (`by_line`) — see the job nest's `savings`. |
| `sheets` | integer | Sheets and remnants it cuts. |
| `sheets_laid` | NestRevisionSheet[] |  |
| `source` | string | `nest` — the job nest's own layout; `edit` — one posted here. |
| `utilisation_net` | number |  |

**Example response**

```json
{
  "active": true,
  "created_at": "2026-06-01T12:00:00Z",
  "created_by_user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "group_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "length_width_mm": {},
  "note": "string",
  "revision": 0,
  "savings": {},
  "sheets": 0,
  "sheets_laid": [
    {
      "container_id": "string",
      "container_kind": "string",
      "count": 0,
      "index": 0,
      "net_area_mm2": 0,
      "placements": [
        {}
      ],
      "repeat": 0,
      "rest": {
        "length_mm": 0,
        "reusable": true,
        "width_mm": 0,
        "x_mm": 0,
        "y_mm": 0
      },
      "utilisation": 0
    }
  ],
  "source": "string",
  "utilisation_net": 0
}
```

## Nest Run Remnant Labels

`GET /api/v1/nest-runs/{run_id}/remnant-labels`

Rack labels for the rests a released job nest put into stock, one
per row in one SVG sized in millimetres — print it when the sheets
come off the cutter.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | Identifier of the run. |
| `lang` | query | `de` \| `en` | no | The labels' language. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/nest-runs/{run_id}/remnant-labels \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/remnant-labels",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/remnant-labels", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | One label per rest the released job nest put into stock. |
| `402` | An assembly calculation ran this job nest for its own sheet components, and your organization has no active Nesting add-on (`addon_required`). |
| `404` | No job nest with this id in your organization (`nest_run_not_found`), or it put no rest into stock — not released, or its sheets leave no reusable rest (`nest_run_no_rests`). |
| `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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


## Start a job nest kept as a draft

`POST /api/v1/nest-runs/{run_id}/start`

Queue a job nest that was kept as a draft.

A start made while the month's runs were used keeps the job nest as a
`draft`. Once the allowance has a run for it, this queues it: it spends
one run, is nested at today's stock and prices, and runs in the
background — poll `GET /nest-runs/{run_id}` until it ends. While the
month's runs are still used it is refused (402) and stays a draft. Only
a draft starts, and only once; one job nest of your organization runs
at a time, as for a start.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `run_id` | path | string | yes | Identifier of the run. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/nest-runs/{run_id}/start \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/nest-runs/{run_id}/start",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/{run_id}/start", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `402` | The Nesting add-on is not active, or the month's runs are still used (`details.feature` `nesting_runs`, with `used` and `limit`): the draft stays a draft. |
| `404` | No run with this id in your organization (`nest_run_not_found`). |
| `409` | The job nest is not a draft (`nest_run_not_draft`: it was started already, or cancelled), another job nest of your organization is still queued or running (`nest_run_in_flight`), or it is an assembly calculation's own nest, which only that assembly changes (`nest_run_internal`). |
| `422` | It has more materials and thicknesses than a run takes (`nest_run_too_many_nests`). |

**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. |
| `404` | `not_found` | A referenced resource doesn't exist or isn't visible to your organisation. |
| `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 |
| --- | --- | --- |
| `allocation_rule` | `area_proportional` \| `equal_per_sheet` | How each sheet's cost is shared by the parts on it. |
| `as_of` | string | The day its stock and prices are read at. |
| `copies` | integer | Copies it nests, over all its lines. |
| `counted_at` | string | When it was released; null while a simulation. |
| `counts_toward_savings` | boolean | True once the job nest was released: its stock is booked and what it saves adds to the total saved by nesting. False: a simulation, whose saving is potential only. |
| `created_at` | string |  |
| `created_by_user_id` | string |  |
| `env_id` | string | The costing environment it nests in. |
| `error` | string | Why it stopped, when it did not finish. |
| `finished_at` | string |  |
| `group_budget_s` | number | The longest each material and thickness may be nested for, in seconds of computing time. |
| `groups_done` | integer | Of those, the ones finished so far: nested, or not nestable. |
| `groups_total` | integer | Materials and thicknesses it nests — each is nested on its own. |
| `id` | string |  |
| `lines` | integer | Lines it nests. |
| `savings` | NestRunSavings | What it saves, once it ends; null before, or where no line compares. |
| `sheets` | integer | Sheets and remnants the nested materials and thicknesses are cut from; set once the job nest ends. |
| `started_at` | string |  |
| `status` | `draft` \| `queued` \| `running` \| `solved` \| `failed` \| `cancelled` | `draft`: kept, not queued — it was started while the month's runs were used, so it spends no run until `POST /nest-runs/{run_id}/start` starts it. `queued`: waiting to start. `running`: nesting, several materials and thicknesses at a time. `solved`: every material and thickness is nested. `failed`: it stopped, `error` says why; what it nested before stands. `cancelled`: stopped on request; what it nested before stands. |

**Example response**

```json
{
  "allocation_rule": "area_proportional",
  "as_of": "2026-06-01",
  "copies": 0,
  "counted_at": "2026-06-01T12:00:00Z",
  "counts_toward_savings": false,
  "created_at": "2026-06-01T12:00:00Z",
  "created_by_user_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "error": "string",
  "finished_at": "2026-06-01T12:00:00Z",
  "group_budget_s": 184,
  "groups_done": 0,
  "groups_total": 0,
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "lines": 0,
  "savings": {
    "alone": 0,
    "alone_kg": 0,
    "alone_sheets": 0,
    "by_order": [
      {
        "alone": 0,
        "alone_kg": 0,
        "alone_sheets": 0,
        "copies": 0,
        "currency": "EUR",
        "lines": 0,
        "lines_compared": 0,
        "nested": 0,
        "nested_kg": 0,
        "nested_sheets": 0,
        "order_ref": "string",
        "rectangular": 0,
        "rectangular_kg": 0,
        "rectangular_sheets": 0,
        "saved": 0,
        "saved_combining": 0,
        "saved_combining_kg": 0,
        "saved_kg": 0,
        "saved_true_shape": 0,
        "saved_true_shape_kg": 0,
        "split_lines": 0
      }
    ],
    "copies": 0,
    "currency": "EUR",
    "lines": 0,
    "lines_compared": 0,
    "nested": 0,
    "nested_kg": 0,
    "nested_sheets": 0,
    "rectangular": 0,
    "rectangular_kg": 0,
    "rectangular_sheets": 0,
    "saved": 0,
    "saved_combining": 0,
    "saved_combining_kg": 0,
    "saved_kg": 0,
    "saved_true_shape": 0,
    "saved_true_shape_kg": 0,
    "split_lines": 0
  },
  "sheets": 0,
  "started_at": "2026-06-01T12:00:00Z",
  "status": "draft"
}
```

## Preview a job nest

`POST /api/v1/nest-runs/preview`

What a job nest of this selection would take, without starting it.

Takes the body `POST /nest-runs` takes and reads it the same way: the
lines it would nest, each material and thickness they fall into with
the remnant pieces of it in stock now, and what a batch or folder
leaves out and why. It refuses a missing add-on and a run with too
many nests. When the month's runs are already used it still returns
the plan, with `kept_as_draft` set, and nothing is stored.

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `allocation_rule` | `area_proportional` \| `equal_per_sheet` | no | How a sheet's cost is shared by the parts on it: by their area, or equally. |
| `batch_id` | string | no | Nest every calculation of this batch priced in the environment, each at its lot size. |
| `env_id` | string | yes | The costing environment the run nests in: its stock and remnants. |
| `folder_id` | string | no | Nest the newest calculation priced in the environment of every part filed in this folder or below it, each at its lot size. |
| `group_budget_s` | number | no | The longest each material and thickness may be nested for, in seconds of computing time: longer can use less sheet. |
| `items` | NestRunItemIn[] | no |  |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/nest-runs/preview \
  -H "X-API-Key: $ARCNM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }'
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/nest-runs/preview",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
    },
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/preview", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `404` | The environment, batch or folder is not your organization's (`nest_run_selection_not_found`). |
| `422` | An item can't be nested — `details.items` names each with its reason (`nest_run_items_unusable`) — or the batch or folder holds nothing that can (`nest_run_nothing_to_nest`), or more calculations than one run takes (`nest_run_selection_too_large`), or a line asks for more copies than one line takes (`nest_run_line_too_large`), or the lines together more than one run takes (`nest_run_too_many_copies`), or `group_budget_s` is past what your organization may ask for (`nest_run_budget_too_long`). |

**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 |
| --- | --- | --- |
| `copies` | integer | Copies it would nest, over all its lines. |
| `env_id` | string |  |
| `groups` | NestPlanGroup[] |  |
| `items` | NestPlanLine[] |  |
| `kept_as_draft` | boolean | The month's runs are already used. Starting stores this selection as a draft and does not queue it or spend a run. |
| `left_out` | LeftOutLine[] | Calculations of the batch or folder it would not take, each with why. |
| `lines` | integer | Lines it would nest. |

**Example response**

```json
{
  "copies": 0,
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "groups": [
    {
      "copies": 0,
      "gauge_mm": 0,
      "lines": 0,
      "material_category": "string",
      "material_grade_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "remnants_in_stock": 0
    }
  ],
  "items": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "group": 0,
      "order_ref": "string",
      "position": 0,
      "quantity": 0
    }
  ],
  "kept_as_draft": false,
  "left_out": [
    {
      "calculation_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "reason": "string"
    }
  ],
  "lines": 0
}
```

## Read the total saved by nesting

`GET /api/v1/nest-runs/savings`

What the job nests your organization released have saved together —
rectangular blanks cut part by part against the parts nested together,
split into each part's true shape and the orders nested together, the
money per currency and the sheet metal over all — and, apart from
it, the potential of the job nests that are still simulations. A job
nest is a simulation, and stays out of the total, until it is released.

**Request**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/nest-runs/savings \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/nest-runs/savings",
    headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
```

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/nest-runs/savings", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
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. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


**Response body** `200`

| Field | Type | Description |
| --- | --- | --- |
| `alone_kg` | number | The same with each part nested alone, in kg, over the lines in `split_lines`. |
| `lines_compared` | integer | Their lines the figures cover. |
| `money` | NestSavingsMoney[] | The released job nests' savings per currency, in currency order. |
| `nested_kg` | number | The same nested together, in kg. |
| `potential` | NestSavingsSum | The simulations' savings, summed the same way: what releasing them would add. Never part of the total. |
| `rectangular_kg` | number | The sheet the released lines take as rectangular blanks, in kg. |
| `runs_counted` | integer | Released job nests, each with a saving. |
| `runs_simulated` | integer | Job nests with a saving that are not released: simulations. |
| `saved_combining_kg` | number | `alone_kg` less `nested_kg`, over the lines in `split_lines`. |
| `saved_kg` | number | `rectangular_kg` less `nested_kg`. |
| `saved_true_shape_kg` | number | `rectangular_kg` less `alone_kg`, over the lines in `split_lines`. |
| `split_lines` | integer | Of those, the lines the split of the saving covers. |

**Example response**

```json
{
  "alone_kg": 0,
  "lines_compared": 0,
  "money": [
    {
      "alone": 0,
      "currency": "EUR",
      "lines_compared": 0,
      "nested": 0,
      "rectangular": 0,
      "runs": 0,
      "saved": 0,
      "saved_combining": 0,
      "saved_true_shape": 0,
      "split_lines": 0
    }
  ],
  "nested_kg": 0,
  "potential": {
    "alone_kg": 0,
    "lines_compared": 0,
    "money": [
      {
        "alone": 0,
        "currency": "EUR",
        "lines_compared": 0,
        "nested": 0,
        "rectangular": 0,
        "runs": 0,
        "saved": 0,
        "saved_combining": 0,
        "saved_true_shape": 0,
        "split_lines": 0
      }
    ],
    "nested_kg": 0,
    "rectangular_kg": 0,
    "runs": 0,
    "saved_combining_kg": 0,
    "saved_kg": 0,
    "saved_true_shape_kg": 0,
    "split_lines": 0
  },
  "rectangular_kg": 0,
  "runs_counted": 0,
  "runs_simulated": 0,
  "saved_combining_kg": 0,
  "saved_kg": 0,
  "saved_true_shape_kg": 0,
  "split_lines": 0
}
```
