API reference
Environments
The Environments API manages costing environments: create, list, update, and delete them, set their identity, and attach the machines they cost against.
The Environments API manages costing environments: create, list, update, and delete them, set their identity, and attach the machines they cost against.
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).
Create Environment
POST /api/v1/environments
Create a costing environment, a named set of machines, tools, rates and
overrides that calculations are priced in. By default it starts as a copy of
your default environment; source can start it blank (tools only) or empty.
Counts against your plan's environment limit.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
currency |
string | no | ISO 4217 currency code the environment's rates are denominated in. |
description |
string | no | Optional longer description of the environment. |
empty |
boolean | no | Skip the documented default of attaching every tool in your crib to the new environment. With explicit tool membership (each environment prices only the tools attached to it), an empty environment unambiguously means 'deliberately empty'. |
name |
string | yes | Human-readable name for the new environment. |
region |
string | no | Geographic region this environment prices for (e.g. DE, US). |
source |
baseline_copy | blank | empty |
no | What the new environment starts from. baseline_copy (the default): a copy of your default environment — its machines, tools, rates and overrides. blank: a new environment with every tool of your crib attached and no machines. empty: nothing attached. Omitted → baseline_copy (or empty when the legacy empty: true is sent). |
valid_from |
string | yes | ISO date (YYYY-MM-DD) from which the environment is effective. |
valid_to |
string | no | ISO date (YYYY-MM-DD) the environment stops being effective; null = open-ended. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "string",
"valid_from": "2026-06-01"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"name": "string",
"valid_from": "2026-06-01"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "string",
"valid_from": "2026-06-01"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
201 |
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. |
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 |
|---|---|---|
currency |
string | ISO 4217 currency code the environment's rates are denominated in. |
description |
string | Optional longer description of the environment. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
machine_count |
integer | Machines currently in this environment's fleet. |
machines_cloned |
integer | Number of machines copied into the environment; only set by the clone endpoints. |
name |
string | Human-readable name of the costing environment. |
parent_environment_id |
string | Environment this one inherits from, or null when it stands alone. Anything this environment does not state itself resolves from the parent and, recursively, from the parent's parent. |
rate_count |
integer | Rate rows this environment states itself (every kind, every validity window) — what GET /{env_id}/rates returns. Rates it only inherits from its parent are not counted. |
rates_cloned |
integer | Number of rate rows copied into the environment; only set by the clone endpoints. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
scheduled_changes_dropped |
integer | Memberships of the source whose validity window is not active today (scheduled future additions or removals). A clone is a snapshot as of today, so these do not travel; re-schedule them on the copy if you need them. Only set by the clone endpoint. |
subcontractor_count |
integer | Subcontractors this environment orders from. |
tool_count |
integer | Crib tools currently attached to this environment. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this environment is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the environment stops being effective; null = open-ended. |
Example response
{
"currency": "EUR",
"description": "string",
"id": "string",
"is_baseline": true,
"machine_count": 0,
"machines_cloned": 0,
"name": "string",
"parent_environment_id": "string",
"rate_count": 0,
"rates_cloned": 0,
"region": "EU",
"scheduled_changes_dropped": 0,
"subcontractor_count": 0,
"tool_count": 0,
"valid_from": "string",
"valid_to": "string"
}
List Environments
GET /api/v1/environments/
List the organization's costing environments, newest first.
The response is a plain array, so the page position travels in the
Link (RFC 8288) and X-Next-Cursor / 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 |
|---|---|---|---|---|
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. |
order |
query | asc | desc |
no | Sort direction over the collection's ordering key. Use asc to reconcile a batch: rows come oldest-first, so work created while you page lands after your position instead of shifting rows under it. |
created_after |
query | string | no | Only rows created at or after this instant (RFC 3339, e.g. 2026-07-20T09:00:00Z). Inclusive. |
created_before |
query | string | no | Only rows created strictly before this instant (RFC 3339). Exclusive, so an after/before pair tiles a range without overlap. |
offset |
query | integer | no | Rows to skip. Superseded by cursor, which is stable under concurrent writes; kept for existing integrations. Bounded — past the cap, page with cursor. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/ \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/", {
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. |
Delete Environment
DELETE /api/v1/environments/{env_id}
Delete an environment. ON DELETE CASCADE on the rate + machine-
membership tables means the rates + memberships disappear with it;
the underlying MachineDefinition rows survive (they're org-
scoped, not env-scoped, and may be reused by sibling envs).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/environments/{env_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/environments/{env_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}", {
method: "DELETE",
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. |
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 |
|---|---|---|
message |
string | Human-readable result of the operation. |
Example response
{
"message": "string"
}
Get Environment
GET /api/v1/environments/{env_id}
Read one costing environment in full: its currency, region, validity dates and the rates, overheads and surcharges that calculations in it are priced with.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}", {
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. |
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 |
|---|---|---|
cash_discount_pct |
number | Customer cash discount (Kundenskonto) this environment prices into the offer, as a fraction of the target price, 'im Hundert' (0.02 = 2 %); null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
config_revision |
integer | Monotonic configuration revision; every configuration write increments it, and each calculation records the revision it priced under. |
currency |
string | ISO 4217 currency code the environment's rates are denominated in. |
customer_discount_pct |
number | Customer discount (Kundenrabatt) this environment prices into the offer, as a fraction of the list price, 'im Hundert'; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
default_labour_rate_eur_per_h |
number | Labour rate (EUR/h) this environment states. Read-only here: an effective labour rate row beats this column outright, and that row is the setter. Null = states nothing, so the parent environment's value applies, else the platform default. |
default_material_family |
string | Default material family this environment states for calcs that name no grade; null = states nothing, so the parent environment's value applies, else the platform default. |
description |
string | Optional longer description of the environment. |
duty_eur_per_part |
number | Customs duty per unit this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
energy_eur_per_kwh |
number | Energy price (EUR/kWh) this environment states; null = states nothing, so the parent environment's value applies, else the platform default. |
eoq_holding_cost_per_kg_per_year |
number | Storage charge for finished stock this environment states, per kg and year; null = states nothing, so the parent environment's value applies, else the platform default. |
factor_first_off |
number | First-article-inspection factor this environment states: it scales a full inspection into the per-lot first-off time (0 = none). Null = states nothing, so the parent environment's value applies, else the platform default. |
fai_cost |
number | Flat first-article-inspection cost per lot this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default. |
freight_eur_per_part |
number | Outbound freight per unit this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
holding_cost_rate_per_year |
number | Imputed interest rate on capital tied up in finished stock this environment states, per year (0.08 = 8 %); null = states nothing, so the parent environment's value applies, else the platform default. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
machine_count |
integer | Machines currently in this environment's fleet. |
machines_cloned |
integer | Number of machines copied into the environment; only set by the clone endpoints. |
margin_base |
string | The base of the profit mark-up, as this environment states it: 'selbstkosten' (total cost) or 'netto_selbstkosten' (total cost without packaging, freight and duty); null = states nothing, so the parent environment's value applies, else the platform default, 'selbstkosten'. |
margin_pct |
number | Profit mark-up on total cost this environment states; null = states nothing, so the parent environment's value applies, else the platform default, which adds no mark-up — the quote is a should-cost. |
material_overhead_pct |
number | Procurement / storage surcharge on material cost this environment states (0.07 = 7 %); null = states nothing, so the parent environment's value applies, else the platform default, which adds no surcharge. |
name |
string | Human-readable name of the costing environment. |
ordering_cost_eur_per_order |
number | The buyer's fixed cost of placing and receiving one order (Bestellkosten je Bestellvorgang) this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default of 0. Read by the lot-size scenarios where the buyer orders in lots. |
overhead_fixed_eur_per_part |
number | Fixed overhead (EUR/part) this environment states. Read-only here: an effective fixed-overhead rate row beats this column outright, and that row is the setter. Null = states nothing, so the parent environment's value applies, else the platform default. |
overhead_var_pct |
number | Variable-overhead fraction this environment states. Read-only here: an effective variable-overhead rate row beats this column outright, and that row is the setter. Null = states nothing, so the parent environment's value applies, else the platform default (0.15 on direct and machine cost) — unless a material or production overhead is stated, which switches that default off: the Zuschlagskalkulation ladder and this rate are one overhead system, never charged together by default. |
packaging_eur_per_part |
number | Packaging material per unit this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
parent_environment_id |
string | Environment this one inherits from, or null when it stands alone. Anything this environment does not state itself resolves from the parent and, recursively, from the parent's parent. |
production_overhead_pct |
number | Surcharge on production wages this environment states (0.80 = 80 %); null = states nothing, so the parent environment's value applies, else the platform default, which adds no surcharge. |
rate_count |
integer | Rate rows this environment states itself (every kind, every validity window) — what GET /{env_id}/rates returns. Rates it only inherits from its parent are not counted. |
rates_cloned |
integer | Number of rate rows copied into the environment; only set by the clone endpoints. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
reject_rate_base |
string | What a reject costs, as this environment states it: 'material' (the stock drawn for the rejected parts) or 'herstellkosten' (the whole part before scrap, for rejects found at final inspection); null = states nothing, so the parent environment's value applies, else the platform default, 'material'. |
reject_rate_pct |
number | Share of started parts this environment states that the shop rejects (0.03 = 3 %); null = states nothing, so the parent environment's value applies, else the platform default, which rejects none. |
sales_commission_pct |
number | Sales commission (Vertreterprovision) this environment prices into the offer, as a fraction of the target price, 'im Hundert'; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
sample_pct_default |
number | In-process inspection sampling fraction (0..1) this environment states; null = states nothing, so the parent environment's value applies, else the platform default. |
scheduled_changes_dropped |
integer | Memberships of the source whose validity window is not active today (scheduled future additions or removals). A clone is a snapshot as of today, so these do not travel; re-schedule them on the copy if you need them. Only set by the clone endpoint. |
scrap_credit_eur_per_kg |
number | Flat return per kg of scrap this environment states, net of the cost of selling it, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
sga_pct |
number | Administration and selling surcharge on manufacturing cost this environment states; null = states nothing, so the parent environment's value applies, else the platform default, which adds no surcharge. |
source_preset_id |
string | Environment this one was cloned from (lineage). |
space_eur_per_m2_y |
number | Floor-space cost (EUR/m²·year) this environment states; null = states nothing, so the parent environment's value applies, else the platform default. |
subcontract_overhead_pct |
number | Procurement overhead on subcontract work this environment states, as a fraction of the subcontract cost (0.05 = 5 %); null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
subcontractor_count |
integer | Subcontractors this environment orders from. |
tool_count |
integer | Crib tools currently attached to this environment. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this environment is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the environment stops being effective; null = open-ended. |
Example response
{
"cash_discount_pct": 0,
"config_revision": 0,
"currency": "EUR",
"customer_discount_pct": 0,
"default_labour_rate_eur_per_h": 0,
"default_material_family": "string",
"description": "string",
"duty_eur_per_part": 0,
"energy_eur_per_kwh": 0,
"eoq_holding_cost_per_kg_per_year": 0,
"factor_first_off": 0,
"fai_cost": 0,
"freight_eur_per_part": 0,
"holding_cost_rate_per_year": 0,
"id": "string",
"is_baseline": true,
"machine_count": 0,
"machines_cloned": 0,
"margin_base": "string",
"margin_pct": 0,
"material_overhead_pct": 0,
"name": "string",
"ordering_cost_eur_per_order": 0,
"overhead_fixed_eur_per_part": 0,
"overhead_var_pct": 0,
"packaging_eur_per_part": 0,
"parent_environment_id": "string",
"production_overhead_pct": 0,
"rate_count": 0,
"rates_cloned": 0,
"region": "EU",
"reject_rate_base": "string",
"reject_rate_pct": 0,
"sales_commission_pct": 0,
"sample_pct_default": 0,
"scheduled_changes_dropped": 0,
"scrap_credit_eur_per_kg": 0,
"sga_pct": 0,
"source_preset_id": "string",
"space_eur_per_m2_y": 0,
"subcontract_overhead_pct": 0,
"subcontractor_count": 0,
"tool_count": 0,
"valid_from": "string",
"valid_to": "string"
}
Get Environment Assumptions
GET /api/v1/environments/{env_id}/assumptions
Every pricing parameter this costing environment has stated —
the value a calculation would use right now, with the tier that won
and where it came from. Filter with bin_prefix (e.g. milling.),
or ask for the economics alone with parameters=false.
Integration credentials receive your own statements: environment overrides, values inherited from a parent environment, and your tools' own cutting data. The full parameter plane behind them is shown in the app.
Parity contract: values here come from the SAME resolver construction
the pricing pipeline uses, at the environment's current
config_revision.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
bin_prefix |
query | string | no | |
parameters |
query | boolean | no | Include the parameter plane — around a thousand rows, each resolved through the environment chain. Pass false when you only need the stated economics: parameters comes back empty and total is 0, which is what the count has always meant — the rows this response carries. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/assumptions \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/assumptions",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/assumptions", {
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. |
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 |
|---|---|---|
chain |
string[] | Environment names, leaf to root — the inheritance path. |
config_revision |
integer | |
economics |
object | |
environment_id |
string | |
name |
string | |
named_sigmas |
object | The named confidence constants manual writes carry (relative 1-sigma): manual_entry, tuning_and_rate_pin, subcontract_override. |
parameter_version |
string | Version stamp of the parameter set this resolution ran under; it changes whenever the underlying defaults change, so two responses with the same stamp are directly comparable. |
parameters |
AssumptionRow[] | |
total |
integer | Number of parameter rows in this response — 0 when it was requested with parameters=false. |
Example response
{
"chain": [
"string"
],
"config_revision": 0,
"economics": {},
"environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "string",
"named_sigmas": {},
"parameter_version": "string",
"parameters": [
{
"bin": "string",
"parameter": "string",
"sigma": 0,
"source": "string",
"source_id": "string",
"unit": "",
"value": 0
}
],
"total": 0
}
Get Environment Audit
GET /api/v1/environments/{env_id}/audit
Audit the data a price in this environment depends on: unsourced or out-of-range cutting data and overrides, tools priced without a life, unresolvable machines, spindle classes with no power stated, members that source nothing, rate rows in another currency, and — while the environment credits sold scrap — published scrap prices more than 90 days old. Read-only; every finding names the panel that fixes it.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/audit \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/audit",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/audit", {
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. |
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 |
|---|---|---|
clean |
boolean | No error or warning finding. |
counts |
AuditCountsPublic | |
findings |
AuditFindingPublic[] |
Example response
{
"clean": true,
"counts": {
"error": 0,
"info": 0,
"warning": 0
},
"findings": [
{
"code": "string",
"panel": "string",
"params": {
"age_days": 0,
"bin": "string",
"count": 0,
"distance_pct": 0,
"env_currency": "string",
"material": "string",
"material_category": "string",
"max_spindle_rpm": 0,
"missing": "string",
"observed_on": "string",
"parameter": "string",
"platform": 0,
"price_eur": 0,
"retired_on": "string",
"row_currency": "string",
"scrap_class": "string",
"spindle_power_kw": 0,
"tool_life_min": 0,
"tools": [],
"value": 0
},
"severity": "string",
"subject_id": "string",
"subject_kind": "string",
"subject_name": "string"
}
]
}
Clone Environment
POST /api/v1/environments/{env_id}/clone
Duplicate one of the tenant's OWN environments (env + rates + fleet) into a fresh, non-baseline, uncalibrated copy — "start from this environment, then tune or calibrate the duplicate". Only the tenant's own envs can be cloned (any other id, including a platform preset or another tenant's env, → 404).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Optional name for the duplicate; defaults to ' (copy)'. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/clone \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "string"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/clone",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"name": "string"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/clone", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"name": "string"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
201 |
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. |
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 |
|---|---|---|
currency |
string | ISO 4217 currency code the environment's rates are denominated in. |
description |
string | Optional longer description of the environment. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
machine_count |
integer | Machines currently in this environment's fleet. |
machines_cloned |
integer | Number of machines copied into the environment; only set by the clone endpoints. |
name |
string | Human-readable name of the costing environment. |
parent_environment_id |
string | Environment this one inherits from, or null when it stands alone. Anything this environment does not state itself resolves from the parent and, recursively, from the parent's parent. |
rate_count |
integer | Rate rows this environment states itself (every kind, every validity window) — what GET /{env_id}/rates returns. Rates it only inherits from its parent are not counted. |
rates_cloned |
integer | Number of rate rows copied into the environment; only set by the clone endpoints. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
scheduled_changes_dropped |
integer | Memberships of the source whose validity window is not active today (scheduled future additions or removals). A clone is a snapshot as of today, so these do not travel; re-schedule them on the copy if you need them. Only set by the clone endpoint. |
subcontractor_count |
integer | Subcontractors this environment orders from. |
tool_count |
integer | Crib tools currently attached to this environment. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this environment is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the environment stops being effective; null = open-ended. |
Example response
{
"currency": "EUR",
"description": "string",
"id": "string",
"is_baseline": true,
"machine_count": 0,
"machines_cloned": 0,
"name": "string",
"parent_environment_id": "string",
"rate_count": 0,
"rates_cloned": 0,
"region": "EU",
"scheduled_changes_dropped": 0,
"subcontractor_count": 0,
"tool_count": 0,
"valid_from": "string",
"valid_to": "string"
}
Get Cutting Data
GET /api/v1/environments/{env_id}/cutting-data
The environment's effective cutting data (Schnittwerte) per process family and material group, and per ISO 513 workpiece group for the cutting speed — the finer axis a grade's speed is resolved from. Your own value on either axis beats a platform figure; where you have set both, the workpiece group wins as the more specific statement. Every row carries its provenance ("platform default vs your override") and the accepted value window for that parameter.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/cutting-data \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/cutting-data",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/cutting-data", {
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. |
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 |
|---|---|---|
rows |
CuttingDataRowPublic[] | Every editable cutting-data entry with its effective value. |
Example response
{
"rows": [
{
"axis": "material",
"hardness_band": "string",
"iso_group": "string",
"material_code": "string",
"material_label": "string",
"max": 0,
"min": 0,
"parameter": "string",
"process": "string",
"source": "your_override",
"unit": "string",
"value": 0
}
]
}
Update Cutting Data
PUT /api/v1/environments/{env_id}/cutting-data
Write environment-level cutting-data overrides (audited).
Each entry targets one (process, material-or-workpiece-group, parameter) from the closed catalogue; a null value clears your override so the platform default applies again. Your own value on either axis beats a platform figure; where you have set a cutting speed on both the material and its workpiece group, the group wins for every grade mapped to that leaf — the order the pricing engine resolves them in. Values must sit inside the parameter's plausibility window. Changes re-price new calculations in this environment and its child environments immediately.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
values |
CuttingDataEntryUpdate[] | yes | Cutting-data entries to write (null value = reset to default). |
Request
curl -X PUT https://api.arcnm.io/api/v1/environments/{env_id}/cutting-data \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"values": [
{
"material_code": "string",
"parameter": "string",
"process": "string",
"value": 0
}
]
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/environments/{env_id}/cutting-data",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"values": [
{
"material_code": "string",
"parameter": "string",
"process": "string",
"value": 0
}
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/cutting-data", {
method: "PUT",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"values": [
{
"material_code": "string",
"parameter": "string",
"process": "string",
"value": 0
}
]
}),
})
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. |
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 |
|---|---|---|
rows |
CuttingDataRowPublic[] | Every editable cutting-data entry with its effective value. |
Example response
{
"rows": [
{
"axis": "material",
"hardness_band": "string",
"iso_group": "string",
"material_code": "string",
"material_label": "string",
"max": 0,
"min": 0,
"parameter": "string",
"process": "string",
"source": "your_override",
"unit": "string",
"value": 0
}
]
}
Update the environment's stated economics.
PATCH /api/v1/environments/{env_id}/economics
Set the typed regional economics this environment states itself: energy price, floor-space cost, default material family, the cost-sheet surcharges (material overhead, residual production overhead, administration and selling, profit mark-up) with packaging, freight and duty, the price terms priced in 'im Hundert' (cash discount, sales commission, customer discount), and the inspection / holding scalars. Omit a field to keep it; send null to clear it, which stores nothing on this environment so the value inherits from the parent environment again and ends at the platform default. Takes effect on the next calculation.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
cash_discount_pct |
number | no | Customer cash discount (Kundenskonto) priced into the offer 'im Hundert' (0.02 = 2 %): it is a share of the target price that already contains it, so a customer who deducts it still pays the cash price. Null clears back to inherit / platform default, which prices none. |
customer_discount_pct |
number | no | Customer discount (Kundenrabatt) priced into the offer 'im Hundert', as a share of the list price (the offer price). Null clears back to inherit / platform default, which prices none. |
default_material_family |
string | no | Default material family for calculations that name no grade (steel, stainless, aluminum, titanium, nickel, other); null clears back to inherit / platform default. |
duty_eur_per_part |
number | no | Customs duty per unit, in the environment's currency; null clears back to inherit / platform default. |
energy_eur_per_kwh |
number | no | Stated energy price per kWh, in the environment's currency; null clears back to inherit / platform default. |
eoq_holding_cost_per_kg_per_year |
number | no | Storage charge for finished stock per kg and year; null clears back to inherit / platform default. |
factor_first_off |
number | no | First-article-inspection factor: scales a full inspection into the per-lot first-off time (0 = none, and an explicit 0 is a statement that survives). Null clears back to inherit / platform default. |
fai_cost |
number | no | Flat first-article-inspection cost per lot, in the environment's currency; null clears back to inherit / platform default. |
freight_eur_per_part |
number | no | Outbound freight per unit, in the environment's currency; null clears back to inherit / platform default. |
holding_cost_rate_per_year |
number | no | Imputed yearly interest rate on capital tied up in finished stock (0.08 = 8 %); null clears back to inherit / platform default. |
margin_base |
selbstkosten | netto_selbstkosten |
no | The base of the profit mark-up. 'selbstkosten' charges margin_pct on the total cost. 'netto_selbstkosten' charges it on the total cost without packaging, freight and duty — the base German public-contract pricing names. Null clears back to inherit / platform default, 'selbstkosten'. |
margin_pct |
number | no | Profit mark-up on total cost (0.10 = 10 %); null clears back to inherit / platform default, which adds none. |
material_overhead_pct |
number | no | Procurement / storage surcharge on material cost (0.07 = 7 %); null clears back to inherit / platform default. |
ordering_cost_eur_per_order |
number | no | The buyer's fixed cost of placing and receiving one order, in the environment's currency; null clears back to inherit / platform default (0). |
packaging_eur_per_part |
number | no | Packaging material per unit (Sondereinzelkosten des Vertriebs), in the environment's currency; the packing labour is priced on the labour line, not here. Null clears back to inherit / platform default. |
production_overhead_pct |
number | no | Surcharge on production wages (0.80 = 80 %); null clears back to inherit / platform default. |
reject_rate_base |
material | herstellkosten |
no | What a reject costs. 'material' charges the reject rate on the stock drawn for the parts that fail — the lower bound, right when rejects are found at the first operation. 'herstellkosten' charges it on the whole part before scrap (material, manufacturing and their overheads), the industry convention for end-item scrap found at final inspection. One method, never both. Null clears back to inherit / platform default, 'material'. |
reject_rate_pct |
number | no | Share of the parts this shop STARTS that it rejects, as a fraction (0.03 = 3 %, not 3). It is a yield, not a surcharge: to ship 100 good parts at 0.03 the shop starts 104, and the stock drawn for the four that never shipped is charged to the ones that did. Charged on the base reject_rate_base names: the material draw by default, or the whole part when the shop finds its rejects at final inspection. Must be below 0.5 — half a shop's output failing is a process problem, not a costing input. Null clears back to inherit / platform default, which rejects nothing. |
sales_commission_pct |
number | no | Sales commission (Vertreterprovision) priced into the offer 'im Hundert', as a share of the target price. Null clears back to inherit / platform default, which prices none. |
sample_pct_default |
number | no | In-process inspection sampling fraction (0..1). An explicit 0 switches sampling off and is a statement that survives; null clears back to inherit / platform default. |
scrap_credit_eur_per_kg |
number | no | Scrap credit (Reststoffgutschrift): the flat return per kg of scrap, in the environment's currency — what the dealer pays less what sorting, containers and haulage cost you. Paid on the scrap a dealer buys (sheet skeleton, chips, cut-offs) wherever no scrap price is stated for that kind of scrap; never on a reusable remnant or on the part itself. The credit is never more than the material cost; it reduces the material before the material overhead and a material scrap allowance are charged. Null clears back to inherit / platform default, which credits nothing. |
sga_pct |
number | no | Administration and selling surcharge on manufacturing cost (0.12 = 12 %); null clears back to inherit / platform default. |
space_eur_per_m2_y |
number | no | Stated floor-space cost per m² and year, in the environment's currency; null clears back to inherit / platform default. |
subcontract_overhead_pct |
number | no | Procurement overhead on subcontract work, as a fraction of the subcontract cost (0.05 = 5 %), charged inside the manufacturing cost. Null clears back to inherit / platform default, which adds none. |
Request
curl -X PATCH https://api.arcnm.io/api/v1/environments/{env_id}/economics \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"cash_discount_pct": 0,
"customer_discount_pct": 0,
"default_material_family": "string",
"duty_eur_per_part": 0,
"energy_eur_per_kwh": 0,
"eoq_holding_cost_per_kg_per_year": 0,
"factor_first_off": 0,
"fai_cost": 0,
"freight_eur_per_part": 0,
"holding_cost_rate_per_year": 0,
"margin_base": "selbstkosten",
"margin_pct": 0,
"material_overhead_pct": 0,
"ordering_cost_eur_per_order": 0,
"packaging_eur_per_part": 0,
"production_overhead_pct": 0,
"reject_rate_base": "material",
"reject_rate_pct": 0,
"sales_commission_pct": 0,
"sample_pct_default": 0,
"scrap_credit_eur_per_kg": 0,
"sga_pct": 0,
"space_eur_per_m2_y": 0,
"subcontract_overhead_pct": 0
}'
import requests
resp = requests.patch(
"https://api.arcnm.io/api/v1/environments/{env_id}/economics",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"cash_discount_pct": 0,
"customer_discount_pct": 0,
"default_material_family": "string",
"duty_eur_per_part": 0,
"energy_eur_per_kwh": 0,
"eoq_holding_cost_per_kg_per_year": 0,
"factor_first_off": 0,
"fai_cost": 0,
"freight_eur_per_part": 0,
"holding_cost_rate_per_year": 0,
"margin_base": "selbstkosten",
"margin_pct": 0,
"material_overhead_pct": 0,
"ordering_cost_eur_per_order": 0,
"packaging_eur_per_part": 0,
"production_overhead_pct": 0,
"reject_rate_base": "material",
"reject_rate_pct": 0,
"sales_commission_pct": 0,
"sample_pct_default": 0,
"scrap_credit_eur_per_kg": 0,
"sga_pct": 0,
"space_eur_per_m2_y": 0,
"subcontract_overhead_pct": 0
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/economics", {
method: "PATCH",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"cash_discount_pct": 0,
"customer_discount_pct": 0,
"default_material_family": "string",
"duty_eur_per_part": 0,
"energy_eur_per_kwh": 0,
"eoq_holding_cost_per_kg_per_year": 0,
"factor_first_off": 0,
"fai_cost": 0,
"freight_eur_per_part": 0,
"holding_cost_rate_per_year": 0,
"margin_base": "selbstkosten",
"margin_pct": 0,
"material_overhead_pct": 0,
"ordering_cost_eur_per_order": 0,
"packaging_eur_per_part": 0,
"production_overhead_pct": 0,
"reject_rate_base": "material",
"reject_rate_pct": 0,
"sales_commission_pct": 0,
"sample_pct_default": 0,
"scrap_credit_eur_per_kg": 0,
"sga_pct": 0,
"space_eur_per_m2_y": 0,
"subcontract_overhead_pct": 0
}),
})
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. |
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 |
|---|---|---|
cash_discount_pct |
number | Customer cash discount (Kundenskonto) this environment prices into the offer, as a fraction of the target price, 'im Hundert' (0.02 = 2 %); null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
config_revision |
integer | Monotonic configuration revision; every configuration write increments it, and each calculation records the revision it priced under. |
currency |
string | ISO 4217 currency code the environment's rates are denominated in. |
customer_discount_pct |
number | Customer discount (Kundenrabatt) this environment prices into the offer, as a fraction of the list price, 'im Hundert'; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
default_labour_rate_eur_per_h |
number | Labour rate (EUR/h) this environment states. Read-only here: an effective labour rate row beats this column outright, and that row is the setter. Null = states nothing, so the parent environment's value applies, else the platform default. |
default_material_family |
string | Default material family this environment states for calcs that name no grade; null = states nothing, so the parent environment's value applies, else the platform default. |
description |
string | Optional longer description of the environment. |
duty_eur_per_part |
number | Customs duty per unit this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
energy_eur_per_kwh |
number | Energy price (EUR/kWh) this environment states; null = states nothing, so the parent environment's value applies, else the platform default. |
eoq_holding_cost_per_kg_per_year |
number | Storage charge for finished stock this environment states, per kg and year; null = states nothing, so the parent environment's value applies, else the platform default. |
factor_first_off |
number | First-article-inspection factor this environment states: it scales a full inspection into the per-lot first-off time (0 = none). Null = states nothing, so the parent environment's value applies, else the platform default. |
fai_cost |
number | Flat first-article-inspection cost per lot this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default. |
freight_eur_per_part |
number | Outbound freight per unit this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
holding_cost_rate_per_year |
number | Imputed interest rate on capital tied up in finished stock this environment states, per year (0.08 = 8 %); null = states nothing, so the parent environment's value applies, else the platform default. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
machine_count |
integer | Machines currently in this environment's fleet. |
machines_cloned |
integer | Number of machines copied into the environment; only set by the clone endpoints. |
margin_base |
string | The base of the profit mark-up, as this environment states it: 'selbstkosten' (total cost) or 'netto_selbstkosten' (total cost without packaging, freight and duty); null = states nothing, so the parent environment's value applies, else the platform default, 'selbstkosten'. |
margin_pct |
number | Profit mark-up on total cost this environment states; null = states nothing, so the parent environment's value applies, else the platform default, which adds no mark-up — the quote is a should-cost. |
material_overhead_pct |
number | Procurement / storage surcharge on material cost this environment states (0.07 = 7 %); null = states nothing, so the parent environment's value applies, else the platform default, which adds no surcharge. |
name |
string | Human-readable name of the costing environment. |
ordering_cost_eur_per_order |
number | The buyer's fixed cost of placing and receiving one order (Bestellkosten je Bestellvorgang) this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default of 0. Read by the lot-size scenarios where the buyer orders in lots. |
overhead_fixed_eur_per_part |
number | Fixed overhead (EUR/part) this environment states. Read-only here: an effective fixed-overhead rate row beats this column outright, and that row is the setter. Null = states nothing, so the parent environment's value applies, else the platform default. |
overhead_var_pct |
number | Variable-overhead fraction this environment states. Read-only here: an effective variable-overhead rate row beats this column outright, and that row is the setter. Null = states nothing, so the parent environment's value applies, else the platform default (0.15 on direct and machine cost) — unless a material or production overhead is stated, which switches that default off: the Zuschlagskalkulation ladder and this rate are one overhead system, never charged together by default. |
packaging_eur_per_part |
number | Packaging material per unit this environment states, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
parent_environment_id |
string | Environment this one inherits from, or null when it stands alone. Anything this environment does not state itself resolves from the parent and, recursively, from the parent's parent. |
production_overhead_pct |
number | Surcharge on production wages this environment states (0.80 = 80 %); null = states nothing, so the parent environment's value applies, else the platform default, which adds no surcharge. |
rate_count |
integer | Rate rows this environment states itself (every kind, every validity window) — what GET /{env_id}/rates returns. Rates it only inherits from its parent are not counted. |
rates_cloned |
integer | Number of rate rows copied into the environment; only set by the clone endpoints. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
reject_rate_base |
string | What a reject costs, as this environment states it: 'material' (the stock drawn for the rejected parts) or 'herstellkosten' (the whole part before scrap, for rejects found at final inspection); null = states nothing, so the parent environment's value applies, else the platform default, 'material'. |
reject_rate_pct |
number | Share of started parts this environment states that the shop rejects (0.03 = 3 %); null = states nothing, so the parent environment's value applies, else the platform default, which rejects none. |
sales_commission_pct |
number | Sales commission (Vertreterprovision) this environment prices into the offer, as a fraction of the target price, 'im Hundert'; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
sample_pct_default |
number | In-process inspection sampling fraction (0..1) this environment states; null = states nothing, so the parent environment's value applies, else the platform default. |
scheduled_changes_dropped |
integer | Memberships of the source whose validity window is not active today (scheduled future additions or removals). A clone is a snapshot as of today, so these do not travel; re-schedule them on the copy if you need them. Only set by the clone endpoint. |
scrap_credit_eur_per_kg |
number | Flat return per kg of scrap this environment states, net of the cost of selling it, in its own currency; null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
sga_pct |
number | Administration and selling surcharge on manufacturing cost this environment states; null = states nothing, so the parent environment's value applies, else the platform default, which adds no surcharge. |
source_preset_id |
string | Environment this one was cloned from (lineage). |
space_eur_per_m2_y |
number | Floor-space cost (EUR/m²·year) this environment states; null = states nothing, so the parent environment's value applies, else the platform default. |
subcontract_overhead_pct |
number | Procurement overhead on subcontract work this environment states, as a fraction of the subcontract cost (0.05 = 5 %); null = states nothing, so the parent environment's value applies, else the platform default, which is none. |
subcontractor_count |
integer | Subcontractors this environment orders from. |
tool_count |
integer | Crib tools currently attached to this environment. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this environment is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the environment stops being effective; null = open-ended. |
Example response
{
"cash_discount_pct": 0,
"config_revision": 0,
"currency": "EUR",
"customer_discount_pct": 0,
"default_labour_rate_eur_per_h": 0,
"default_material_family": "string",
"description": "string",
"duty_eur_per_part": 0,
"energy_eur_per_kwh": 0,
"eoq_holding_cost_per_kg_per_year": 0,
"factor_first_off": 0,
"fai_cost": 0,
"freight_eur_per_part": 0,
"holding_cost_rate_per_year": 0,
"id": "string",
"is_baseline": true,
"machine_count": 0,
"machines_cloned": 0,
"margin_base": "string",
"margin_pct": 0,
"material_overhead_pct": 0,
"name": "string",
"ordering_cost_eur_per_order": 0,
"overhead_fixed_eur_per_part": 0,
"overhead_var_pct": 0,
"packaging_eur_per_part": 0,
"parent_environment_id": "string",
"production_overhead_pct": 0,
"rate_count": 0,
"rates_cloned": 0,
"region": "EU",
"reject_rate_base": "string",
"reject_rate_pct": 0,
"sales_commission_pct": 0,
"sample_pct_default": 0,
"scheduled_changes_dropped": 0,
"scrap_credit_eur_per_kg": 0,
"sga_pct": 0,
"source_preset_id": "string",
"space_eur_per_m2_y": 0,
"subcontract_overhead_pct": 0,
"subcontractor_count": 0,
"tool_count": 0,
"valid_from": "string",
"valid_to": "string"
}
Read the environment's effective rates.
GET /api/v1/environments/{env_id}/effective-rates
The rates this environment's prices are computed with — per driver (machine hour, labour hour, overheads) and per machine, each with its source: your override, a calibrated adjustment, or the platform default. Rate detail is shown in the Arcanum app; integrations receive the status envelope.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
at |
query | string | no | As-of date for effective-dated rates; defaults to today (UTC). |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/effective-rates \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/effective-rates",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/effective-rates", {
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. |
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 |
|---|---|---|
as_of |
string | |
calibrated |
boolean | Whether this environment has been calibrated to your actuals. |
currency |
string | |
env_id |
string | |
mode |
per_driver | overall |
|
note |
string | |
overhead_var_base |
direct | material | labour | conversion |
The cost the variable-overhead percentage is charged on: all direct and machine cost, bought-in material only, your own wages only, or your own value-add (direct and machine cost less material and bought-in work). Determines whether a supplier invoice attracts your overhead surcharge. |
Example response
{
"as_of": "2026-06-01",
"calibrated": true,
"currency": "EUR",
"env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"mode": "per_driver",
"note": "string",
"overhead_var_base": "direct"
}
Set an effective rate for one driver.
PUT /api/v1/environments/{env_id}/effective-rates
Set the rate a driver charges, in its own unit. Your number becomes the effective rate; future calibrations adjust around it. Takes effect on the next calculation.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
driver |
machine | labour | overhead_var | overhead_fix |
yes | Which rate to set. |
value |
number | yes | The effective rate to charge, in the driver's unit. The two overheads accept 0 (charge none); machine and labour must be above 0. |
Request
curl -X PUT https://api.arcnm.io/api/v1/environments/{env_id}/effective-rates \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"driver": "machine",
"value": 0
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/environments/{env_id}/effective-rates",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"driver": "machine",
"value": 0
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/effective-rates", {
method: "PUT",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"driver": "machine",
"value": 0
}),
})
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. |
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 |
|---|---|---|
as_of |
string | |
calibrated |
boolean | Whether this environment has been calibrated to your actuals. |
currency |
string | |
env_id |
string | |
mode |
per_driver | overall |
|
note |
string | |
overhead_var_base |
direct | material | labour | conversion |
The cost the variable-overhead percentage is charged on: all direct and machine cost, bought-in material only, your own wages only, or your own value-add (direct and machine cost less material and bought-in work). Determines whether a supplier invoice attracts your overhead surcharge. |
Example response
{
"as_of": "2026-06-01",
"calibrated": true,
"currency": "EUR",
"env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"mode": "per_driver",
"note": "string",
"overhead_var_base": "direct"
}
Get Environment Factors
GET /api/v1/environments/{env_id}/factors
Every number the cost sheet lets you change, resolved for this environment: what it is worth here, where that came from, the range a what-if may explore, which cost lines it moves, and the endpoint that saves it.
Values are read through the same resolvers a calculation uses, so a factor shown here is the factor that was charged.
Integration credentials receive your own statements; the platform's own defaults are shown in the app.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/factors \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/factors",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/factors", {
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. |
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 |
|---|---|---|
config_revision |
integer | |
currency |
string | Currency every money unit in 'rows' is denominated in. |
environment_id |
string | |
rows |
FactorRowPublic[] | |
total |
integer |
Example response
{
"config_revision": 0,
"currency": "EUR",
"environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"rows": [
{
"drives": [
"string"
],
"edit_path": "string",
"key": "string",
"label_id": "string",
"max": 0,
"min": 0,
"moves": "string",
"platform_value": 0,
"preview": "linear",
"process": "string",
"source": "your_override",
"source_quality": "sourced",
"unit": "string",
"value": 0
}
],
"total": 0
}
Update Environment Identity
PUT /api/v1/environments/{env_id}/identity
Change a costing environment's name, description, region, currency, validity dates or parent environment. Its machines, tools and rates are changed with their own tools.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
currency |
string | no | New ISO 4217 currency code; omit to leave unchanged. |
description |
string | no | New description for the environment; omit to leave unchanged. |
name |
string | no | New name for the environment; omit to leave unchanged. |
parent_environment_id |
string | no | Environment this one inherits from. Anything this environment does not state itself — a machine rate, a labour rate, a subcontract price, a physics override — resolves from the parent, and from ITS parent above that, so a region can be priced once and a quarter or a customer programme can restate only what differs. Send null to detach. Omit to leave unchanged. |
region |
string | no | New region for the environment; omit to leave unchanged. |
valid_from |
string | no | New effective-from ISO date (YYYY-MM-DD); omit to leave unchanged. |
valid_to |
string | no | New effective-to ISO date (YYYY-MM-DD); omit to leave unchanged. |
Request
curl -X PUT https://api.arcnm.io/api/v1/environments/{env_id}/identity \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"description": "string",
"name": "string",
"parent_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"region": "EU",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/environments/{env_id}/identity",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"currency": "EUR",
"description": "string",
"name": "string",
"parent_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"region": "EU",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/identity", {
method: "PUT",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"currency": "EUR",
"description": "string",
"name": "string",
"parent_environment_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"region": "EU",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
}),
})
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. |
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 |
|---|---|---|
currency |
string | ISO 4217 currency code the environment's rates are denominated in. |
description |
string | Optional longer description of the environment. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
machine_count |
integer | Machines currently in this environment's fleet. |
machines_cloned |
integer | Number of machines copied into the environment; only set by the clone endpoints. |
name |
string | Human-readable name of the costing environment. |
parent_environment_id |
string | Environment this one inherits from, or null when it stands alone. Anything this environment does not state itself resolves from the parent and, recursively, from the parent's parent. |
rate_count |
integer | Rate rows this environment states itself (every kind, every validity window) — what GET /{env_id}/rates returns. Rates it only inherits from its parent are not counted. |
rates_cloned |
integer | Number of rate rows copied into the environment; only set by the clone endpoints. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
scheduled_changes_dropped |
integer | Memberships of the source whose validity window is not active today (scheduled future additions or removals). A clone is a snapshot as of today, so these do not travel; re-schedule them on the copy if you need them. Only set by the clone endpoint. |
subcontractor_count |
integer | Subcontractors this environment orders from. |
tool_count |
integer | Crib tools currently attached to this environment. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this environment is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the environment stops being effective; null = open-ended. |
Example response
{
"currency": "EUR",
"description": "string",
"id": "string",
"is_baseline": true,
"machine_count": 0,
"machines_cloned": 0,
"name": "string",
"parent_environment_id": "string",
"rate_count": 0,
"rates_cloned": 0,
"region": "EU",
"scheduled_changes_dropped": 0,
"subcontractor_count": 0,
"tool_count": 0,
"valid_from": "string",
"valid_to": "string"
}
Get Individualization
GET /api/v1/environments/{env_id}/individualization
What this environment states differently from the platform defaults, per lever — the honest zero-setup meter. Counts only; every line maps to a Setup panel and, on the default environment, to its restore.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/individualization \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/individualization",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/individualization", {
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. |
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 |
|---|---|---|
assumptions |
AssumptionsCounts | |
calibration |
CalibrationCounts | |
individualized |
boolean | Any lever differs from the platform set / platform defaults. |
is_baseline |
boolean | |
machines |
LeverCounts | |
rates |
RatesCounts | |
subcontractors |
LeverCounts | |
tools |
LeverCounts |
Example response
{
"assumptions": {
"overrides": 0,
"tuning_changed": true
},
"calibration": {
"is_calibrated": true,
"observations": 0
},
"individualized": true,
"is_baseline": true,
"machines": {
"added": 0,
"custom": 0,
"edited": 0,
"hidden": 0,
"off": 0,
"removed": 0
},
"rates": {
"stated": 0
},
"subcontractors": {
"added": 0,
"custom": 0,
"edited": 0,
"hidden": 0,
"off": 0,
"removed": 0
},
"tools": {
"added": 0,
"custom": 0,
"edited": 0,
"hidden": 0,
"off": 0,
"removed": 0
}
}
List Env Machines
GET /api/v1/environments/{env_id}/machines
Machines wired to this env, ordered by fleet_priority (lower =
earlier candidate). The same fleet feeds every pricing run for the
environment. at answers "what does the fleet look like on date
X" — the same active_as_of window pricing resolves on that date
(rates already take ?at); it defaults to today.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
at |
query | string | no | As-of date for the fleet window; defaults to today. Answers "what does the fleet look like on date X" — a scheduled addition appears once at reaches its valid_from, a scheduled removal disappears from its valid_to on. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/machines \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/machines",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/machines", {
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. |
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. |
Attach Machine
POST /api/v1/environments/{env_id}/machines
Add a machine to a costing environment so calculations in it can plan on it: one of your machines, a machine-library entry, or a new machine defined inline, with optional rate and capability overrides.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
capability_overrides |
object | no | Sparse per-environment capability overrides merged over the library defaults. |
fleet_priority |
integer | no | Orders machines within the environment; lower values are tried first. |
hourly_rate_override_eur |
number | no | Optional flat machine-hour rate; defaults to the library entry's nominal rate. |
is_enabled |
boolean | no | Whether the machine is active in the environment's fleet on attach. |
library_entry_id |
string | no | Instantiate a machine from this shared/tenant library entry; mutually exclusive with machine_id / new_machine. |
machine_id |
string | no | Identifier of an existing machine to attach; mutually exclusive with new_machine. |
name_override |
string | no | Optional name for the instantiated machine; defaults to the library entry's name. |
new_machine |
MachineDefinitionCreate | no | Inline definition of a new machine to create and attach; mutually exclusive with machine_id. |
rate_operator_eur_per_h_override |
number | no | Operator wage already contained in hourly_rate_override_eur, in EUR per hour. Only meaningful with a rate override; without one the library entry's own declaration is inherited along with its rate. 0 means the rate is machine-only. |
valid_from |
string | no | ISO date (YYYY-MM-DD) the membership starts; defaults to the machine's own valid_from. |
valid_to |
string | no | ISO date (YYYY-MM-DD) the membership ends; defaults to the machine's own valid_to. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/machines \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"capability_overrides": {},
"fleet_priority": 100,
"hourly_rate_override_eur": 0,
"is_enabled": true,
"library_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"machine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name_override": "string",
"new_machine": {
"burden_rate_eur": 0,
"capabilities": {
"axes_indexable": 0,
"axes_simultaneous": 0,
"bar_feeder": false,
"certifications": [
"string"
],
"chatter_stability_lobe": {
"rpm_to_max_axial_depth_mm": [
{}
]
},
"coolant": [
"flood"
],
"iso_286_achievable_grade": "IT01",
"klass": "milling.3axis_vmc",
"laser_power_kw": 0,
"max_material_thickness_mm": 0,
"max_part_envelope_mm": [
null
],
"max_setups_per_part": 6,
"max_spindle_rpm": 0,
"max_table_load_kg": 0,
"max_tool_diameter_mm": 0,
"max_tool_length_mm": 0,
"min_material_thickness_mm": 0,
"nominal_tool_change_time_s_by_class": {},
"pallet_change_time_s": 0,
"plasma_amperage_a": 0,
"positioning_accuracy_mm": 0.01,
"press_force_kn": 0,
"rapid_traverse_m_per_min": 24,
"repeatability_mm": 0.005,
"schema_version": "1.0.0",
"spindle_power_kw": 0,
"subclass": "small",
"tool_interface": "string",
"tool_magazine_capacity": 0,
"turret_stations": 0,
"vdi_3258": {
"acquisition_cost_eur": 0,
"annual_hours_T_G": 0,
"annual_hours_T_IH": 0,
"annual_hours_T_ST": 0,
"capital_interest_rate": 0,
"depreciation_life_h": 0,
"energy_eur_per_kwh": 0,
"energy_kw": 0,
"floor_space_m2": 0,
"maintenance_eur_per_year": 0,
"operator_hourly_eur": 0,
"operator_share": 0,
"replacement_value_eur": 0,
"shift_model": {
"availability": 1,
"days_per_week": 0,
"hours_per_shift": 0,
"shifts_per_day": 0,
"weeks_per_year": 47
},
"space_eur_per_m2_y": 0,
"tooling_eur_per_year": 0
},
"workholding": [
"vise.3jaw"
]
},
"hourly_rate_eur": 0,
"klass": "milling.3axis_vmc",
"library_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"model_no": "string",
"name": "string",
"programming_rate_eur": 0,
"rate_operator_eur_per_h": 0,
"rate_operator_share": 0,
"setup_rate_eur": 0,
"subclass": "small",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01",
"vendor": "string"
},
"rate_operator_eur_per_h_override": 0,
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/machines",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"capability_overrides": {},
"fleet_priority": 100,
"hourly_rate_override_eur": 0,
"is_enabled": True,
"library_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"machine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name_override": "string",
"new_machine": {
"burden_rate_eur": 0,
"capabilities": {
"axes_indexable": 0,
"axes_simultaneous": 0,
"bar_feeder": False,
"certifications": [
"string"
],
"chatter_stability_lobe": {
"rpm_to_max_axial_depth_mm": [
{}
]
},
"coolant": [
"flood"
],
"iso_286_achievable_grade": "IT01",
"klass": "milling.3axis_vmc",
"laser_power_kw": 0,
"max_material_thickness_mm": 0,
"max_part_envelope_mm": [
None
],
"max_setups_per_part": 6,
"max_spindle_rpm": 0,
"max_table_load_kg": 0,
"max_tool_diameter_mm": 0,
"max_tool_length_mm": 0,
"min_material_thickness_mm": 0,
"nominal_tool_change_time_s_by_class": {},
"pallet_change_time_s": 0,
"plasma_amperage_a": 0,
"positioning_accuracy_mm": 0.01,
"press_force_kn": 0,
"rapid_traverse_m_per_min": 24,
"repeatability_mm": 0.005,
"schema_version": "1.0.0",
"spindle_power_kw": 0,
"subclass": "small",
"tool_interface": "string",
"tool_magazine_capacity": 0,
"turret_stations": 0,
"vdi_3258": {
"acquisition_cost_eur": 0,
"annual_hours_T_G": 0,
"annual_hours_T_IH": 0,
"annual_hours_T_ST": 0,
"capital_interest_rate": 0,
"depreciation_life_h": 0,
"energy_eur_per_kwh": 0,
"energy_kw": 0,
"floor_space_m2": 0,
"maintenance_eur_per_year": 0,
"operator_hourly_eur": 0,
"operator_share": 0,
"replacement_value_eur": 0,
"shift_model": {
"availability": 1,
"days_per_week": 0,
"hours_per_shift": 0,
"shifts_per_day": 0,
"weeks_per_year": 47
},
"space_eur_per_m2_y": 0,
"tooling_eur_per_year": 0
},
"workholding": [
"vise.3jaw"
]
},
"hourly_rate_eur": 0,
"klass": "milling.3axis_vmc",
"library_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"model_no": "string",
"name": "string",
"programming_rate_eur": 0,
"rate_operator_eur_per_h": 0,
"rate_operator_share": 0,
"setup_rate_eur": 0,
"subclass": "small",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01",
"vendor": "string"
},
"rate_operator_eur_per_h_override": 0,
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/machines", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"capability_overrides": {},
"fleet_priority": 100,
"hourly_rate_override_eur": 0,
"is_enabled": true,
"library_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"machine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name_override": "string",
"new_machine": {
"burden_rate_eur": 0,
"capabilities": {
"axes_indexable": 0,
"axes_simultaneous": 0,
"bar_feeder": false,
"certifications": [
"string"
],
"chatter_stability_lobe": {
"rpm_to_max_axial_depth_mm": [
{}
]
},
"coolant": [
"flood"
],
"iso_286_achievable_grade": "IT01",
"klass": "milling.3axis_vmc",
"laser_power_kw": 0,
"max_material_thickness_mm": 0,
"max_part_envelope_mm": [
null
],
"max_setups_per_part": 6,
"max_spindle_rpm": 0,
"max_table_load_kg": 0,
"max_tool_diameter_mm": 0,
"max_tool_length_mm": 0,
"min_material_thickness_mm": 0,
"nominal_tool_change_time_s_by_class": {},
"pallet_change_time_s": 0,
"plasma_amperage_a": 0,
"positioning_accuracy_mm": 0.01,
"press_force_kn": 0,
"rapid_traverse_m_per_min": 24,
"repeatability_mm": 0.005,
"schema_version": "1.0.0",
"spindle_power_kw": 0,
"subclass": "small",
"tool_interface": "string",
"tool_magazine_capacity": 0,
"turret_stations": 0,
"vdi_3258": {
"acquisition_cost_eur": 0,
"annual_hours_T_G": 0,
"annual_hours_T_IH": 0,
"annual_hours_T_ST": 0,
"capital_interest_rate": 0,
"depreciation_life_h": 0,
"energy_eur_per_kwh": 0,
"energy_kw": 0,
"floor_space_m2": 0,
"maintenance_eur_per_year": 0,
"operator_hourly_eur": 0,
"operator_share": 0,
"replacement_value_eur": 0,
"shift_model": {
"availability": 1,
"days_per_week": 0,
"hours_per_shift": 0,
"shifts_per_day": 0,
"weeks_per_year": 47
},
"space_eur_per_m2_y": 0,
"tooling_eur_per_year": 0
},
"workholding": [
"vise.3jaw"
]
},
"hourly_rate_eur": 0,
"klass": "milling.3axis_vmc",
"library_entry_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"model_no": "string",
"name": "string",
"programming_rate_eur": 0,
"rate_operator_eur_per_h": 0,
"rate_operator_share": 0,
"setup_rate_eur": 0,
"subclass": "small",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01",
"vendor": "string"
},
"rate_operator_eur_per_h_override": 0,
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
201 |
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. |
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 |
|---|---|---|
env_count |
integer | How many of your environments this machine is currently in. Editing the machine changes what every one of them prices with, so a save button can say how far the edit reaches before it is pressed. |
fleet_priority |
integer | Orders machines within the environment; lower values are tried first. |
health |
string | Whether this machine can be used for pricing. 'ok' — it takes part in selection. 'unresolvable' — its capability configuration is incomplete, so it is NOT considered by any calculation, even though it is enabled. Render an unresolvable machine as broken; it used to render as a normal row and silently do nothing. |
health_error |
string | Why the machine cannot be evaluated; null when health is 'ok'. |
is_enabled |
boolean | Whether this machine is active in the environment's fleet. |
machine |
MachinePublic | The attached machine definition; null only when it cannot be re-read. |
membership_id |
string | Unique identifier of the environment-machine membership. |
tool_kinds_fit |
string[] | The tool kinds among them, for the panel's summary. |
tools_fit_count |
integer | How many of this environment's enabled tools this machine can run (kind-based fitment; a tool is never allocated to one machine). |
valid_from |
string | ISO date (YYYY-MM-DD) from which this membership is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the membership stops being effective; null = open-ended. |
Example response
{
"env_count": 1,
"fleet_priority": 0,
"health": "ok",
"health_error": "string",
"is_enabled": true,
"machine": {
"burden_rate_eur": 0,
"capabilities": {},
"environment_count": 0,
"hourly_rate_eur": 0,
"id": "string",
"klass": "string",
"model_no": "string",
"name": "string",
"programming_rate_eur": 0,
"rate_operator_eur_per_h": 0,
"rate_operator_share": 0,
"setup_rate_eur": 0,
"source": "custom",
"subclass": "string",
"valid_from": "string",
"valid_to": "string",
"vendor": "string"
},
"membership_id": "string",
"tool_kinds_fit": [
"string"
],
"tools_fit_count": 0,
"valid_from": "string",
"valid_to": "string"
}
Detach Machine
DELETE /api/v1/environments/{env_id}/machines/{membership_id}
Remove a machine from a costing environment, so calculations run afterwards no longer plan on it. The machine definition itself is kept.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
membership_id |
path | string | yes | Identifier of the membership. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/environments/{env_id}/machines/{membership_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/environments/{env_id}/machines/{membership_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/machines/{membership_id}", {
method: "DELETE",
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. |
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 |
|---|---|---|
message |
string | Human-readable result of the operation. |
Example response
{
"message": "string"
}
Update Membership
PATCH /api/v1/environments/{env_id}/machines/{membership_id}
Change how a machine is used in one costing environment: its priority among machines that can do the same work, whether it is enabled, and its validity dates.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
membership_id |
path | string | yes | Identifier of the membership. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
fleet_priority |
integer | no | New ordering within the environment (lower tried first); omit to leave unchanged. |
is_enabled |
boolean | no | Whether the machine is active in the fleet; omit to leave unchanged. |
valid_from |
string | no | New membership start ISO date (YYYY-MM-DD); omit to leave unchanged. |
valid_to |
string | no | New membership end ISO date (YYYY-MM-DD); omit to leave unchanged. |
Request
curl -X PATCH https://api.arcnm.io/api/v1/environments/{env_id}/machines/{membership_id} \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fleet_priority": 0,
"is_enabled": true,
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
}'
import requests
resp = requests.patch(
"https://api.arcnm.io/api/v1/environments/{env_id}/machines/{membership_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"fleet_priority": 0,
"is_enabled": True,
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/machines/{membership_id}", {
method: "PATCH",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"fleet_priority": 0,
"is_enabled": true,
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
}),
})
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. |
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 |
|---|---|---|
env_count |
integer | How many of your environments this machine is currently in. Editing the machine changes what every one of them prices with, so a save button can say how far the edit reaches before it is pressed. |
fleet_priority |
integer | Orders machines within the environment; lower values are tried first. |
health |
string | Whether this machine can be used for pricing. 'ok' — it takes part in selection. 'unresolvable' — its capability configuration is incomplete, so it is NOT considered by any calculation, even though it is enabled. Render an unresolvable machine as broken; it used to render as a normal row and silently do nothing. |
health_error |
string | Why the machine cannot be evaluated; null when health is 'ok'. |
is_enabled |
boolean | Whether this machine is active in the environment's fleet. |
machine |
MachinePublic | The attached machine definition; null only when it cannot be re-read. |
membership_id |
string | Unique identifier of the environment-machine membership. |
tool_kinds_fit |
string[] | The tool kinds among them, for the panel's summary. |
tools_fit_count |
integer | How many of this environment's enabled tools this machine can run (kind-based fitment; a tool is never allocated to one machine). |
valid_from |
string | ISO date (YYYY-MM-DD) from which this membership is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the membership stops being effective; null = open-ended. |
Example response
{
"env_count": 1,
"fleet_priority": 0,
"health": "ok",
"health_error": "string",
"is_enabled": true,
"machine": {
"burden_rate_eur": 0,
"capabilities": {},
"environment_count": 0,
"hourly_rate_eur": 0,
"id": "string",
"klass": "string",
"model_no": "string",
"name": "string",
"programming_rate_eur": 0,
"rate_operator_eur_per_h": 0,
"rate_operator_share": 0,
"setup_rate_eur": 0,
"source": "custom",
"subclass": "string",
"valid_from": "string",
"valid_to": "string",
"vendor": "string"
},
"membership_id": "string",
"tool_kinds_fit": [
"string"
],
"tools_fit_count": 0,
"valid_from": "string",
"valid_to": "string"
}
Restore Env Machines
POST /api/v1/environments/{env_id}/machines/restore
Restore the platform machine set in the default environment: removed machines are re-attached (new SCD2 rows), switched-off ones switch on, platform classes the environment never had are added. Capability overrides and machine rates are untouched. 409 on any other environment.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/machines/restore \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/machines/restore",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/machines/restore", {
method: "POST",
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. |
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 |
|---|---|---|
added |
integer | Platform machine classes added that the environment never had. |
enabled |
integer | Switched-off memberships switched back on. |
message |
string | Always 'restored'. |
reattached |
integer | Removed machines re-attached (new SCD2 rows). |
Example response
{
"added": 0,
"enabled": 0,
"message": "string",
"reattached": 0
}
List Rates
GET /api/v1/environments/{env_id}/rates
List all rates for an env, uniformly shaped so the rate editor
can render every kind side by side. Optional ?kind= filter.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
kind |
query | string | no | Filter to a single rate kind (labour, overhead, material, fx, subcontract, or scrap). |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/rates \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates", {
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. |
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. |
Add a price without replacing one you already hold.
POST /api/v1/environments/{env_id}/rates
Adds a rate and leaves every price you already hold open, so you can keep more than one on file — a second subcontractor's quote for the same operation, for instance. Which of them a part is costed at is decided per order.
This one adds. To state the single price this environment should use — closing the price it replaces — use the PUT for the thing itself: .../rates/subcontract/{operation_kind} for bought-in work, .../rates/material/{grade_id} for a material. A scrap price has only its own PUT .../rates/scrap/{material_category}/{scrap_class}.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
currency |
string | no | ISO 4217 currency code for the rate; null to inherit. |
fields |
object | no | Kind-specific columns (category, machine_ref, …). |
kind |
string | yes | labour |
valid_from |
string | yes | Date from which the rate is effective (ISO 8601). |
valid_to |
string | no | Date after which the rate is no longer effective; null if open-ended (ISO 8601). |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/rates \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"kind": "string",
"valid_from": "2026-06-01"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"kind": "string",
"valid_from": "2026-06-01"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"kind": "string",
"valid_from": "2026-06-01"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
201 |
Successful Response |
422 |
Validation Error — or a refused rate, among them scrap_price_use_scrap_route for kind scrap. |
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 201
| Field | Type | Description |
|---|---|---|
created_at |
string | ISO 8601 timestamp when the rate row was created. |
currency |
string | ISO 4217 currency code for the rate; null for kinds without a currency. |
effective_now |
boolean | True when this row is the one currently pricing parts: its validity window contains today and no other row for the same key supersedes it. |
engine_honoured |
boolean | True when this row can be applied as written; false rows are listed but never priced (see unhonoured_reason). |
env_id |
string | Identifier of the environment this rate belongs to. |
fields |
object | Kind-specific rate columns (e.g. hourly_rate, value, price_per_kg, rate). |
id |
string | Unique identifier of the rate row. |
kind |
string | Rate kind: labour, overhead, material, fx, subcontract, or scrap. |
unhonoured_reason |
string | Why the row is never applied, when engine_honoured is false: 'currency_mismatch' (row currency differs from the environment's; fx rows are exempt because they carry their own currency pair), 'pricing_unit_mismatch' (the row's pricing_unit is not the one this operation is quoted in — re-enter the price in the stated unit), 'bucket_combo_unhonoured' (this bucket/kind/base combination is not priceable), 'value_not_positive' (the row's amount is zero or negative and is skipped), 'subcontractor_no_capability' (the subcontractor this price is from does not list this operation — add it to that subcontractor, or enter the price under the one that does) or 'subcontractor_disabled' (that subcontractor is inactive, or is switched off in this environment). Null otherwise. New reasons may be added; treat an unrecognised value as a plain 'not applied'. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this rate is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the rate stops being effective; null = open-ended. |
Example response
{
"created_at": "string",
"currency": "EUR",
"effective_now": false,
"engine_honoured": true,
"env_id": "string",
"fields": {},
"id": "string",
"kind": "string",
"unhonoured_reason": "string",
"valid_from": "string",
"valid_to": "string"
}
Delete Rate
DELETE /api/v1/environments/{env_id}/rates/{kind}/{rate_id}
Delete one rate from a costing environment: a labour, overhead, material, FX or subcontract rate. This cannot be undone.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
kind |
path | string | yes | Rate kind to delete (labour, overhead, material, fx, or subcontract; a scrap price is cleared on its own path). |
rate_id |
path | string | yes | Identifier of the rate. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/environments/{env_id}/rates/{kind}/{rate_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates/{kind}/{rate_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates/{kind}/{rate_id}", {
method: "DELETE",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error — or scrap_price_use_scrap_route: a scrap price is cleared on its own path. |
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 |
|---|---|---|
message |
string | Human-readable result of the operation. |
Example response
{
"message": "string"
}
Drop your price for a material in this environment.
DELETE /api/v1/environments/{env_id}/rates/material/{grade_id}
Removes the environment's own price rows for this material, so it prices at the platform figure for its category again. Takes effect immediately.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
grade_id |
path | string | yes | Identifier of the grade. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/environments/{env_id}/rates/material/{grade_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates/material/{grade_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates/material/{grade_id}", {
method: "DELETE",
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. |
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 |
|---|---|---|
message |
string | Human-readable result of the operation. |
Example response
{
"message": "string"
}
Set what a material costs in this environment.
PUT /api/v1/environments/{env_id}/rates/material/{grade_id}
Your number becomes the price per kilogram this environment calculates with, from valid_from onwards. The price it replaces is closed the same day rather than deleted, so past quotes keep the figure they were costed at.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
grade_id |
path | string | yes | Identifier of the grade. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
price_per_kg |
number | yes | Price per kilogram, in the environment's currency. Takes effect on the next calculation. |
valid_from |
string | no | Day the price starts applying. Defaults to today. The price you replace is closed on the same day, so the two never overlap. |
Request
curl -X PUT https://api.arcnm.io/api/v1/environments/{env_id}/rates/material/{grade_id} \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"price_per_kg": 0
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates/material/{grade_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"price_per_kg": 0
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates/material/{grade_id}", {
method: "PUT",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"price_per_kg": 0
}),
})
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. |
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 |
|---|---|---|
created_at |
string | ISO 8601 timestamp when the rate row was created. |
currency |
string | ISO 4217 currency code for the rate; null for kinds without a currency. |
effective_now |
boolean | True when this row is the one currently pricing parts: its validity window contains today and no other row for the same key supersedes it. |
engine_honoured |
boolean | True when this row can be applied as written; false rows are listed but never priced (see unhonoured_reason). |
env_id |
string | Identifier of the environment this rate belongs to. |
fields |
object | Kind-specific rate columns (e.g. hourly_rate, value, price_per_kg, rate). |
id |
string | Unique identifier of the rate row. |
kind |
string | Rate kind: labour, overhead, material, fx, subcontract, or scrap. |
unhonoured_reason |
string | Why the row is never applied, when engine_honoured is false: 'currency_mismatch' (row currency differs from the environment's; fx rows are exempt because they carry their own currency pair), 'pricing_unit_mismatch' (the row's pricing_unit is not the one this operation is quoted in — re-enter the price in the stated unit), 'bucket_combo_unhonoured' (this bucket/kind/base combination is not priceable), 'value_not_positive' (the row's amount is zero or negative and is skipped), 'subcontractor_no_capability' (the subcontractor this price is from does not list this operation — add it to that subcontractor, or enter the price under the one that does) or 'subcontractor_disabled' (that subcontractor is inactive, or is switched off in this environment). Null otherwise. New reasons may be added; treat an unrecognised value as a plain 'not applied'. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this rate is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the rate stops being effective; null = open-ended. |
Example response
{
"created_at": "string",
"currency": "EUR",
"effective_now": false,
"engine_honoured": true,
"env_id": "string",
"fields": {},
"id": "string",
"kind": "string",
"unhonoured_reason": "string",
"valid_from": "string",
"valid_to": "string"
}
Restore Env Rates
POST /api/v1/environments/{env_id}/rates/restore
Drops every rate this environment states, so it prices at the platform defaults again with honest "platform default" provenance. A scrap price that applied on an earlier day stays on record, ending yesterday, so quotes already made keep it. Your assumptions, tuning and calibration are untouched. Takes effect immediately. Only the default environment can be restored.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/rates/restore \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates/restore",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates/restore", {
method: "POST",
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. |
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 |
|---|---|---|
closed |
integer | Stated rate rows removed, so the platform defaults price again. Takes effect immediately. |
message |
string | Always 'restored'. |
Example response
{
"closed": 0,
"message": "string"
}
Read the scrap prices this environment uses today.
GET /api/v1/environments/{env_id}/rates/scrap
Every scrap price a calculation in this environment would use today, one row per material, grade and kind of scrap, with whose price it is — this environment's, a parent environment's or the platform's published reference figure — and whether a platform figure is stale. credit_scrap_stated says whether any of them changes a quote yet.
Integration credentials receive your own prices; the platform's reference figures are shown in the app.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/rates/scrap \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates/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/environments/{env_id}/rates/scrap", {
method: "GET",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
404 |
No environment with this id is visible to your organization. |
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. |
Response body 200
| Field | Type | Description |
|---|---|---|
credit_scrap_stated |
boolean | True when this environment, or a parent it inherits from, credits sold scrap at these prices. Until it does, none of these prices changes a quote; an environment that states only a flat return per kg is credited at that. |
rows |
ScrapPriceResolvedPublic[] |
Example response
{
"credit_scrap_stated": true,
"rows": [
{
"age_days": 0,
"as_of": "string",
"currency": "EUR",
"grade_label": "string",
"material_category": "string",
"material_grade_id": "string",
"price_per_kg": 0,
"price_source": "string",
"reference": "string",
"scrap_class": "string",
"source_environment_id": "string",
"source_environment_name": "string",
"stale": true,
"stated_grade_id": "string",
"valid_from": "string",
"valid_to": "string"
}
]
}
Stop using your price for one kind of scrap.
DELETE /api/v1/environments/{env_id}/rates/scrap/{material_category}/{scrap_class}
The environment's own price for this material and kind of scrap stops applying today. One that started on an earlier day stays on record, so quotes already made keep it; one set today, or for a later day, is withdrawn. Calculations then use the price a parent environment states, or the platform's reference figure.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
material_category |
path | 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 |
yes | |
scrap_class |
path | sheet_skeleton | chips | drop |
yes | |
grade_id |
query | string | no | The grade the price is for. Leave it out to price every grade of the category that states no price of its own. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/environments/{env_id}/rates/scrap/{material_category}/{scrap_class} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates/scrap/{material_category}/{scrap_class}",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates/scrap/{material_category}/{scrap_class}", {
method: "DELETE",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
404 |
No environment with this id is visible to your organization, or it states no price for this material and kind of scrap (scrap_price_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. |
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 |
|---|---|---|
message |
string | Human-readable result of the operation. |
Example response
{
"message": "string"
}
Set what your scrap dealer pays for one kind of scrap.
PUT /api/v1/environments/{env_id}/rates/scrap/{material_category}/{scrap_class}
Your price per kilogram for the scrap parts of this material leave behind — sheet skeletons and offcuts, chips, or bar ends and cut-offs — from valid_from onwards. Name a grade with grade_id to price it apart from the rest of its category. A price it replaces that started on an earlier day stops the day before rather than being deleted, so quotes already made keep the figure they were costed at; one set earlier the same day is simply replaced.
A scrap price is credited only in an environment that credits sold scrap: the shop-practice question scrap_credit.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
material_category |
path | 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 |
yes | |
scrap_class |
path | sheet_skeleton | chips | drop |
yes | |
grade_id |
query | string | no | The grade the price is for. Leave it out to price every grade of the category that states no price of its own. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
price_per_kg |
number | yes | What you get back per kilogram, in the environment's currency: what the dealer pays less what sorting, containers and haulage cost you. Zero is allowed: a shop that sells nothing back states 0. |
valid_from |
string | no | Day the price starts applying: today, which is the default, or later — never earlier, so quotes already made keep the price they were costed at. The price it replaces stops the day before. |
Request
curl -X PUT https://api.arcnm.io/api/v1/environments/{env_id}/rates/scrap/{material_category}/{scrap_class} \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"price_per_kg": 0
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates/scrap/{material_category}/{scrap_class}",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"price_per_kg": 0
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates/scrap/{material_category}/{scrap_class}", {
method: "PUT",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"price_per_kg": 0
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
404 |
No environment or material grade with this id is visible to your organization. |
422 |
Validation Error — or a refused price: scrap_price_backdated (valid_from lies before today) or scrap_price_grade_category_mismatch (the grade is not of this material category). |
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 |
|---|---|---|
created_at |
string | ISO 8601 timestamp when the rate row was created. |
currency |
string | ISO 4217 currency code for the rate; null for kinds without a currency. |
effective_now |
boolean | True when this row is the one currently pricing parts: its validity window contains today and no other row for the same key supersedes it. |
engine_honoured |
boolean | True when this row can be applied as written; false rows are listed but never priced (see unhonoured_reason). |
env_id |
string | Identifier of the environment this rate belongs to. |
fields |
object | Kind-specific rate columns (e.g. hourly_rate, value, price_per_kg, rate). |
id |
string | Unique identifier of the rate row. |
kind |
string | Rate kind: labour, overhead, material, fx, subcontract, or scrap. |
unhonoured_reason |
string | Why the row is never applied, when engine_honoured is false: 'currency_mismatch' (row currency differs from the environment's; fx rows are exempt because they carry their own currency pair), 'pricing_unit_mismatch' (the row's pricing_unit is not the one this operation is quoted in — re-enter the price in the stated unit), 'bucket_combo_unhonoured' (this bucket/kind/base combination is not priceable), 'value_not_positive' (the row's amount is zero or negative and is skipped), 'subcontractor_no_capability' (the subcontractor this price is from does not list this operation — add it to that subcontractor, or enter the price under the one that does) or 'subcontractor_disabled' (that subcontractor is inactive, or is switched off in this environment). Null otherwise. New reasons may be added; treat an unrecognised value as a plain 'not applied'. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this rate is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the rate stops being effective; null = open-ended. |
Example response
{
"created_at": "string",
"currency": "EUR",
"effective_now": false,
"engine_honoured": true,
"env_id": "string",
"fields": {},
"id": "string",
"kind": "string",
"unhonoured_reason": "string",
"valid_from": "string",
"valid_to": "string"
}
Set what an outside operation costs in this environment.
PUT /api/v1/environments/{env_id}/rates/subcontract/{operation_kind}
Your number becomes the price this environment uses for this operation, from valid_from onwards. Prices you were holding open for the same operation are closed the same day rather than deleted, so quotes you have already sent keep the figure they were costed at.
This one replaces: afterwards there is a single open price for the operation. To put a second subcontractor's quote on file alongside the one you already hold, use POST /environments/{env_id}/rates, which adds a price and closes nothing.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
operation_kind |
path | string | yes | The operation this price is for — heat_treatment:<kind>, coating:<kind>, marking:<kind> or weld. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
lot_minimum_eur |
number | no | Minimum order value, in the environment's currency: the shop invoices one order at max(its work, this). The difference is the small-lot surcharge, spread over the lot — it dominates small batches and is zero once the lot's own work is worth the minimum. |
order_fee_eur |
number | no | Fee the shop charges on every order regardless of its value, in the environment's currency, on top of the work or the minimum. Spread over the lot. Zero for a shop that charges none. |
pricing_unit |
string | yes | The cost basis your price is quoted per: kg, m2 or part for any heat-treatment, coating or marking operation (the part's mass, surface area or one piece is what the rate multiplies), m for a weld (metres of deposited seam). Each shop states its own; the answer names the accepted bases and stores nothing otherwise. |
subcontractor_id |
string | no | Which of your subcontractors this price is from. Optional — leave it out for a figure you carry without naming a shop. |
unit_rate_eur |
number | yes | What the subcontractor charges per pricing unit, in the environment's currency. Zero is allowed — a shop that charges a minimum and nothing per unit. |
valid_from |
string | no | Day the price starts applying. Defaults to today. The price you replace is closed on the same day, so the two never overlap. |
Request
curl -X PUT https://api.arcnm.io/api/v1/environments/{env_id}/rates/subcontract/{operation_kind} \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"unit_rate_eur": 0,
"pricing_unit": "string"
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/environments/{env_id}/rates/subcontract/{operation_kind}",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"unit_rate_eur": 0,
"pricing_unit": "string"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/rates/subcontract/{operation_kind}", {
method: "PUT",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"unit_rate_eur": 0,
"pricing_unit": "string"
}),
})
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. |
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 |
|---|---|---|
created_at |
string | ISO 8601 timestamp when the rate row was created. |
currency |
string | ISO 4217 currency code for the rate; null for kinds without a currency. |
effective_now |
boolean | True when this row is the one currently pricing parts: its validity window contains today and no other row for the same key supersedes it. |
engine_honoured |
boolean | True when this row can be applied as written; false rows are listed but never priced (see unhonoured_reason). |
env_id |
string | Identifier of the environment this rate belongs to. |
fields |
object | Kind-specific rate columns (e.g. hourly_rate, value, price_per_kg, rate). |
id |
string | Unique identifier of the rate row. |
kind |
string | Rate kind: labour, overhead, material, fx, subcontract, or scrap. |
unhonoured_reason |
string | Why the row is never applied, when engine_honoured is false: 'currency_mismatch' (row currency differs from the environment's; fx rows are exempt because they carry their own currency pair), 'pricing_unit_mismatch' (the row's pricing_unit is not the one this operation is quoted in — re-enter the price in the stated unit), 'bucket_combo_unhonoured' (this bucket/kind/base combination is not priceable), 'value_not_positive' (the row's amount is zero or negative and is skipped), 'subcontractor_no_capability' (the subcontractor this price is from does not list this operation — add it to that subcontractor, or enter the price under the one that does) or 'subcontractor_disabled' (that subcontractor is inactive, or is switched off in this environment). Null otherwise. New reasons may be added; treat an unrecognised value as a plain 'not applied'. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this rate is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the rate stops being effective; null = open-ended. |
Example response
{
"created_at": "string",
"currency": "EUR",
"effective_now": false,
"engine_honoured": true,
"env_id": "string",
"fields": {},
"id": "string",
"kind": "string",
"unhonoured_reason": "string",
"valid_from": "string",
"valid_to": "string"
}
Read the environment's shop-practice profile.
GET /api/v1/environments/{env_id}/shop-practice
How this environment answers the shop-practice questions — packaging, deburring, and whether sold scrap and reusable remnants (sheet remnants, bar and tube ends) are credited against the material bought. Each answer says where it comes from: stated here, inherited from a parent environment, or the platform default.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/shop-practice \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/shop-practice",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/shop-practice", {
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. |
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 |
|---|---|---|
questions |
object | Per-question state: effective answer, source, and options. |
Example response
{
"questions": {}
}
Update the environment's shop-practice profile.
PUT /api/v1/environments/{env_id}/shop-practice
Answer the shop-practice questions (packaging, deburring, scrap credit, remnant credit, and how sheet is cut: grain direction, mirroring, the web by thickness, micro-joints, common-line cutting, the job nest's rotation step). Each answer sets this environment's time allowances, credit policy or cutting practice from a closed set of choices and is recorded in the audit log; a question you leave out keeps its answer. 'standard' removes this environment's own answer: where a parent environment states one, that answer applies and is returned with source 'parent_environment'; otherwise the platform behaviour does.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
answers |
object | yes | Question key → answer. Only catalogued questions and answers are accepted. 'standard' removes this environment's own answer: where a parent environment states one, that answer applies (source 'parent_environment'); otherwise the platform behaviour does. |
Request
curl -X PUT https://api.arcnm.io/api/v1/environments/{env_id}/shop-practice \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"answers": {}
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/environments/{env_id}/shop-practice",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"answers": {}
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/shop-practice", {
method: "PUT",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"answers": {}
}),
})
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. |
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 |
|---|---|---|
questions |
object | Per-question state: effective answer, source, and options. |
Example response
{
"questions": {}
}
List Env Subcontractors
GET /api/v1/environments/{env_id}/subcontractors
The subcontractors THIS environment orders from (active
memberships). An external operation priced here that no member offers
carries a subcontract_unsourced note on the result — a note,
never a veto.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors", {
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. |
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. |
Attach Env Subcontractor
POST /api/v1/environments/{env_id}/subcontractors
Attach one of your subcontractors to this environment.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
is_enabled |
boolean | no | |
subcontractor_id |
string | yes |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"subcontractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"subcontractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"subcontractor_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
201 |
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. |
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 |
|---|---|---|
is_active |
boolean | |
is_enabled |
boolean | |
lead_time_days |
integer | |
membership_id |
string | |
name |
string | |
operation_kinds |
string[] | External operations this member offers (capability slugs). |
preset_key |
string | Platform preset this shop was installed from (heat_treatment_shop, electroplating_shop, …); null = the org's own subcontractor. |
region |
string | |
subcontractor_id |
string | |
valid_from |
string |
Example response
{
"is_active": true,
"is_enabled": true,
"lead_time_days": 0,
"membership_id": "string",
"name": "string",
"operation_kinds": [
"string"
],
"preset_key": "string",
"region": "EU",
"subcontractor_id": "string",
"valid_from": "string"
}
Detach Env Subcontractor
DELETE /api/v1/environments/{env_id}/subcontractors/{membership_id}
Detach a subcontractor (SCD2: the membership closes its window; a same-day attach+detach hard-deletes the zero-length row).
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
membership_id |
path | string | yes | Identifier of the membership. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/{membership_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/{membership_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/{membership_id}", {
method: "DELETE",
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. |
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 |
|---|---|---|
message |
string | Human-readable result of the operation. |
Example response
{
"message": "string"
}
Update Env Subcontractor Membership
PATCH /api/v1/environments/{env_id}/subcontractors/{membership_id}
Enable/disable a subcontractor here without detaching it.
The engine has always gated on this flag — pipeline_context_cache
filters is_enabled.is_(True) when it collects the sourced operation
kinds — but no route set it, so it could never be anything but the
True it defaults to on attach. A shop wanting to stop sourcing one
operation from one subcontractor in one environment had to detach and
re-attach, discarding the SCD2 history the membership exists to keep.
Same shape as the tool sibling above.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
membership_id |
path | string | yes | Identifier of the membership. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
is_enabled |
boolean | yes | Enable/disable without detaching. |
Request
curl -X PATCH https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/{membership_id} \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"is_enabled": true
}'
import requests
resp = requests.patch(
"https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/{membership_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"is_enabled": True
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/{membership_id}", {
method: "PATCH",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"is_enabled": true
}),
})
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. |
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 |
|---|---|---|
is_active |
boolean | |
is_enabled |
boolean | |
lead_time_days |
integer | |
membership_id |
string | |
name |
string | |
operation_kinds |
string[] | External operations this member offers (capability slugs). |
preset_key |
string | Platform preset this shop was installed from (heat_treatment_shop, electroplating_shop, …); null = the org's own subcontractor. |
region |
string | |
subcontractor_id |
string | |
valid_from |
string |
Example response
{
"is_active": true,
"is_enabled": true,
"lead_time_days": 0,
"membership_id": "string",
"name": "string",
"operation_kinds": [
"string"
],
"preset_key": "string",
"region": "EU",
"subcontractor_id": "string",
"valid_from": "string"
}
Restore Env Subcontractors
POST /api/v1/environments/{env_id}/subcontractors/restore
Restore the platform subcontractor presets in the default environment: hidden presets return, removed ones re-attach, switched-off ones switch on and are active again. Your own subcontractors and every other environment are untouched. 409 on any other environment.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/restore \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/restore",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/subcontractors/restore", {
method: "POST",
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. |
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 |
|---|---|---|
enabled |
integer | Switched-off memberships switched back on. |
message |
string | Always 'restored'. |
reattached |
integer | Presets re-attached (new SCD2 rows). |
Example response
{
"enabled": 0,
"message": "string",
"reattached": 0,
"unhidden": 0
}
List Env Tools
GET /api/v1/environments/{env_id}/tools
The crib tools THIS environment runs (active memberships). What the
engine prices with — an empty list means the environment deliberately
runs no crib tool and every handler keeps its own constants. at
answers "what does the crib look like on date X" — the same
active_as_of window pricing resolves on that date (rates already
take ?at); it defaults to today.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
at |
query | string | no | As-of date for the crib window; defaults to today. Answers "what does the crib look like on date X" — a scheduled addition appears once at reaches its valid_from, a scheduled removal disappears from its valid_to on. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/tools \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/tools",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/tools", {
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. |
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. |
Attach Env Tool
POST /api/v1/environments/{env_id}/tools
Attach one of your crib tools to this environment.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
is_enabled |
boolean | no | |
tool_id |
string | yes | Crib tool to attach. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/tools \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/tools",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"tool_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/tools", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"tool_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
201 |
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. |
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 |
|---|---|---|
catalogue_item_id |
string | Platform article this tool was adopted from; null = custom tool. |
diameter_mm |
number | Cutting diameter, mm. |
differs_from_article |
boolean | The tool's geometry / economics / spec differ from its article's (edited); always false for a custom tool. |
fits_machine_names |
string[] | The enabled machines that can run the tool, by name — the positive half of the fitment answer. Null when fitment does not apply to this tool kind (nothing mounts it). |
fits_machines |
boolean | Whether any enabled machine of this environment can run the tool; false = the tool is inert here whatever its switch says. |
is_enabled |
boolean | Whether pricing may use this tool here. |
kind |
string | Tool kind (VDI 2852 class). |
membership_id |
string | Membership row id (detach/toggle handle). |
name |
string | Tool name. |
price_eur |
number | Replacement price, EUR. |
teeth |
integer | Cutting edges / flutes. |
tool_id |
string | Crib tool id. |
tool_life_min |
number | Edge life, cutting minutes. |
valid_from |
string | Membership validity start (ISO date). |
Example response
{
"catalogue_item_id": "string",
"diameter_mm": 0,
"differs_from_article": false,
"fits_machine_names": [
"string"
],
"fits_machines": true,
"is_enabled": true,
"kind": "string",
"membership_id": "string",
"name": "string",
"price_eur": 0,
"teeth": 0,
"tool_id": "string",
"tool_life_min": 0,
"valid_from": "string"
}
Detach Env Tool
DELETE /api/v1/environments/{env_id}/tools/{membership_id}
Detach a tool from this environment (SCD2 retire; a same-day attach is hard-deleted). The heal-forward record means a detached tool is never silently re-attached by provisioning.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
membership_id |
path | string | yes | Identifier of the membership. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/environments/{env_id}/tools/{membership_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/environments/{env_id}/tools/{membership_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/tools/{membership_id}", {
method: "DELETE",
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. |
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 |
|---|---|---|
message |
string | Human-readable result of the operation. |
Example response
{
"message": "string"
}
Update Env Tool Membership
PATCH /api/v1/environments/{env_id}/tools/{membership_id}
Enable/disable a tool here without detaching it.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
membership_id |
path | string | yes | Identifier of the membership. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
is_enabled |
boolean | yes | Enable/disable without detaching. |
Request
curl -X PATCH https://api.arcnm.io/api/v1/environments/{env_id}/tools/{membership_id} \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"is_enabled": true
}'
import requests
resp = requests.patch(
"https://api.arcnm.io/api/v1/environments/{env_id}/tools/{membership_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"is_enabled": True
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/tools/{membership_id}", {
method: "PATCH",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"is_enabled": true
}),
})
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. |
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 |
|---|---|---|
catalogue_item_id |
string | Platform article this tool was adopted from; null = custom tool. |
diameter_mm |
number | Cutting diameter, mm. |
differs_from_article |
boolean | The tool's geometry / economics / spec differ from its article's (edited); always false for a custom tool. |
fits_machine_names |
string[] | The enabled machines that can run the tool, by name — the positive half of the fitment answer. Null when fitment does not apply to this tool kind (nothing mounts it). |
fits_machines |
boolean | Whether any enabled machine of this environment can run the tool; false = the tool is inert here whatever its switch says. |
is_enabled |
boolean | Whether pricing may use this tool here. |
kind |
string | Tool kind (VDI 2852 class). |
membership_id |
string | Membership row id (detach/toggle handle). |
name |
string | Tool name. |
price_eur |
number | Replacement price, EUR. |
teeth |
integer | Cutting edges / flutes. |
tool_id |
string | Crib tool id. |
tool_life_min |
number | Edge life, cutting minutes. |
valid_from |
string | Membership validity start (ISO date). |
Example response
{
"catalogue_item_id": "string",
"diameter_mm": 0,
"differs_from_article": false,
"fits_machine_names": [
"string"
],
"fits_machines": true,
"is_enabled": true,
"kind": "string",
"membership_id": "string",
"name": "string",
"price_eur": 0,
"teeth": 0,
"tool_id": "string",
"tool_life_min": 0,
"valid_from": "string"
}
Restore Env Tools
POST /api/v1/environments/{env_id}/tools/restore
Restore the platform tool set in the default environment: hidden platform tools return, removed ones are re-attached, switched-off ones switch on. Edited values, custom tools and every other environment are untouched. 409 on any other environment.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/{env_id}/tools/restore \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/{env_id}/tools/restore",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/tools/restore", {
method: "POST",
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. |
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 |
|---|---|---|
enabled |
integer | Switched-off memberships switched back on. |
message |
string | Always 'restored'. |
reattached |
integer | Tools re-attached to the default environment (new SCD2 rows). |
unhidden |
integer | Hidden platform tools that returned. |
Example response
{
"enabled": 0,
"message": "string",
"reattached": 0,
"unhidden": 0
}
Get Calculation Tuning
GET /api/v1/environments/{env_id}/tuning
Read the environment's calculation-tuning knobs with provenance: stated on this environment, inherited from a parent environment, or the platform default — the order calculations read them in.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X GET https://api.arcnm.io/api/v1/environments/{env_id}/tuning \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/{env_id}/tuning",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/tuning", {
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. |
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 |
|---|---|---|
bar_clamp_remnant_mm |
TuningKnob | End of each bar, mm, the clamp holds and no part is cut from. It leaves as scrap. |
bar_length_mm |
TuningKnob | Length, mm, the round bar a turned part is cut from is bought in. A part carries its share of one bar. Once your stock formats list bars of your own, a part is priced on the lengths listed there, and this length is only the fallback when none of them can be used. |
bar_remnant_min_l_mm |
TuningKnob | Shortest end, mm, a bar leaves past its last part — or a tube past its part — that counts as a reusable remnant. Applies only where remnants are credited. |
custom_format_surcharge_pct |
TuningKnob | Premium a sheet cut to an ordered size carries on its metal over the same metal in a stocked format, as a fraction of the metal's price (0.15 = 15 %). Read by the nesting workbench's sheet comparison: a proposed custom size must beat the best stocked format after this premium and the cut-to-size fees. A re-run on a stated size books no surcharge yet. |
cut_to_size_increment_mm |
TuningKnob | Step, mm, a plate ordered cut to size is bought in: each side rounds up to a whole multiple of it. 0 buys the exact size. |
cycle_factor |
TuningKnob | Multiplier on the WHOLE per-unit run time across all process bins — cutting, air moves, peck retract, tool changes and load/unload, read off the reference process (milling). See 'processes'. Setup and programming have their own factors. |
edge_margin_mm |
TuningKnob | Edge margin kept to every side of a purchased sheet, mm — clamping and edge trim. A plate ordered cut to size carries it on each side too. |
erholzeit_pct |
TuningKnob | Recovery allowance (Erholzeit) as a fraction OF THE RUN BASE TIME it is added to. Setup has its own, ruesterholzeit_pct. |
full_stock_threshold |
TuningKnob | The fill of a started stock piece — a sheet, a bar — from which a lot pays the piece whole, as a fraction of the parts it holds (0.85 = 85 %). Below it the lot pays its share and the rest goes back to stock; from it on the whole piece is the lot's and its empty cells are credited as scrap where scrap is credited. 1.0 turns the rule off (every lot pays its share). |
learning_rate |
TuningKnob | Wright learning-curve rate applied to the lot-size curve (1.0 = off). |
micro_joint_width_mm |
TuningKnob | Width of a micro-joint, mm — the uncut tab that holds a part in the skeleton; applies where micro-joints are used (shop practice). |
micro_joints_over_1000mm |
TuningKnob | Micro-joints per part whose outline is over 1000 mm long. |
micro_joints_to_1000mm |
TuningKnob | Micro-joints per part whose outline is over 300 up to 1000 mm. |
micro_joints_to_300mm |
TuningKnob | Micro-joints per part whose outline is up to 300 mm long. |
min_web_mm_over_12mm |
TuningKnob | Minimum web between parts, mm, for plate over 12 mm. |
min_web_mm_to_12mm |
TuningKnob | Minimum web between parts, mm, for plate over 6 up to 12 mm. |
min_web_mm_to_3mm |
TuningKnob | Minimum web between parts, mm, for sheet up to 3 mm thick — applies where the web depends on the thickness (shop practice). |
min_web_mm_to_6mm |
TuningKnob | Minimum web between parts, mm, for sheet over 3 up to 6 mm. |
processes |
object | Every process that states a factor of its own on this environment, keyed by process (milling, turning, sawing, ...). A flat write states all of them; a per-process write states only the ones it names. Empty when this environment sets nothing; a parent environment's per-process factors are not listed. |
programming_factor |
TuningKnob | Multiplier on estimated programming times, read off the reference process (milling). See 'processes'. |
remnant_min_area_share |
TuningKnob | Area a reusable remnant needs, as a fraction of the purchased sheet's (0.05 = 5 %). Applies only where remnants are credited. |
remnant_min_l_mm |
TuningKnob | Longer side, mm, a rest of the sheet needs to count as a reusable remnant. Applies only where remnants are credited. |
remnant_min_w_mm |
TuningKnob | Shorter side, mm, a rest of the sheet needs to count as a reusable remnant. Applies only where remnants are credited. |
remnant_value_factor |
TuningKnob | What a reusable remnant — a rest of a sheet, an end of a bar or a tube — is worth against new stock of its kind, as a fraction of its price per kg (0.8 = 80 %). Applies only where remnants are credited. |
ruesterholzeit_pct |
TuningKnob | Recovery allowance on setup (Rüsterholzeit) as a fraction OF THE SETUP BASE TIME. Separate from the run plane's erholzeit_pct: setting up and running are different work. |
ruestverteilzeit_pct |
TuningKnob | Distribution allowance on setup (Rüstverteilzeit) as a fraction OF THE SETUP BASE TIME. |
setup_factor |
TuningKnob | Multiplier on estimated setup times, read off the reference process (milling). Per-process factors are in 'processes'. |
skeleton_web_mm |
TuningKnob | Web left standing between two nested parts, mm, WITHOUT the cutting gap: the cutter's own kerf is added to it, so parts sit this web plus the kerf apart. |
verteilzeit_pct |
TuningKnob | Distribution allowance (Verteilzeit) as a fraction OF THE RUN BASE TIME — 0.10 adds 10 % to every machining time. |
Example response
{
"bar_clamp_remnant_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"bar_length_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"bar_remnant_min_l_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"custom_format_surcharge_pct": {
"platform_default": 0,
"source": "string",
"value": 0
},
"cut_to_size_increment_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"cycle_factor": {
"platform_default": 0,
"source": "string",
"value": 0
},
"edge_margin_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"erholzeit_pct": {
"platform_default": 0,
"source": "string",
"value": 0
},
"full_stock_threshold": {
"platform_default": 0,
"source": "string",
"value": 0
},
"learning_rate": {
"platform_default": 0,
"source": "string",
"value": 0
},
"micro_joint_width_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"micro_joints_over_1000mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"micro_joints_to_1000mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"micro_joints_to_300mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"min_web_mm_over_12mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"min_web_mm_to_12mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"min_web_mm_to_3mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"min_web_mm_to_6mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"processes": {},
"programming_factor": {
"platform_default": 0,
"source": "string",
"value": 0
},
"remnant_min_area_share": {
"platform_default": 0,
"source": "string",
"value": 0
},
"remnant_min_l_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"remnant_min_w_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"remnant_value_factor": {
"platform_default": 0,
"source": "string",
"value": 0
},
"ruesterholzeit_pct": {
"platform_default": 0,
"source": "string",
"value": 0
},
"ruestverteilzeit_pct": {
"platform_default": 0,
"source": "string",
"value": 0
},
"setup_factor": {
"platform_default": 0,
"source": "string",
"value": 0
},
"skeleton_web_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"verteilzeit_pct": {
"platform_default": 0,
"source": "string",
"value": 0
}
}
Update Calculation Tuning
PUT /api/v1/environments/{env_id}/tuning
Write env-tier tuning overrides (audited).
setup_factor / programming_factor / cycle_factor write
the per-bin setup_efficiency / programming_efficiency /
cycle_efficiency resolver overrides for every routable process
bin at ENV tier; 1.0, their identity, clears the override.
processes writes the same three cells for only the processes it
names, applied after the flat fields.
The four REFA allowances (verteilzeit_pct, erholzeit_pct,
ruestverteilzeit_pct, ruesterholzeit_pct) write the same
per-bin overrides, over every bin that reads them.
The six sheet-nesting settings (edge_margin_mm,
skeleton_web_mm, the three remnant thresholds and
cut_to_size_increment_mm) each write their one cell of the
sheet-nesting settings, which is where a sheet part's purchase reads
them, and remnant_value_factor its cell of the material valuation
policy, beside the remnant-credit answer that switches it on. The three
bar stock settings (bar_length_mm, bar_clamp_remnant_mm and the
shortest end that counts as a remnant, bar_remnant_min_l_mm) write
theirs, which is where a turned part's bar is bought. Like the
allowances, 0 is a statement and reset is the way back.
The four inspection_* fields write the platform-wide inspection
constants in the global bin — the cells the cost sheet's Prüfung
line lets a reader move. They read back on
GET /environments/{env_id}/factors with their provenance and the
platform value beside them, which is where every resolved factor is
published; this endpoint's own read model stays the tuning knobs.
learning_rate writes the env's Wright rate. Every change lands an
audit row.
null means "no statement" on every field of this body, and
never a write: a client that serialises its whole model sends nulls
for the knobs it is silent about, and that must leave them alone.
Clearing a knob, so that it reads a parent environment's statement
where one exists and else the platform default, is the named reset
list — the allowances need it (0 is a statable allowance, so they
have no identity value to send), and the multipliers accept it as
the spelled-out equivalent of sending 1.0. The inspection cells are
the one exception: an explicit null there clears the override,
the contract the cost sheet's save dialog was built against.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
bar_clamp_remnant_mm |
number | no | End of each bar the clamp holds and no part is cut from, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
bar_length_mm |
number | no | Length the round bar a turned part is cut from is bought in, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
bar_remnant_min_l_mm |
number | no | Shortest bar or tube end that counts as a reusable remnant, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
custom_format_surcharge_pct |
number | no | Premium a sheet cut to an ordered size carries on its metal, as a fraction of the metal's price (0.15 = 15 %); omit or send null to keep, "reset" to clear it back to inherit / platform default. |
cut_to_size_increment_mm |
number | no | Step a plate ordered cut to size is bought in, mm — each side rounds up to a multiple of it, 0 buys the exact size; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
cycle_factor |
number | no | Multiplier on the whole per-unit run time — cutting, air moves, peck retract, tool changes and load/unload; omit or send null to keep, 1.0 (its identity) or "reset" to clear it back to inherit / platform default. |
edge_margin_mm |
number | no | Edge margin kept to every side of a purchased sheet, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. 0 is a statement, not a reset. |
erholzeit_pct |
number | no | Recovery allowance on the RUN base time, as a fraction (0.045 = 4.5 %); omit or send null to keep, "reset" to clear it back to inherit / platform default. |
full_stock_threshold |
number | no | The fill of a started stock piece from which a lot pays it whole, as a fraction of the parts it holds (0.85 = 85 %; 1.0 turns the rule off); omit or send null to keep, "reset" to clear it back to inherit / platform default. |
inspection_tau_s_per_element |
number | no | Seconds to measure one characteristic; omit to keep, send null to clear your override. |
inspection_weight_datum |
number | no | Effort weight of a datum characteristic; omit to keep, null to clear. |
inspection_weight_gdt |
number | no | Effort weight of a geometric-tolerance characteristic; omit to keep, null to clear. |
inspection_weight_ndt |
number | no | Effort weight of a non-destructive-testing callout; omit to keep, null to clear. |
learning_rate |
number | no | Wright rate in [0.5, 1.0]; 1.0 disables learning. Omit or send null to keep it; name it in "reset" to return it to the platform default. |
micro_joint_width_mm |
number | no | Micro-joint width, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
micro_joints_over_1000mm |
number | no | Micro-joints per part with an outline over 1000 mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
micro_joints_to_1000mm |
number | no | Micro-joints per part with an outline over 300 up to 1000 mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
micro_joints_to_300mm |
number | no | Micro-joints per part with an outline up to 300 mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
min_web_mm_over_12mm |
number | no | Minimum web for plate over 12 mm, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
min_web_mm_to_12mm |
number | no | Minimum web for plate over 6 up to 12 mm, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
min_web_mm_to_3mm |
number | no | Minimum web for sheet up to 3 mm, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
min_web_mm_to_6mm |
number | no | Minimum web for sheet over 3 up to 6 mm, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
processes |
object | no | Per-process factors, keyed by process (milling, turning, sawing, ...). Applied after the flat fields, so a flat value sets every process and a named one overrides it. |
programming_factor |
number | no | Programming-time multiplier; omit or send null to keep, 1.0 (its identity) or "reset" to clear it back to inherit / platform default. |
remnant_min_area_share |
number | no | Area a reusable remnant needs, as a fraction of the purchased sheet's (0.05 = 5 %); omit or send null to keep, "reset" to clear it back to inherit / platform default. |
remnant_min_l_mm |
number | no | Longer side a reusable remnant needs, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
remnant_min_w_mm |
number | no | Shorter side a reusable remnant needs, mm; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
remnant_value_factor |
number | no | What a reusable remnant is worth against new stock of its kind, as a fraction of its price per kg (0.8 = 80 %); omit or send null to keep, "reset" to clear it back to inherit / platform default. |
reset |
string[] | no | Knobs to clear on this environment, by name — learning_rate, setup_factor, programming_factor, cycle_factor, verteilzeit_pct, erholzeit_pct, ruestverteilzeit_pct, ruesterholzeit_pct, edge_margin_mm, skeleton_web_mm, remnant_min_l_mm, remnant_min_w_mm, remnant_min_area_share, remnant_value_factor, full_stock_threshold, cut_to_size_increment_mm, custom_format_surcharge_pct, bar_length_mm, bar_clamp_remnant_mm, bar_remnant_min_l_mm, min_web_mm_to_3mm, min_web_mm_to_6mm, min_web_mm_to_12mm, min_web_mm_over_12mm, micro_joint_width_mm, micro_joints_to_300mm, micro_joints_to_1000mm, micro_joints_over_1000mm. A cleared knob reads what a parent environment states, else the platform default; learning_rate is this environment's own and returns to the platform default. Clearing is a named gesture because a null in a knob's own field means 'no statement' and must never delete one. An unknown name is rejected. |
ruesterholzeit_pct |
number | no | Recovery allowance on the SETUP base time, as a fraction (0.045 = 4.5 %); omit or send null to keep, "reset" to clear it back to inherit / platform default. |
ruestverteilzeit_pct |
number | no | Distribution allowance on setup as a fraction of the setup base time (0.10 = 10 %); omit or send null to keep, "reset" to clear it back to inherit / platform default. |
setup_factor |
number | no | Setup-time multiplier; omit or send null to keep, 1.0 (its identity) or "reset" to clear it back to inherit / platform default. |
skeleton_web_mm |
number | no | Web between two nested parts WITHOUT the cutting gap, mm — the cutter's kerf is added to it; omit or send null to keep, "reset" to clear it back to inherit / platform default. |
verteilzeit_pct |
number | no | Distribution allowance as a fraction of the run base time (0.10 = 10 %); omit or send null to keep, "reset" to clear it back to inherit / platform default. 0 is a statable allowance, not a reset. |
Request
curl -X PUT https://api.arcnm.io/api/v1/environments/{env_id}/tuning \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"bar_clamp_remnant_mm": 0,
"bar_length_mm": 0,
"bar_remnant_min_l_mm": 0,
"custom_format_surcharge_pct": 0,
"cut_to_size_increment_mm": 0,
"cycle_factor": 0,
"edge_margin_mm": 0,
"erholzeit_pct": 0,
"full_stock_threshold": 0,
"inspection_tau_s_per_element": 0,
"inspection_weight_datum": 0,
"inspection_weight_gdt": 0,
"inspection_weight_ndt": 0,
"learning_rate": 0,
"micro_joint_width_mm": 0,
"micro_joints_over_1000mm": 0,
"micro_joints_to_1000mm": 0,
"micro_joints_to_300mm": 0,
"min_web_mm_over_12mm": 0,
"min_web_mm_to_12mm": 0,
"min_web_mm_to_3mm": 0,
"min_web_mm_to_6mm": 0,
"processes": {},
"programming_factor": 0,
"remnant_min_area_share": 0,
"remnant_min_l_mm": 0,
"remnant_min_w_mm": 0,
"remnant_value_factor": 0,
"reset": [
"string"
],
"ruesterholzeit_pct": 0,
"ruestverteilzeit_pct": 0,
"setup_factor": 0,
"skeleton_web_mm": 0,
"verteilzeit_pct": 0
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/environments/{env_id}/tuning",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"bar_clamp_remnant_mm": 0,
"bar_length_mm": 0,
"bar_remnant_min_l_mm": 0,
"custom_format_surcharge_pct": 0,
"cut_to_size_increment_mm": 0,
"cycle_factor": 0,
"edge_margin_mm": 0,
"erholzeit_pct": 0,
"full_stock_threshold": 0,
"inspection_tau_s_per_element": 0,
"inspection_weight_datum": 0,
"inspection_weight_gdt": 0,
"inspection_weight_ndt": 0,
"learning_rate": 0,
"micro_joint_width_mm": 0,
"micro_joints_over_1000mm": 0,
"micro_joints_to_1000mm": 0,
"micro_joints_to_300mm": 0,
"min_web_mm_over_12mm": 0,
"min_web_mm_to_12mm": 0,
"min_web_mm_to_3mm": 0,
"min_web_mm_to_6mm": 0,
"processes": {},
"programming_factor": 0,
"remnant_min_area_share": 0,
"remnant_min_l_mm": 0,
"remnant_min_w_mm": 0,
"remnant_value_factor": 0,
"reset": [
"string"
],
"ruesterholzeit_pct": 0,
"ruestverteilzeit_pct": 0,
"setup_factor": 0,
"skeleton_web_mm": 0,
"verteilzeit_pct": 0
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/{env_id}/tuning", {
method: "PUT",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"bar_clamp_remnant_mm": 0,
"bar_length_mm": 0,
"bar_remnant_min_l_mm": 0,
"custom_format_surcharge_pct": 0,
"cut_to_size_increment_mm": 0,
"cycle_factor": 0,
"edge_margin_mm": 0,
"erholzeit_pct": 0,
"full_stock_threshold": 0,
"inspection_tau_s_per_element": 0,
"inspection_weight_datum": 0,
"inspection_weight_gdt": 0,
"inspection_weight_ndt": 0,
"learning_rate": 0,
"micro_joint_width_mm": 0,
"micro_joints_over_1000mm": 0,
"micro_joints_to_1000mm": 0,
"micro_joints_to_300mm": 0,
"min_web_mm_over_12mm": 0,
"min_web_mm_to_12mm": 0,
"min_web_mm_to_3mm": 0,
"min_web_mm_to_6mm": 0,
"processes": {},
"programming_factor": 0,
"remnant_min_area_share": 0,
"remnant_min_l_mm": 0,
"remnant_min_w_mm": 0,
"remnant_value_factor": 0,
"reset": [
"string"
],
"ruesterholzeit_pct": 0,
"ruestverteilzeit_pct": 0,
"setup_factor": 0,
"skeleton_web_mm": 0,
"verteilzeit_pct": 0
}),
})
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. |
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 |
|---|---|---|
bar_clamp_remnant_mm |
TuningKnob | End of each bar, mm, the clamp holds and no part is cut from. It leaves as scrap. |
bar_length_mm |
TuningKnob | Length, mm, the round bar a turned part is cut from is bought in. A part carries its share of one bar. Once your stock formats list bars of your own, a part is priced on the lengths listed there, and this length is only the fallback when none of them can be used. |
bar_remnant_min_l_mm |
TuningKnob | Shortest end, mm, a bar leaves past its last part — or a tube past its part — that counts as a reusable remnant. Applies only where remnants are credited. |
custom_format_surcharge_pct |
TuningKnob | Premium a sheet cut to an ordered size carries on its metal over the same metal in a stocked format, as a fraction of the metal's price (0.15 = 15 %). Read by the nesting workbench's sheet comparison: a proposed custom size must beat the best stocked format after this premium and the cut-to-size fees. A re-run on a stated size books no surcharge yet. |
cut_to_size_increment_mm |
TuningKnob | Step, mm, a plate ordered cut to size is bought in: each side rounds up to a whole multiple of it. 0 buys the exact size. |
cycle_factor |
TuningKnob | Multiplier on the WHOLE per-unit run time across all process bins — cutting, air moves, peck retract, tool changes and load/unload, read off the reference process (milling). See 'processes'. Setup and programming have their own factors. |
edge_margin_mm |
TuningKnob | Edge margin kept to every side of a purchased sheet, mm — clamping and edge trim. A plate ordered cut to size carries it on each side too. |
erholzeit_pct |
TuningKnob | Recovery allowance (Erholzeit) as a fraction OF THE RUN BASE TIME it is added to. Setup has its own, ruesterholzeit_pct. |
full_stock_threshold |
TuningKnob | The fill of a started stock piece — a sheet, a bar — from which a lot pays the piece whole, as a fraction of the parts it holds (0.85 = 85 %). Below it the lot pays its share and the rest goes back to stock; from it on the whole piece is the lot's and its empty cells are credited as scrap where scrap is credited. 1.0 turns the rule off (every lot pays its share). |
learning_rate |
TuningKnob | Wright learning-curve rate applied to the lot-size curve (1.0 = off). |
micro_joint_width_mm |
TuningKnob | Width of a micro-joint, mm — the uncut tab that holds a part in the skeleton; applies where micro-joints are used (shop practice). |
micro_joints_over_1000mm |
TuningKnob | Micro-joints per part whose outline is over 1000 mm long. |
micro_joints_to_1000mm |
TuningKnob | Micro-joints per part whose outline is over 300 up to 1000 mm. |
micro_joints_to_300mm |
TuningKnob | Micro-joints per part whose outline is up to 300 mm long. |
min_web_mm_over_12mm |
TuningKnob | Minimum web between parts, mm, for plate over 12 mm. |
min_web_mm_to_12mm |
TuningKnob | Minimum web between parts, mm, for plate over 6 up to 12 mm. |
min_web_mm_to_3mm |
TuningKnob | Minimum web between parts, mm, for sheet up to 3 mm thick — applies where the web depends on the thickness (shop practice). |
min_web_mm_to_6mm |
TuningKnob | Minimum web between parts, mm, for sheet over 3 up to 6 mm. |
processes |
object | Every process that states a factor of its own on this environment, keyed by process (milling, turning, sawing, ...). A flat write states all of them; a per-process write states only the ones it names. Empty when this environment sets nothing; a parent environment's per-process factors are not listed. |
programming_factor |
TuningKnob | Multiplier on estimated programming times, read off the reference process (milling). See 'processes'. |
remnant_min_area_share |
TuningKnob | Area a reusable remnant needs, as a fraction of the purchased sheet's (0.05 = 5 %). Applies only where remnants are credited. |
remnant_min_l_mm |
TuningKnob | Longer side, mm, a rest of the sheet needs to count as a reusable remnant. Applies only where remnants are credited. |
remnant_min_w_mm |
TuningKnob | Shorter side, mm, a rest of the sheet needs to count as a reusable remnant. Applies only where remnants are credited. |
remnant_value_factor |
TuningKnob | What a reusable remnant — a rest of a sheet, an end of a bar or a tube — is worth against new stock of its kind, as a fraction of its price per kg (0.8 = 80 %). Applies only where remnants are credited. |
ruesterholzeit_pct |
TuningKnob | Recovery allowance on setup (Rüsterholzeit) as a fraction OF THE SETUP BASE TIME. Separate from the run plane's erholzeit_pct: setting up and running are different work. |
ruestverteilzeit_pct |
TuningKnob | Distribution allowance on setup (Rüstverteilzeit) as a fraction OF THE SETUP BASE TIME. |
setup_factor |
TuningKnob | Multiplier on estimated setup times, read off the reference process (milling). Per-process factors are in 'processes'. |
skeleton_web_mm |
TuningKnob | Web left standing between two nested parts, mm, WITHOUT the cutting gap: the cutter's own kerf is added to it, so parts sit this web plus the kerf apart. |
verteilzeit_pct |
TuningKnob | Distribution allowance (Verteilzeit) as a fraction OF THE RUN BASE TIME — 0.10 adds 10 % to every machining time. |
Example response
{
"bar_clamp_remnant_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"bar_length_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"bar_remnant_min_l_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"custom_format_surcharge_pct": {
"platform_default": 0,
"source": "string",
"value": 0
},
"cut_to_size_increment_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"cycle_factor": {
"platform_default": 0,
"source": "string",
"value": 0
},
"edge_margin_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"erholzeit_pct": {
"platform_default": 0,
"source": "string",
"value": 0
},
"full_stock_threshold": {
"platform_default": 0,
"source": "string",
"value": 0
},
"learning_rate": {
"platform_default": 0,
"source": "string",
"value": 0
},
"micro_joint_width_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"micro_joints_over_1000mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"micro_joints_to_1000mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"micro_joints_to_300mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"min_web_mm_over_12mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"min_web_mm_to_12mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"min_web_mm_to_3mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"min_web_mm_to_6mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"processes": {},
"programming_factor": {
"platform_default": 0,
"source": "string",
"value": 0
},
"remnant_min_area_share": {
"platform_default": 0,
"source": "string",
"value": 0
},
"remnant_min_l_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"remnant_min_w_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"remnant_value_factor": {
"platform_default": 0,
"source": "string",
"value": 0
},
"ruesterholzeit_pct": {
"platform_default": 0,
"source": "string",
"value": 0
},
"ruestverteilzeit_pct": {
"platform_default": 0,
"source": "string",
"value": 0
},
"setup_factor": {
"platform_default": 0,
"source": "string",
"value": 0
},
"skeleton_web_mm": {
"platform_default": 0,
"source": "string",
"value": 0
},
"verteilzeit_pct": {
"platform_default": 0,
"source": "string",
"value": 0
}
}
Clone Preset
POST /api/v1/environments/clone-preset
Deep-copy a platform region preset (env + rates + fleet) into the tenant.
The clone is immediately runnable and priceable for every discipline. Only a genuine platform preset can be cloned — any other id (including another tenant's env) reads as 404.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | no | Optional name for the cloned environment; defaults to ' (copy)'. |
preset_id |
string | yes | Identifier of the platform region preset to clone. |
Request
curl -X POST https://api.arcnm.io/api/v1/environments/clone-preset \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"preset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/environments/clone-preset",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"preset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/clone-preset", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"preset_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
201 |
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. |
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 |
|---|---|---|
currency |
string | ISO 4217 currency code the environment's rates are denominated in. |
description |
string | Optional longer description of the environment. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
machine_count |
integer | Machines currently in this environment's fleet. |
machines_cloned |
integer | Number of machines copied into the environment; only set by the clone endpoints. |
name |
string | Human-readable name of the costing environment. |
parent_environment_id |
string | Environment this one inherits from, or null when it stands alone. Anything this environment does not state itself resolves from the parent and, recursively, from the parent's parent. |
rate_count |
integer | Rate rows this environment states itself (every kind, every validity window) — what GET /{env_id}/rates returns. Rates it only inherits from its parent are not counted. |
rates_cloned |
integer | Number of rate rows copied into the environment; only set by the clone endpoints. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
scheduled_changes_dropped |
integer | Memberships of the source whose validity window is not active today (scheduled future additions or removals). A clone is a snapshot as of today, so these do not travel; re-schedule them on the copy if you need them. Only set by the clone endpoint. |
subcontractor_count |
integer | Subcontractors this environment orders from. |
tool_count |
integer | Crib tools currently attached to this environment. |
valid_from |
string | ISO date (YYYY-MM-DD) from which this environment is effective. |
valid_to |
string | ISO date (YYYY-MM-DD) the environment stops being effective; null = open-ended. |
Example response
{
"currency": "EUR",
"description": "string",
"id": "string",
"is_baseline": true,
"machine_count": 0,
"machines_cloned": 0,
"name": "string",
"parent_environment_id": "string",
"rate_count": 0,
"rates_cloned": 0,
"region": "EU",
"scheduled_changes_dropped": 0,
"subcontractor_count": 0,
"tool_count": 0,
"valid_from": "string",
"valid_to": "string"
}
List Presets
GET /api/v1/environments/presets
The 6 platform region presets a tenant can clone into a runnable env.
Read-only and platform-scoped: only platform rows (org_id IS NULL) are
returned (bypass-read inside the service), so no tenant data is exposed.
Request
curl -X GET https://api.arcnm.io/api/v1/environments/presets \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/presets",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/presets", {
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. |
List published surcharge-rate presets.
GET /api/v1/environments/surcharge-presets
Benchmark Zuschlagskalkulation rates from primary sources (automotive supplier mark-ups on total manufacturing cost; the usual profit mark-up of German public price law), each with its citation and the equivalent environment economics. Nothing here prices on its own: applying a preset means sending its economics to PATCH /environments/{env_id}/economics, which records the rates as that environment's own statement.
Request
curl -X GET https://api.arcnm.io/api/v1/environments/surcharge-presets \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/environments/surcharge-presets",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/environments/surcharge-presets", {
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. |