API reference
Environments
The Environments API manages costing environments.
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/parts/environments
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. |
enabled_extractors |
string[] | no | Geometry and enrichment sources enabled for the environment. |
name |
string | yes | Human-readable name for the new environment. |
region |
string | no | Geographic region this environment prices for (e.g. DE, US). |
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/parts/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/parts/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/parts/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. |
enabled_extractors |
string[] | Geometry and enrichment sources enabled for this environment. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
name |
string | Human-readable name of the costing environment. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
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",
"enabled_extractors": [
"string"
],
"id": "string",
"is_baseline": true,
"name": "string",
"region": "EU",
"valid_from": "string",
"valid_to": "string"
}
List Environments
GET /api/v1/parts/environments/
Paginated. Pass
limitandoffsetto page through results.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
limit |
query | integer | no | Maximum number of results to return. |
offset |
query | integer | no | Number of results to skip before the returned page. |
Request
curl -X GET https://api.arcnm.io/api/v1/parts/environments/ \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/parts/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/parts/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/parts/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/parts/environments/{env_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/parts/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/parts/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/parts/environments/{env_id}
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/parts/environments/{env_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/parts/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/parts/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 |
|---|---|---|
attributes |
object | Free-form key/value metadata attached to the environment. |
currency |
string | ISO 4217 currency code the environment's rates are denominated in. |
description |
string | Optional longer description of the environment. |
enabled_extractors |
string[] | Geometry and enrichment sources enabled for this environment. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
name |
string | Human-readable name of the costing environment. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
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
{
"attributes": {},
"currency": "EUR",
"description": "string",
"enabled_extractors": [
"string"
],
"id": "string",
"is_baseline": true,
"name": "string",
"region": "EU",
"valid_from": "string",
"valid_to": "string"
}
Update Environment
PATCH /api/v1/parts/environments/{env_id}
Update the tenant-controllable extractor list on an environment.
The canonical geometry source (arcnm) is always retained.
Tenant-scoped: a 404 is returned for env ids belonging to other orgs so we never confirm cross-tenant existence.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
enabled_extractors |
string[] | yes | Geometry-extractor slugs to enable for this environment (at least one). |
Request
curl -X PATCH https://api.arcnm.io/api/v1/parts/environments/{env_id} \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"enabled_extractors": [
"string"
]
}'
import requests
resp = requests.patch(
"https://api.arcnm.io/api/v1/parts/environments/{env_id}",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"enabled_extractors": [
"string"
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/parts/environments/{env_id}", {
method: "PATCH",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"enabled_extractors": [
"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 |
|---|---|---|
currency |
string | ISO 4217 currency code the environment's rates are denominated in. |
description |
string | Optional longer description of the environment. |
enabled_extractors |
string[] | Geometry and enrichment sources enabled for this environment. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
name |
string | Human-readable name of the costing environment. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
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",
"enabled_extractors": [
"string"
],
"id": "string",
"is_baseline": true,
"name": "string",
"region": "EU",
"valid_from": "string",
"valid_to": "string"
}
Update Environment Identity
PUT /api/v1/parts/environments/{env_id}/identity
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. |
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/parts/environments/{env_id}/identity \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"description": "string",
"name": "string",
"region": "EU",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
}'
import requests
resp = requests.put(
"https://api.arcnm.io/api/v1/parts/environments/{env_id}/identity",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"currency": "EUR",
"description": "string",
"name": "string",
"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/parts/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",
"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. |
enabled_extractors |
string[] | Geometry and enrichment sources enabled for this environment. |
id |
string | Unique identifier of the costing environment. |
is_baseline |
boolean | Whether this is the auto-provisioned default environment for the tenant. |
name |
string | Human-readable name of the costing environment. |
region |
string | Geographic region this environment prices for (e.g. DE, US). |
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",
"enabled_extractors": [
"string"
],
"id": "string",
"is_baseline": true,
"name": "string",
"region": "EU",
"valid_from": "string",
"valid_to": "string"
}
List Env Machines
GET /api/v1/parts/environments/{env_id}/machines
Machines wired to this env, ordered by fleet_priority (lower =
earlier candidate). Engine-agnostic — the same fleet feeds ARCNM
today and is reused as-is by any future engine via a shared
resolver.
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/parts/environments/{env_id}/machines \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/parts/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/parts/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/parts/environments/{env_id}/machines
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
fleet_priority |
integer | no | Orders machines within the environment; lower values are tried first. |
is_enabled |
boolean | no | Whether the machine is active in the environment's fleet on attach. |
machine_id |
string | no | Identifier of an existing machine to attach; mutually exclusive with new_machine. |
new_machine |
MachineDefinitionCreate | no | Inline definition of a new machine to create and attach; mutually exclusive with machine_id. |
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/parts/environments/{env_id}/machines \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"fleet_priority": 100,
"is_enabled": true,
"machine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"new_machine": {
"burden_rate_eur": 0,
"capabilities": {
"axes_indexable": 0,
"axes_simultaneous": 0,
"certifications": [
"string"
],
"chatter_stability_lobe": {
"rpm_to_max_axial_depth_mm": [
{}
]
},
"coolant": [
"flood"
],
"iso_286_achievable_grade": "IT01",
"klass": "milling.3axis_vmc",
"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,
"nominal_tool_change_time_s_by_class": {},
"positioning_accuracy_mm": 0.01,
"rapid_traverse_m_per_min": 24,
"repeatability_mm": 0.005,
"schema_version": "1.0.0",
"spindle_power_kw": 0,
"subclass": "small",
"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,
"space_eur_per_m2_y": 0,
"tooling_eur_per_year": 0
},
"workholding": [
"vise.3jaw"
]
},
"hourly_rate_eur": 0,
"klass": "milling.3axis_vmc",
"model_no": "string",
"name": "string",
"parent_template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"subclass": "small",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01",
"vendor": "string"
},
"valid_from": "2026-06-01",
"valid_to": "2026-06-01"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/parts/environments/{env_id}/machines",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"fleet_priority": 100,
"is_enabled": True,
"machine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"new_machine": {
"burden_rate_eur": 0,
"capabilities": {
"axes_indexable": 0,
"axes_simultaneous": 0,
"certifications": [
"string"
],
"chatter_stability_lobe": {
"rpm_to_max_axial_depth_mm": [
{}
]
},
"coolant": [
"flood"
],
"iso_286_achievable_grade": "IT01",
"klass": "milling.3axis_vmc",
"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,
"nominal_tool_change_time_s_by_class": {},
"positioning_accuracy_mm": 0.01,
"rapid_traverse_m_per_min": 24,
"repeatability_mm": 0.005,
"schema_version": "1.0.0",
"spindle_power_kw": 0,
"subclass": "small",
"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,
"space_eur_per_m2_y": 0,
"tooling_eur_per_year": 0
},
"workholding": [
"vise.3jaw"
]
},
"hourly_rate_eur": 0,
"klass": "milling.3axis_vmc",
"model_no": "string",
"name": "string",
"parent_template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"subclass": "small",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01",
"vendor": "string"
},
"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/parts/environments/{env_id}/machines", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"fleet_priority": 100,
"is_enabled": true,
"machine_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"new_machine": {
"burden_rate_eur": 0,
"capabilities": {
"axes_indexable": 0,
"axes_simultaneous": 0,
"certifications": [
"string"
],
"chatter_stability_lobe": {
"rpm_to_max_axial_depth_mm": [
{}
]
},
"coolant": [
"flood"
],
"iso_286_achievable_grade": "IT01",
"klass": "milling.3axis_vmc",
"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,
"nominal_tool_change_time_s_by_class": {},
"positioning_accuracy_mm": 0.01,
"rapid_traverse_m_per_min": 24,
"repeatability_mm": 0.005,
"schema_version": "1.0.0",
"spindle_power_kw": 0,
"subclass": "small",
"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,
"space_eur_per_m2_y": 0,
"tooling_eur_per_year": 0
},
"workholding": [
"vise.3jaw"
]
},
"hourly_rate_eur": 0,
"klass": "milling.3axis_vmc",
"model_no": "string",
"name": "string",
"parent_template_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"subclass": "small",
"valid_from": "2026-06-01",
"valid_to": "2026-06-01",
"vendor": "string"
},
"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 |
|---|---|---|
fleet_priority |
integer | Orders machines within the environment; lower values are tried first. |
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. |
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
{
"fleet_priority": 0,
"is_enabled": true,
"machine": {
"burden_rate_eur": 0,
"capabilities": {},
"hourly_rate_eur": 0,
"id": "string",
"klass": "string",
"model_no": "string",
"name": "string",
"subclass": "string",
"valid_from": "string",
"valid_to": "string",
"vendor": "string"
},
"membership_id": "string",
"valid_from": "string",
"valid_to": "string"
}
Detach Machine
DELETE /api/v1/parts/environments/{env_id}/machines/{membership_id}
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/parts/environments/{env_id}/machines/{membership_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.delete(
"https://api.arcnm.io/api/v1/parts/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/parts/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/parts/environments/{env_id}/machines/{membership_id}
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/parts/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/parts/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/parts/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 |
|---|---|---|
fleet_priority |
integer | Orders machines within the environment; lower values are tried first. |
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. |
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
{
"fleet_priority": 0,
"is_enabled": true,
"machine": {
"burden_rate_eur": 0,
"capabilities": {},
"hourly_rate_eur": 0,
"id": "string",
"klass": "string",
"model_no": "string",
"name": "string",
"subclass": "string",
"valid_from": "string",
"valid_to": "string",
"vendor": "string"
},
"membership_id": "string",
"valid_from": "string",
"valid_to": "string"
}
List Rates
GET /api/v1/parts/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, or fx). |
Request
curl -X GET https://api.arcnm.io/api/v1/parts/environments/{env_id}/rates \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/parts/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/parts/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. |
Upsert Rate
POST /api/v1/parts/environments/{env_id}/rates
Create a rate row of the requested kind. Effective-dated: the new
row's valid_from opens a window; the previous active row's
valid_to should be capped by the caller.
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/parts/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/parts/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/parts/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 |
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. |
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, or fx. |
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",
"env_id": "string",
"fields": {},
"id": "string",
"kind": "string",
"valid_from": "string",
"valid_to": "string"
}
Delete Rate
DELETE /api/v1/parts/environments/{env_id}/rates/{kind}/{rate_id}
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, or fx). |
rate_id |
path | string | yes | Identifier of the rate. |
Request
curl -X DELETE https://api.arcnm.io/api/v1/parts/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/parts/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/parts/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 |
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"
}