---
title: Remnants
description: The Remnants API keeps the remnants your shop holds in stock, per costing environment: list them, state the ones in your racks, and scrap one when it leaves.
---

# Remnants

The Remnants API keeps the remnants your shop holds in stock, per costing environment: list them, state the ones in your racks, and scrap one when it leaves.

> **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 Remnants

`GET /api/v1/remnants`

Your organization's remnants, newest first.

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 remnants. A filter narrows the list: an id that matches no environment of your organization lists nothing. |
| `status` | query | `available` \| `reserved` \| `consumed` \| `scrapped` | no | Only remnants in this state; every state when omitted. |
| `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/remnants \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/remnants",
    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/remnants", {
  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. |


## Create Remnant

`POST /api/v1/remnants`

State a remnant your shop holds.

A job nest in that environment cuts parts of its material and
thickness from it before it buys a sheet.

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

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `env_id` | string | yes |  |
| `gauge_mm` | number | yes |  |
| `length_mm` | number | yes |  |
| `material_category` | `carbon_steel` \| `alloy_steel` \| `stainless_steel` \| `tool_steel` \| `cast_iron` \| `aluminium` \| `copper_alloy` \| `nickel_alloy` \| `titanium_alloy` \| `magnesium_alloy` \| `zinc_alloy` \| `thermoplastic` \| `thermoset` \| `elastomer` \| `composite` \| `other` | no |  |
| `material_grade_id` | string | no |  |
| `quantity` | integer | no |  |
| `width_mm` | number | yes |  |

**Request**

<CodeTabs>

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

```python title="Python"
import requests

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

```typescript title="TypeScript"
const resp = await fetch("https://api.arcnm.io/api/v1/remnants", {
  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",
    "length_mm": 0,
    "width_mm": 0,
    "gauge_mm": 0
  }),
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `422` | The environment or the material grade is not one of your organization's (`remnant_unresolved`). |

**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 |
| --- | --- | --- |
| `consumed_at` | string | When it left stock — consumed or scrapped. |
| `created_at` | string |  |
| `env_id` | string | The costing environment that holds it. |
| `gauge_mm` | number | Thickness, in millimetres. |
| `id` | string |  |
| `length_mm` | number | Longer edge, in millimetres. |
| `material_category` | string | The material category. |
| `material_grade_id` | string | The material grade; null when the remnant is stated for a whole material category. |
| `origin_group_id` | string | The job nest group whose sheet left it; null for a remnant you stated. |
| `quantity` | integer | Pieces of this size. |
| `reserved_by_run_id` | string | The job nest that holds or consumed it. |
| `status` | `available` \| `reserved` \| `consumed` \| `scrapped` | `available`: in stock. `reserved`: held by a job nest while it runs. `consumed`: cut into parts by a job nest. `scrapped`: taken out of stock. |
| `width_mm` | number | Shorter edge, in millimetres. |

**Example response**

```json
{
  "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
}
```

## Remnant Label

`GET /api/v1/remnants/{remnant_id}/label`

The remnant's rack label: size, thickness, material, pieces, origin
and date, with a Code 128 barcode of its code.

Print it at 100 %: the SVG is sized in millimetres. The barcode reads
back through `GET /remnants/by-code/{code}`.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `remnant_id` | path | string | yes | Identifier of the remnant. |
| `lang` | query | `de` \| `en` | no | The label's language. |

**Request**

<CodeTabs>

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

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/remnants/{remnant_id}/label",
    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/remnants/{remnant_id}/label", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | The rack label, an SVG at true size (100 × 50 mm). |
| `404` | No remnant with this id in your organization (`remnant_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. |


## Scrap Remnant

`POST /api/v1/remnants/{remnant_id}/scrap`

Take a remnant out of stock — sold, thrown away, or cut by hand.

It stays on record, so a job nest that used or left it still names
it. Scrapping part of a stack of one size takes out that many pieces.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `remnant_id` | path | string | yes | Identifier of the remnant. |

**Request**

<CodeTabs>

```bash title="cURL"
curl -X POST https://api.arcnm.io/api/v1/remnants/{remnant_id}/scrap \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
import requests

resp = requests.post(
    "https://api.arcnm.io/api/v1/remnants/{remnant_id}/scrap",
    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/remnants/{remnant_id}/scrap", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `404` | No remnant with this id in your organization (`remnant_not_found`). |
| `409` | The remnant is not in stock: a job nest holds it while it runs, or it was consumed or scrapped already, or fewer pieces are in stock than you named (`remnant_not_in_stock`). |
| `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 |
| --- | --- | --- |
| `consumed_at` | string | When it left stock — consumed or scrapped. |
| `created_at` | string |  |
| `env_id` | string | The costing environment that holds it. |
| `gauge_mm` | number | Thickness, in millimetres. |
| `id` | string |  |
| `length_mm` | number | Longer edge, in millimetres. |
| `material_category` | string | The material category. |
| `material_grade_id` | string | The material grade; null when the remnant is stated for a whole material category. |
| `origin_group_id` | string | The job nest group whose sheet left it; null for a remnant you stated. |
| `quantity` | integer | Pieces of this size. |
| `reserved_by_run_id` | string | The job nest that holds or consumed it. |
| `status` | `available` \| `reserved` \| `consumed` \| `scrapped` | `available`: in stock. `reserved`: held by a job nest while it runs. `consumed`: cut into parts by a job nest. `scrapped`: taken out of stock. |
| `width_mm` | number | Shorter edge, in millimetres. |

**Example response**

```json
{
  "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
}
```

## Find Remnant By Code

`GET /api/v1/remnants/by-code/{code}`

The remnant a scanned or typed label code names.

Any state: a scanned piece that was consumed or scrapped is still
found, and its `status` says so.

**Parameters**

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `code` | path | string | yes | The code a label carries — the first 12 hexadecimal digits of the remnant's id — or the whole id. Case does not matter. |

**Request**

<CodeTabs>

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

```python title="Python"
import requests

resp = requests.get(
    "https://api.arcnm.io/api/v1/remnants/by-code/{code}",
    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/remnants/by-code/{code}", {
  method: "GET",
  headers: {
    "X-API-Key": process.env.ARCNM_API_KEY!,
  },
})
const data = await resp.json()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `404` | No remnant with this id in your organization (`remnant_not_found`). |
| `409` | More than one remnant of your organization begins with this code — scan or type more of the id (`remnant_code_ambiguous`). |
| `422` | The code is not a remnant code: hexadecimal digits of the id, at least 8 of them, or the whole id (`remnant_code_invalid`). |

**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 |
| --- | --- | --- |
| `consumed_at` | string | When it left stock — consumed or scrapped. |
| `created_at` | string |  |
| `env_id` | string | The costing environment that holds it. |
| `gauge_mm` | number | Thickness, in millimetres. |
| `id` | string |  |
| `length_mm` | number | Longer edge, in millimetres. |
| `material_category` | string | The material category. |
| `material_grade_id` | string | The material grade; null when the remnant is stated for a whole material category. |
| `origin_group_id` | string | The job nest group whose sheet left it; null for a remnant you stated. |
| `quantity` | integer | Pieces of this size. |
| `reserved_by_run_id` | string | The job nest that holds or consumed it. |
| `status` | `available` \| `reserved` \| `consumed` \| `scrapped` | `available`: in stock. `reserved`: held by a job nest while it runs. `consumed`: cut into parts by a job nest. `scrapped`: taken out of stock. |
| `width_mm` | number | Shorter edge, in millimetres. |

**Example response**

```json
{
  "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
}
```
