ARCNM

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 the X-API-Key header (see Authentication).

Create Environment

POST /api/v1/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.
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/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.
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.
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).
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,
  "machines_cloned": 0,
  "name": "string",
  "parent_environment_id": "string",
  "rates_cloned": 0,
  "region": "EU",
  "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; limit caps 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}

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
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.
id string Unique identifier of the costing environment.
is_baseline boolean Whether this is the auto-provisioned default environment for the tenant.
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.
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).
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",
  "id": "string",
  "is_baseline": true,
  "machines_cloned": 0,
  "name": "string",
  "parent_environment_id": "string",
  "rates_cloned": 0,
  "region": "EU",
  "valid_from": "string",
  "valid_to": "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.
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.
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).
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,
  "machines_cloned": 0,
  "name": "string",
  "parent_environment_id": "string",
  "rates_cloned": 0,
  "region": "EU",
  "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

Example response

{
  "as_of": "2026-06-01",
  "calibrated": true,
  "currency": "EUR",
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "mode": "per_driver",
  "note": "string"
}

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.

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

Example response

{
  "as_of": "2026-06-01",
  "calibrated": true,
  "currency": "EUR",
  "env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "mode": "per_driver",
  "note": "string"
}

Update Environment Identity

PUT /api/v1/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.
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.
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.
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).
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,
  "machines_cloned": 0,
  "name": "string",
  "parent_environment_id": "string",
  "rates_cloned": 0,
  "region": "EU",
  "valid_from": "string",
  "valid_to": "string"
}

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.

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}/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

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,
        "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,
        "min_material_thickness_mm": 0,
        "nominal_tool_change_time_s_by_class": {},
        "pallet_change_time_s": 0,
        "positioning_accuracy_mm": 0.01,
        "rapid_traverse_m_per_min": 24,
        "repeatability_mm": 0.005,
        "saw_blade_cost_eur": 0,
        "saw_blade_life_mm2": 0,
        "saw_blade_type": "bimetal",
        "saw_kerf_mm": 0,
        "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",
      "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,
                "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,
                "min_material_thickness_mm": 0,
                "nominal_tool_change_time_s_by_class": {},
                "pallet_change_time_s": 0,
                "positioning_accuracy_mm": 0.01,
                "rapid_traverse_m_per_min": 24,
                "repeatability_mm": 0.005,
                "saw_blade_cost_eur": 0,
                "saw_blade_life_mm2": 0,
                "saw_blade_type": "bimetal",
                "saw_kerf_mm": 0,
                "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",
            "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,
        "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,
        "min_material_thickness_mm": 0,
        "nominal_tool_change_time_s_by_class": {},
        "pallet_change_time_s": 0,
        "positioning_accuracy_mm": 0.01,
        "rapid_traverse_m_per_min": 24,
        "repeatability_mm": 0.005,
        "saw_blade_cost_eur": 0,
        "saw_blade_life_mm2": 0,
        "saw_blade_type": "bimetal",
        "saw_kerf_mm": 0,
        "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",
      "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
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.
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,
  "health": "ok",
  "health_error": "string",
  "is_enabled": true,
  "machine": {
    "burden_rate_eur": 0,
    "capabilities": {},
    "hourly_rate_eur": 0,
    "id": "string",
    "klass": "string",
    "model_no": "string",
    "name": "string",
    "rate_operator_eur_per_h": 0,
    "rate_operator_share": 0,
    "source": "custom",
    "subclass": "string",
    "valid_from": "string",
    "valid_to": "string",
    "vendor": "string"
  },
  "membership_id": "string",
  "valid_from": "string",
  "valid_to": "string"
}

Detach Machine

DELETE /api/v1/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/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}

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
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.
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,
  "health": "ok",
  "health_error": "string",
  "is_enabled": true,
  "machine": {
    "burden_rate_eur": 0,
    "capabilities": {},
    "hourly_rate_eur": 0,
    "id": "string",
    "klass": "string",
    "model_no": "string",
    "name": "string",
    "rate_operator_eur_per_h": 0,
    "rate_operator_share": 0,
    "source": "custom",
    "subclass": "string",
    "valid_from": "string",
    "valid_to": "string",
    "vendor": "string"
  },
  "membership_id": "string",
  "valid_from": "string",
  "valid_to": "string"
}

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, or subcontract).

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.

Upsert Rate

POST /api/v1/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/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

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, or subcontract.
unhonoured_reason string Why the row is never applied, when engine_honoured is false: 'currency_mismatch' (row currency differs from the environment's; labour/overhead/material rows only — fx rows carry their own currency pair, and a subcontract row's own currency field is not read at all), '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), or 'value_not_positive' (the row's amount is zero or negative and is skipped). Null otherwise.
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}

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

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, or subcontract.
unhonoured_reason string Why the row is never applied, when engine_honoured is false: 'currency_mismatch' (row currency differs from the environment's; labour/overhead/material rows only — fx rows carry their own currency pair, and a subcontract row's own currency field is not read at all), '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), or 'value_not_positive' (the row's amount is zero or negative and is skipped). Null otherwise.
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 models packaging, deburring, and part loading — the practice answers that tailor time allowances to the way your shop actually works.

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, part loading). Each answer adjusts the environment's time allowances within validated bounds and is recorded in the audit log. 'standard' restores the platform behaviour.

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' restores the platform behaviour.

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": {}
}

Get Calculation Tuning

GET /api/v1/environments/{env_id}/tuning

Read the environment's calculation-tuning knobs with provenance ("platform default vs your override").

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
learning_rate TuningKnob Wright learning-curve rate applied to the lot-size curve (1.0 = off).
programming_factor TuningKnob Global multiplier on estimated programming times across all process bins.
setup_factor TuningKnob Global multiplier on estimated setup times across all process bins.

Example response

{
  "learning_rate": {
    "platform_default": 0,
    "source": "string",
    "value": 0
  },
  "programming_factor": {
    "platform_default": 0,
    "source": "string",
    "value": 0
  },
  "setup_factor": {
    "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 write the per-bin setup_efficiency / programming_efficiency resolver overrides for every routable process bin at ENV tier; 1.0 clears the override (back to platform default). learning_rate writes the env's Wright rate. Every change lands an audit row.

Parameters

Name In Type Required Description
env_id path string yes Identifier of the env.

Request body (application/json)

Field Type Required Description
learning_rate number no Wright rate in [0.5, 1.0]; 1.0 disables learning.
programming_factor number no Programming-time multiplier; omit to keep, 1.0 to reset to default.
setup_factor number no Setup-time multiplier; omit to keep, 1.0 to reset to default.

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 '{
    "learning_rate": 0,
    "programming_factor": 0,
    "setup_factor": 0
  }'
import requests

resp = requests.put(
    "https://api.arcnm.io/api/v1/environments/{env_id}/tuning",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={
        "learning_rate": 0,
        "programming_factor": 0,
        "setup_factor": 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({
    "learning_rate": 0,
    "programming_factor": 0,
    "setup_factor": 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
learning_rate TuningKnob Wright learning-curve rate applied to the lot-size curve (1.0 = off).
programming_factor TuningKnob Global multiplier on estimated programming times across all process bins.
setup_factor TuningKnob Global multiplier on estimated setup times across all process bins.

Example response

{
  "learning_rate": {
    "platform_default": 0,
    "source": "string",
    "value": 0
  },
  "programming_factor": {
    "platform_default": 0,
    "source": "string",
    "value": 0
  },
  "setup_factor": {
    "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.
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.
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).
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,
  "machines_cloned": 0,
  "name": "string",
  "parent_environment_id": "string",
  "rates_cloned": 0,
  "region": "EU",
  "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 org_id == PLATFORM_ORG_ID rows 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.