API reference
Calibration
The Calibration API teaches an environment your real costs: submit observations and auto-calibrate its prediction interval, learning curve, and machine policy…
The Calibration API teaches an environment your real costs: submit observations and auto-calibrate its prediction interval, learning curve, and machine policy from your actuals.
Auto-generated from the public OpenAPI spec — this page never drifts from the running API. Base URL
https://api.arcnm.io. Authenticate with theX-API-Keyheader (see Authentication).
One-click environment calibration from (part_revision_id, actual_unit_cost) rows.
POST /api/v1/calibration/environments/{env_id}/auto-calibrate
The headline customer-facing endpoint — turn a list of ERP actual unit costs into a calibrated environment in one call. Each actual is paired with the most recent successful quote for its part at its lot size in this environment, priced by the current version of the costing model, and the calibration is fitted to the difference; nothing is re-quoted. Parts with no such quote — none at all, or only one priced before the costing model last changed — are returned in unmatched_part_ids so you can quote them and retry. Safe for AI-agent use over MCP and idempotent: the same set of actuals yields the same result.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
actuals |
AutoCalibrateActualRow[] | yes | Up to 20,000 (part_revision_id, actual_unit_cost) rows. Designed for ERP exports: an AI agent or a CSV uploader can stream a tenant's history in one call. |
basis |
herstellkosten | selbstkosten | angebotspreis |
no | Which rung of the cost sheet these actuals are measured at: 'herstellkosten' (manufacturing cost per unit — what a work order books), 'selbstkosten' (total cost per unit, including administration, selling, freight and duty) or 'angebotspreis' (the quoted price per unit, including the profit mark-up). Each is compared against the model's own figure at the same rung. Omit it only if you genuinely do not know: an unstated basis is read as manufacturing cost and the environment's calibration report flags how many rows were unstated. |
holdout_pct |
number | no | Fraction of rows held out to size the prediction interval. |
target_alpha |
number | no | Target miss rate for the prediction interval (default 0.10 → a 90% interval). |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/environments/{env_id}/auto-calibrate \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"actuals": [
{
"actual_unit_cost": 0,
"annual_volume": 500,
"bin": "string",
"lot_size": 50,
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"tooling_lifetime_units": 0
}
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/auto-calibrate",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"actuals": [
{
"actual_unit_cost": 0,
"annual_volume": 500,
"bin": "string",
"lot_size": 50,
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"tooling_lifetime_units": 0
}
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/auto-calibrate", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"actuals": [
{
"actual_unit_cost": 0,
"annual_volume": 500,
"bin": "string",
"lot_size": 50,
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"tooling_lifetime_units": 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 |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
Calibrate an env from raw ERP work-order rows.
POST /api/v1/calibration/environments/{env_id}/auto-calibrate-from-erp
ERP-native variant of auto-calibrate: accepts the raw columns most ERPs export (part_number, work_order_id, quantity, total_cost) and handles part-number resolution + unit-cost derivation server-side. Idempotent on work_order_id.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
holdout_pct |
number | no | Fraction of rows held out to size the prediction interval. |
target_alpha |
number | no | Target miss rate for the prediction interval (0.10 → a 90% interval). |
work_orders |
ErpWorkOrderPayload[] | yes | Up to 20 000 raw ERP work-order lines. Designed for the AI-agent ERP-import use case: pull tenant's last 12 months of WIP_DISCRETE_JOBS rows, POST them once, and the service resolves part numbers, decomposes setup vs variable cost from the lot-size diversity, and returns a calibrated env. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/environments/{env_id}/auto-calibrate-from-erp \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"work_orders": [
{
"annual_forecast": 0,
"part_number": "BRACKET-001",
"quantity": 0,
"total_cost": 642,
"work_order_id": "string"
}
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/auto-calibrate-from-erp",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"work_orders": [
{
"annual_forecast": 0,
"part_number": "BRACKET-001",
"quantity": 0,
"total_cost": 642,
"work_order_id": "string"
}
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/auto-calibrate-from-erp", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"work_orders": [
{
"annual_forecast": 0,
"part_number": "BRACKET-001",
"quantity": 0,
"total_cost": 642,
"work_order_id": "string"
}
]
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
404 |
not_found |
A referenced resource doesn't exist or isn't visible to your organisation. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
Per-driver calibrated-environment report for one environment.
GET /api/v1/calibration/environments/{env_id}/calibration-report
Calibration status for an environment: whether it has been calibrated to your actuals and, for the first-party app, the per-driver adjustments (machine / labour / overhead) with their confidence intervals. Public integrations receive the status only.
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/calibration/environments/{env_id}/calibration-report \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/calibration-report",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/calibration-report", {
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 |
|---|---|---|
calibrated |
boolean | True when this environment has been calibrated to your actuals. |
drivers |
object[] | Per-driver calibrated adjustments (machine / labour / overhead), each a structured object with the adjustment, its confidence interval, and whether it was applied. |
env_id |
string | Identifier of the environment this report describes. |
material |
object | Pinned (multiplier 1.0) in the per-driver adjustments: no machine, labour or overhead adjustment scales the material line. Where an environment is calibrated with a single overall correction of the unit cost instead, that correction scales material together with every other line. The price per kg is the one you state, or the platform's reference price where you state none; the quantity is the stock the part is cut from; scrap and remnant credits apply only once you state them, at your own scrap price or the platform's reference price where you state none. |
note |
string | Present on the public API only: points integrations at the app for the per-driver detail. |
provenance |
object | Fit provenance: last_fit_at, corpus_hash, n_effective, run id. |
residual |
object | The explicit residual multiplier (tooling/subcontract catch-all). |
retired_drivers |
object[] | Adjustments calibration no longer makes, each with the reason (for example material_quantity_modelled: the material quantity is modelled rather than adjusted). A correction stored for one is not applied, and the next calibration run removes it. |
Example response
{
"calibrated": true,
"drivers": [
{}
],
"env_id": "string",
"material": {},
"note": "string",
"provenance": {},
"residual": {},
"retired_drivers": [
{}
]
}
Turn an environment's calibration off.
POST /api/v1/calibration/environments/{env_id}/deactivate
Retire the environment's active calibration without replacing it: quotes priced after this call use the uncalibrated defaults. The run history is kept, so any earlier run can be restored with the revert endpoint. Fails when no calibration is active.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/environments/{env_id}/deactivate \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/deactivate",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/deactivate", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
404 |
not_found |
A referenced resource doesn't exist or isn't visible to your organisation. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
Recalibrate an environment from its accumulated observations.
POST /api/v1/calibration/environments/{env_id}/finalize
Recalibrate this environment for the selected target metric using every observation submitted for it so far, then activate the result. Use after submitting observations. Only an observation whose predicted value comes from the current version of the costing model is fitted: one submitted before the model last changed, or naming a calculation priced before it, is left out and counted in message, and a run with none left is refused. To start from raw ERP actuals without submitting observations first, use the one-click auto-calibrate endpoint instead.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
notes |
string | no | Optional free-text note recorded with the run. |
target_alpha |
number | no | Target miss rate for the prediction interval (0.10 → a 90% interval). |
target_metric |
cycle_time_s | setup_time_s | programming_time_s | unit_cost | machine_choice |
no | Metric to calibrate the environment for. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/environments/{env_id}/finalize \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"notes": "string",
"target_alpha": 0.1,
"target_metric": "cycle_time_s"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/finalize",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"notes": "string",
"target_alpha": 0.1,
"target_metric": "cycle_time_s"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/finalize", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"notes": "string",
"target_alpha": 0.1,
"target_metric": "cycle_time_s"
}),
})
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 |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
Calibrate cost-down-with-volume from lot-progression actuals.
POST /api/v1/calibration/environments/{env_id}/learning-curve-fit
Calibrate how this environment's per-unit cost falls as cumulative production grows, using a sequence of production-lot actuals for one part. Choose the cumulative model (the safer default, observed directly) or the per-unit model. Submit at least three lots.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
lots |
LearningCurveLotPayload[] | yes | Sequence of production-lot actuals to fit; at least three. |
model |
crawford_unit | wright_cumulative |
no | Learning-curve model to fit (per-unit or cumulative). |
part_revision_id |
string | yes | Identifier of the part revision the lots belong to. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/environments/{env_id}/learning-curve-fit \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lots": [
{
"lot_index": 0,
"lot_size": 50,
"lot_total_cost": 0
}
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/learning-curve-fit",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lots": [
{
"lot_index": 0,
"lot_size": 50,
"lot_total_cost": 0
}
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/learning-curve-fit", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"lots": [
{
"lot_index": 0,
"lot_size": 50,
"lot_total_cost": 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 |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
What would improve this environment's calibration most.
GET /api/v1/calibration/environments/{env_id}/next-best-evidence
Ranks the missing evidence for an environment: which parts or lot sizes to submit actuals for next, and where the model is capped and needs a configuration review instead of more data. Safe for AI agent use: read-only.
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/calibration/environments/{env_id}/next-best-evidence \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/next-best-evidence",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/next-best-evidence", {
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 |
|---|---|---|
env_id |
string | The environment this advice is for. |
items |
NextBestEvidenceItem[] | Ranked asks; empty when the corpus is already rich. |
Example response
{
"env_id": "string",
"items": [
{
"ask": "string",
"code": "other",
"count": 0,
"process": "string",
"rank": 0,
"reason": "string"
}
]
}
Calibrate which machine an environment recommends from past choices.
POST /api/v1/calibration/environments/{env_id}/policy/calibrate
Calibrate how this environment chooses a machine, using a list of historical 'the shop actually picked machine X' decisions. Once calibrated, the environment's machine recommendations better match your shop's real choices.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
history |
object[] | yes | Past machine-choice records, each with candidates and the chosen_machine_id. |
region |
string | no | Region whose seed policy to start from (e.g. DE-BY); null for the default. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/environments/{env_id}/policy/calibrate \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"history": [
{}
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/policy/calibrate",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"history": [
{}
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/policy/calibrate", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"history": [
{}
]
}),
})
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 |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
Calibration run history for one environment.
GET /api/v1/calibration/environments/{env_id}/runs
Reverse-chronological list of this environment's calibration runs: when each ran, how many observations it used, and whether its result is the currently active calibration. Use it to pick the run to revert to. Read-only; safe for AI agent use.
Paginated. Pass
limitandoffsetto page through results.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
limit |
query | integer | no | Runs per page. |
offset |
query | integer | no | Runs to skip. |
Request
curl -X GET https://api.arcnm.io/api/v1/calibration/environments/{env_id}/runs \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/runs",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/runs", {
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 |
|---|---|---|
env_id |
string | Environment this history belongs to. |
limit |
integer | Page size used for this response. |
offset |
integer | Runs skipped before this page. |
runs |
CalibrationRunHistoryItem[] | Runs, newest first. |
total |
integer | Total runs in the environment's history. |
Example response
{
"env_id": "string",
"limit": 0,
"offset": 0,
"runs": [
{
"created_at": "string",
"id": "string",
"is_active": true,
"n_observations": 0,
"run_kind": "string"
}
],
"total": 0
}
Restore an earlier calibration run's result.
POST /api/v1/calibration/environments/{env_id}/runs/{run_id}/revert
Make an earlier run's calibration the active one again: the current result is retired and the selected run's is restored, exactly as it was fitted — except an adjustment calibration no longer makes (retired_drivers on the calibration report), which is not restored. Nothing is refit and no history is lost — every run stays in the history and can be restored again. Quotes priced after this call use the restored calibration.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
run_id |
path | string | yes | Identifier of the run. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/environments/{env_id}/runs/{run_id}/revert \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/runs/{run_id}/revert",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/runs/{run_id}/revert", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
404 |
not_found |
A referenced resource doesn't exist or isn't visible to your organisation. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
Calibration health summary for one environment.
GET /api/v1/calibration/environments/{env_id}/status
Read view of an environment's calibration health: whether it is calibrated, when it was last calibrated, and how many submitted observations are waiting to be applied. Designed as the AI agent's discovery endpoint — call this first to decide whether to submit fresh observations or recalibrate.
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/calibration/environments/{env_id}/status \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/status",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/status", {
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 |
|---|---|---|
env_id |
string | Identifier of the environment this status describes. |
is_calibrated |
boolean | Whether this environment has been calibrated to your actuals. |
last_fit_at |
string | ISO 8601 timestamp of the last calibration; null if never run. |
observations |
object[] | Per-source counts of submitted observations awaiting application. |
Example response
{
"env_id": "string",
"is_calibrated": true,
"last_fit_at": "string",
"observations": [
{}
]
}
Calibrate an environment from (predicted, actual) pairs.
POST /api/v1/calibration/environments/{env_id}/teach
Calibrate an environment from caller-supplied (predicted, actual) rows. Each predicted value must come from a current quote for the part: a part this environment priced only before the costing model last changed has none, and a request naming one is refused until the part is priced again. If you only have actuals, use the one-click auto-calibrate endpoint, which pairs each actual with the part's current quote server-side.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
env_id |
path | string | yes | Identifier of the env. |
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
holdout_pct |
number | no | Fraction (0.05–0.5) of rows held out to size the prediction interval. |
rows |
TeachRow[] | yes | Between 1 and 20,000 (predicted, actual) rows to calibrate from. |
target_alpha |
number | no | Target miss rate for the prediction interval (0.10 → a 90% interval). |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/environments/{env_id}/teach \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"rows": [
{
"actual": 0,
"bin": "string",
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"predicted": 0,
"target_metric": "unit_cost"
}
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/environments/{env_id}/teach",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"rows": [
{
"actual": 0,
"bin": "string",
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"predicted": 0,
"target_metric": "unit_cost"
}
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/environments/{env_id}/teach", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"rows": [
{
"actual": 0,
"bin": "string",
"part_revision_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"predicted": 0,
"target_metric": "unit_cost"
}
]
}),
})
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 |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
Ask whether your ERP part numbers resolve, before you calibrate.
POST /api/v1/calibration/erp-preflight
Resolves the part_number column of an ERP export against the parts you hold and returns how many match. Reads only — it needs no environment and it writes nothing.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
part_numbers |
string[] | yes | The part_number column of the work orders you are about to send, in any order. Duplicates are fine. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/erp-preflight \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"part_numbers": [
"string"
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/erp-preflight",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"part_numbers": [
"string"
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/erp-preflight", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"part_numbers": [
"string"
]
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
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 |
|---|---|---|
matched |
integer | How many of them resolve to a part you hold. |
requested |
integer | Distinct part numbers you asked about. |
unknown |
integer | How many do not. |
unknown_part_numbers |
string[] | The ones that do not, up to 100 of them — the list to fix before you run the calibration. |
Example response
{
"matched": 0,
"requested": 0,
"unknown": 0,
"unknown_part_numbers": [
"string"
]
}
Create a calibration import and disclose what pricing it will cost.
POST /api/v1/calibration/imports
Upload historical actuals as rows. Every row is resolved against your part library and bucketed: already priced (free), servable from cache (free), needs a new calculation (billable), or unusable (with the fix named). Only a quote priced by the current version of the costing model counts as already priced; a part quoted only before the model last changed needs a new calculation. NOTHING is priced or spent by this call — the disclosed calculations_required is committed explicitly via POST /calibration/imports/{id}/price. Re-posting the same rows returns the same import until the costing model changes; after that, the same rows start a new import.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
basis |
herstellkosten | selbstkosten | angebotspreis |
no | Which rung of the cost sheet the whole file is measured at: 'herstellkosten' (manufacturing cost per unit — what a work order books), 'selbstkosten' (total cost per unit, including administration, selling, freight and duty) or 'angebotspreis' (the price per unit, including the profit mark-up). One basis per file, like one currency per file. Each row is compared against the model's own figure at the same rung. A column header cannot answer this — 'unit cost' and 'unit price' are used interchangeably in exports — so it is asked rather than guessed. Omit it only if you genuinely do not know: an unstated basis is read as manufacturing cost and the environment's calibration report flags how many rows were unstated. Fixed when the import is created; re-posting the same rows returns the existing import unchanged. |
currency |
string | no | Currency the actuals are denominated in. Must match the environment's currency; defaults to it. |
env_id |
string | yes | The costing environment to calibrate. |
rows |
CalibrationImportRowIn[] | yes | Up to 20,000 historical actuals. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/imports \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"rows": [
{
"actual_unit_cost": 0,
"annual_volume": 500,
"bin": "string",
"lot_size": 1,
"part_number": "BRACKET-001",
"source_ref": "string"
}
]
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/imports",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"rows": [
{
"actual_unit_cost": 0,
"annual_volume": 500,
"bin": "string",
"lot_size": 1,
"part_number": "BRACKET-001",
"source_ref": "string"
}
]
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/imports", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"env_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"rows": [
{
"actual_unit_cost": 0,
"annual_volume": 500,
"bin": "string",
"lot_size": 1,
"part_number": "BRACKET-001",
"source_ref": "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. |
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 |
|---|---|---|
calculations_free |
integer | Rows already priced (or servable from cache) — free. |
calculations_remaining |
integer | Your plan's remaining calculation allowance, when known. |
calculations_required |
integer | Distinct (part, lot) pairs that need a NEW calculation — the billable spend POST /price will commit. Nothing is priced by this call. |
created |
boolean | False when these exact rows were already imported into this environment — the existing import is returned instead of a duplicate. |
distinct_lot_sizes |
integer | Distinct lot sizes across usable rows. Below two the fit cannot split setup cost from per-part cost. |
env_id |
string | The environment this import calibrates. |
import_id |
string | Identifier of the created import. |
message |
string | The cost disclosure, in plain language. |
parts_without_cad |
string[] | Parts with no STEP file to calculate from — upload one. |
rows_received |
integer | Rows submitted. |
rows_usable |
integer | Rows that can become calibration observations. |
status |
string | Import lifecycle status. |
unresolved_part_numbers |
string[] | Part numbers not in your library — add the parts. |
Example response
{
"calculations_free": 0,
"calculations_remaining": 0,
"calculations_required": 0,
"created": true,
"distinct_lot_sizes": 0,
"env_id": "string",
"import_id": "string",
"message": "string",
"parts_without_cad": [
"string"
],
"rows_received": 0,
"rows_usable": 0,
"status": "string",
"unresolved_part_numbers": [
"string"
]
}
Import status: per-row states and whether the priced pairs settled.
GET /api/v1/calibration/imports/{import_id}
Joins every priced row to its calculation and folds terminal outcomes back onto the rows (succeeded → priced, failed → pricing_failed, verbatim reason attached). settled is the green light for POST /calibrate. Rows are capped at 1000 in the response; the counts never are.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
import_id |
path | string | yes | Identifier of the import. |
Request
curl -X GET https://api.arcnm.io/api/v1/calibration/imports/{import_id} \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.get(
"https://api.arcnm.io/api/v1/calibration/imports/{import_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/calibration/imports/{import_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 |
|---|---|---|
calculations_required |
integer | Billable pairs disclosed at preflight. |
calculations_run |
integer | Calculations actually created by the price commits. |
env_id |
string | |
error |
string | Import-level failure, when one happened. |
import_id |
string | |
pending_calculations |
integer | Rows whose calculation is still queued or running. |
rows |
CalibrationImportRowPublic[] | |
rows_truncated |
boolean | True when more than 1000 rows exist; counts are never truncated. |
settled |
boolean | True when no priced pair can change any more — the moment POST /calibrate will run. |
state_counts |
object | Row count per disposition state. |
status |
string |
Example response
{
"calculations_required": 0,
"calculations_run": 0,
"env_id": "string",
"error": "string",
"import_id": "string",
"pending_calculations": 0,
"rows": [
{
"actual_unit_cost": 0,
"annual_volume": 500,
"calculation_id": "string",
"detail": "string",
"lot_size": 50,
"part_number": "BRACKET-001",
"row_ordinal": 0,
"state": "string"
}
],
"rows_truncated": false,
"settled": true,
"state_counts": {},
"status": "string"
}
Fit the environment on every settled (part, lot) actual.
POST /api/v1/calibration/imports/{import_id}/calibrate
Calibrates the environment on every now-matching row, exactly as the one-click auto-calibrate endpoint would, and returns the same result shape (mape_before → mape_after). Refused (409) while priced pairs are still running. Metered as one calibration run. Idempotent: a completed import replays its stored result without metering a second run.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
import_id |
path | string | yes | Identifier of the import. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/imports/{import_id}/calibrate \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/imports/{import_id}/calibrate",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/imports/{import_id}/calibrate", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
404 |
not_found |
A referenced resource doesn't exist or isn't visible to your organisation. |
409 |
conflict |
A conflicting change, or an Idempotency-Key reused with a different body. |
429 |
rate_limited |
Per-key or per-org rate limit exceeded — back off with jitter and retry. |
Response body 200
| Field | Type | Description |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}
Commit the disclosed spend: price every unpriced (part, lot) pair.
POST /api/v1/calibration/imports/{import_id}/price
Creates and enqueues one calculation per unpriced (part, lot) pair. Each is billed, deduplicated and admitted exactly like a calculation you run directly — an identical prior run is served free. Idempotent: rows already priced or queued are skipped, so a retry resumes instead of duplicating. Poll GET /calibration/imports/{id} until settled.
Parameters
| Name | In | Type | Required | Description |
|---|---|---|---|---|
import_id |
path | string | yes | Identifier of the import. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/imports/{import_id}/price \
-H "X-API-Key: $ARCNM_API_KEY"
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/imports/{import_id}/price",
headers={"X-API-Key": "YOUR_API_KEY"},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/imports/{import_id}/price", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
},
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
202 |
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 202
| Field | Type | Description |
|---|---|---|
accepted |
integer | Rows whose calculation was created and enqueued. |
calculations_created |
integer | New calculation rows this call created. |
import_id |
string | |
pairs_priced |
integer | Distinct (part, lot) pairs fanned out by THIS call. |
parked |
integer | Rows whose calculation was created but parked on billing (spend cap / approval); resumable, not running. |
remaining_needs_calc |
integer | Rows still unpriced after this call (0 when fully committed). |
served_from_cache |
integer | Rows answered instantly from an identical prior run — never billed. |
skipped |
integer | Rows already priced or already queued before this call — a re-POST resumes, it never duplicates. |
status |
string | Import lifecycle status after the commit. |
Example response
{
"accepted": 0,
"calculations_created": 0,
"import_id": "string",
"pairs_priced": 0,
"parked": 0,
"remaining_needs_calc": 0,
"served_from_cache": 0,
"skipped": 0,
"status": "string"
}
Submit calibration observations for an environment.
POST /api/v1/calibration/outcomes
Submit a batch of observations (predicted vs. actual outcomes) for an environment so it can be calibrated. Tenant-bound; scope='environment' only. Safe for AI agent use: idempotent on each observation's natural key and rate-limited by the global tenant policy. Recalibrate the environment afterwards to apply the accumulated observations. Take each predicted value from a current quote: an observation is recorded with the version of the costing model current when it is submitted, and once the model changes, recalibration leaves it out until it is submitted again with a predicted value from the new model.
Request body (application/json)
| Field | Type | Required | Description |
|---|---|---|---|
basis |
herstellkosten | selbstkosten | angebotspreis |
no | Which rung of the cost sheet these numbers are measured at: 'herstellkosten' (manufacturing cost per unit — what a work order books), 'selbstkosten' (total cost per unit, including administration, selling, freight and duty) or 'angebotspreis' (the quoted price per unit, including the profit mark-up). Each is compared against the model's own figure at the same rung. Omit it only if you genuinely do not know: an unstated basis is read as manufacturing cost and the environment's calibration report flags how many rows were unstated. |
env_id |
string | no | Target environment; required when scope is 'environment'. |
observations |
object[] | no | Up to 20,000 observations; each carries target_metric, observation_type, predicted, and actual. |
oracle |
string | yes | Source of the observations (e.g. operator report, MES, supplier quote). |
scope |
platform | environment |
yes | Scope of the observations; tenant tokens may submit 'environment' only. |
Request
curl -X POST https://api.arcnm.io/api/v1/calibration/outcomes \
-H "X-API-Key: $ARCNM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"scope": "platform",
"oracle": "string"
}'
import requests
resp = requests.post(
"https://api.arcnm.io/api/v1/calibration/outcomes",
headers={"X-API-Key": "YOUR_API_KEY"},
json={
"scope": "platform",
"oracle": "string"
},
)
resp.raise_for_status()
print(resp.json())
const resp = await fetch("https://api.arcnm.io/api/v1/calibration/outcomes", {
method: "POST",
headers: {
"X-API-Key": process.env.ARCNM_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({
"scope": "platform",
"oracle": "string"
}),
})
const data = await resp.json()
Responses
| Status | Description |
|---|---|
200 |
Successful Response |
422 |
Validation Error |
Errors
Standard error responses — see the Errors catalog for the full envelope, request_id, and retry-safety table.
| Status | Code | When |
|---|---|---|
401 |
invalid_api_key |
Missing, malformed, or revoked API key. |
403 |
insufficient_scope |
The key is valid but lacks a scope this endpoint requires. |
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 |
|---|---|---|
a |
number | Fitted first-unit cost coefficient of the curve. |
b |
number | Fitted log-log slope exponent of the learning curve. |
env_id |
string | Identifier of the calibrated environment. |
fit |
object | Structured object with the fitted parameters and fit diagnostics. |
fitted_accuracy |
number | Accuracy (0–1) of the fitted policy on the training rows. |
fitted_policy |
object | Structured object describing the fitted selection policy. |
grid_size |
integer | Number of policy candidates evaluated during the fit. |
history_received |
integer | Historical choice records supplied in the request. |
history_rejected |
object[] | Records that could not be used, each with its position in the request and why. Capped at 50 entries; the counts above are never truncated. |
history_used |
integer | Records the fit actually used. Lower than history_received means some could not be read; see history_rejected. |
holdout_accuracy |
number | Accuracy (0–1) of the fitted policy on held-out rows. |
holdout_coverage |
number | Fraction (0–1) of held-out actuals falling inside the interval. |
holdout_mape |
number | Mean absolute percentage error of the PRE-calibration predictions on the held-out rows — the miss this run set out to close, not the outcome. Read mape_after for the result. |
id |
string | Identifier of the resulting record, when applicable. |
ingested |
integer | Number of observations persisted by an ingest call. |
learning_rate |
number | Learning rate (0–1): cost retained each time output doubles. |
mape_after |
number | Holdout MAPE AFTER the fit — the held-out rows re-priced under the calibrated result. The run's headline outcome; null when no rows were held out. |
mape_before |
number | Holdout MAPE before the fit (same figure as holdout_mape, named for the before/after pair). |
matched |
integer | Number of supplied rows matched to an existing quote. |
message |
string | Human-readable note about the run, when present. |
model |
string | Learning-curve model that was fit (crawford_unit or wright_cumulative). |
n_holdout |
integer | Number of rows held out to size the prediction interval. |
n_iterations |
integer | Iterations the midpoint solver ran before converging. |
n_lots |
integer | Number of production lots used in the curve fit. |
n_observations |
integer | Number of observations the calibration fit used. |
n_train |
integer | Number of rows used to fit the calibration. |
part_revision_id |
string | Identifier of the part revision that was fit. |
policy_history_id |
string | Identifier of the newly written selection-policy row. |
r_squared |
number | Coefficient of determination (0–1) of the curve fit. |
residual_sigma_log |
number | Standard deviation of the fit residuals in log space. |
run_id |
string | Identifier of the calibration run, when one was created. |
samples |
integer | Number of samples considered by the run. |
samples_applied |
integer | Number of samples actually applied to the fit. |
scope |
string | Scope the run applied to: 'platform' or 'environment'. |
seed_accuracy |
number | Accuracy (0–1) of the seed policy before calibration. |
status |
string | Outcome status of the calibration run. |
target_metric |
string | Metric the calibration targeted (e.g. 'unit_cost'). |
unknown_part_numbers |
string[] | Part numbers that could not be resolved to a known part. |
unmatched_part_ids |
string[] | Part revision IDs with no quote at their lot size priced by the current version of the costing model, so they were skipped. |
work_orders_received |
integer | Number of ERP work-order rows received. |
work_orders_resolved |
integer | Number of work orders resolved to a known part revision. |
Example response
{
"a": 0,
"b": 0,
"env_id": "string",
"fit": {},
"fitted_accuracy": 0,
"fitted_policy": {},
"grid_size": 0,
"history_received": 0,
"history_rejected": [
{}
],
"history_used": 0,
"holdout_accuracy": 0,
"holdout_coverage": 0.9,
"holdout_mape": 0.05,
"id": "string",
"ingested": 0,
"learning_rate": 0,
"mape_after": 0.05,
"mape_before": 0.05,
"matched": 0,
"message": "string",
"model": "string",
"n_holdout": 0,
"n_iterations": 0,
"n_lots": 0,
"n_observations": 0,
"n_train": 0,
"part_revision_id": "string",
"policy_history_id": "string",
"r_squared": 0,
"residual_sigma_log": 0,
"run_id": "string",
"samples": 0,
"samples_applied": 0,
"scope": "string",
"seed_accuracy": 0,
"status": "string",
"target_metric": "string",
"unknown_part_numbers": [
"string"
],
"unmatched_part_ids": [
"string"
],
"work_orders_received": 0,
"work_orders_resolved": 0
}