---
title: Environments
description: The Environments API manages costing environments.
---

# 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](../authentication.md)).

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

<CodeTabs>

```bash title="cURL"
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"
  }'
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

```json
{
  "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 `limit` and `offset` to 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**

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/environments/ \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `429` | `rate_limited` | Per-key or per-org rate limit exceeded — back off with jitter and retry. |


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

<CodeTabs>

```bash title="cURL"
curl -X DELETE https://api.arcnm.io/api/v1/parts/environments/{env_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

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

<CodeTabs>

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

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

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

<CodeTabs>

```bash title="cURL"
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"
    ]
  }'
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

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

<CodeTabs>

```bash title="cURL"
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"
  }'
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

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

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/environments/{env_id}/machines \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

<CodeTabs>

```bash title="cURL"
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"
  }'
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

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

<CodeTabs>

```bash title="cURL"
curl -X DELETE https://api.arcnm.io/api/v1/parts/environments/{env_id}/machines/{membership_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

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

<CodeTabs>

```bash title="cURL"
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"
  }'
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

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

<CodeTabs>

```bash title="cURL"
curl -X GET https://api.arcnm.io/api/v1/parts/environments/{env_id}/rates \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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|machine|overhead|material|fx |
| `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**

<CodeTabs>

```bash title="cURL"
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"
  }'
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `201` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

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

<CodeTabs>

```bash title="cURL"
curl -X DELETE https://api.arcnm.io/api/v1/parts/environments/{env_id}/rates/{kind}/{rate_id} \
  -H "X-API-Key: $ARCNM_API_KEY"
```

```python title="Python"
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())
```

```typescript title="TypeScript"
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()
```

</CodeTabs>

**Responses**

| Status | Description |
| --- | --- |
| `200` | Successful Response |
| `422` | Validation Error |

**Errors**

Standard error responses — see the [Errors catalog](../errors.md) for the full envelope, `request_id`, and retry-safety table.

| Status | Code | When |
| --- | --- | --- |
| `401` | `invalid_api_key` | Missing, malformed, or revoked API key. |
| `403` | `insufficient_scope` | The key is valid but lacks a scope this endpoint requires. |
| `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**

```json
{
  "message": "string"
}
```
