API reference
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.
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 theX-API-Keyheader (see Authentication).
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;limitcaps 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
curl -X GET https://api.arcnm.io/api/v1/remnants \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog 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
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
}'
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())
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()
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 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
{
"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
curl -X GET https://api.arcnm.io/api/v1/remnants/{remnant_id}/label \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 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
curl -X POST https://api.arcnm.io/api/v1/remnants/{remnant_id}/scrap \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 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
{
"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
curl -X GET https://api.arcnm.io/api/v1/remnants/by-code/{code} \
-H "X-API-Key: $ARCNM_API_KEY"
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())
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()
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 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
{
"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
}