Skip to content

Complete API Reference

Base URL: http://localhost:8002
All routes prefixed with /api/
Interactive docs: http://localhost:8002/docs

Figure: FastAPI router topology — all routers are mounted under the single /api prefix.

mindmap
  root((FastAPI /api))
    auth
      login
      microsoft
      google
      users
    series
      data
      forecasts
      distributions
    analytics
      accuracy
      aggregate-demand
    pipeline
      run
      jobs
      workflows
    supply
      orders
      inventory
      exceptions
    meio
      scenarios
      results
    allocation
      overview

Authentication

Figure: request lifecycle — JWT verification, tenant DB resolution from account_id, pipeline_id guard on PIPE tables, then handler logic across PostgreSQL master and ClickHouse PIPE tables.

sequenceDiagram
    participant C as "Client"
    participant M as "FastAPI middleware"
    participant H as "Route handler"
    C->>M: "GET /api/... (Authorization: Bearer)"
    M->>M: "Verify JWT (sub, role, account_id)"
    M->>M: "Resolve tenant DB from account_id"
    M->>H: "request + tenant context"
    H->>H: "pipeline_id guard<br/>(required for PIPE_* endpoints)"
    H->>H: "Business logic (PG master + CH PIPE_*)"
    H-->>M: "Response JSON"
    M-->>C: "200 / 4xx / 5xx"

All endpoints (except /api/auth/login, /api/auth/microsoft, /api/auth/google) require a JWT bearer token.

Header format:

Authorization: Bearer <token>

The token is obtained from any of the login endpoints and stored in localStorage under the key mirabelle_token.

Token format: HS256 JWT containing sub (user ID), email, role, account_id, account_name, can_run_process, can_create_override.

Error responses:

{ "detail": "Error message" }

HTTP status codes: - 401 — missing or invalid token - 403 — authenticated but insufficient role/permission - 404 — resource not found - 400 — invalid request body or parameters - 500 — internal server error - 503 — database not available

Pagination pattern: Endpoints that return lists support skip (offset) and limit query parameters where applicable. Exception-log and process-log endpoints use page / page_size.


Auth — /api/auth/*

These routes are defined in files/api/auth.py and mounted at /api/auth/.


GET /api/auth/probe

Check which tenant accounts are available for an email address. Used for the superAdmin two-phase login flow.

Query params: - email (string, required)

Response:

{ "accounts": [{ "id": "uuid", "name": "Tenant Name" }] }


POST /api/auth/login

Local email/password login.

Body:

{
  "email": "user@example.com",
  "password": "secret",
  "account_id": "uuid"   // optional — superAdmin only
}

Success response:

{
  "access_token": "<jwt>",
  "user": {
    "id": 1,
    "email": "user@example.com",
    "name": "Alice",
    "role": "admin",
    "account_id": "uuid",
    "account_name": "Acme Corp"
  }
}

SuperAdmin multi-tenant response (when account_id not supplied):

{ "status": "select_account", "accounts": [...] }


POST /api/auth/microsoft

Microsoft OAuth login. Pass the MSAL access token from the browser SDK.

Body:

{ "access_token": "<msal_token>", "account_id": "uuid" }

Response: Same as local login.


POST /api/auth/google

Google OAuth login. Pass the Google ID credential from the Google Sign-In button.

Body:

{ "credential": "<google_id_token>", "account_id": "uuid" }

Response: Same as local login.


POST /api/auth/switch-account

Switch the current session to a different tenant. Requires superAdmin or multi-account access.

Body:

{ "account_id": "uuid" }

Response: New token and user object.


GET /api/auth/me

Return the current user profile from the JWT.

Response:

{
  "id": 1, "email": "...", "name": "...", "role": "admin",
  "account_id": "uuid", "account_name": "Acme Corp",
  "can_run_process": true, "can_create_override": true,
  "allowed_segments": [], "allowed_segments_edit": []
}


POST /api/auth/users

Create a new user (admin only).

Body:

{
  "email": "new@example.com",
  "name": "Bob",
  "password": "secret123",
  "role": "user",
  "can_run_process": false,
  "can_create_override": false
}


GET /api/auth/users

List all users in the current tenant (admin only).


PUT /api/auth/users/{user_id}

Update a user (admin only). Same body shape as POST.


POST /api/auth/change-password

Change the current user's password.

Body:

{ "old_password": "...", "new_password": "..." }


POST /api/auth/logout

Invalidate the current session (best-effort; JWT is stateless so client-side cleanup is also needed).


Config and System — /api/config, /api/reload


GET /api/config

Return the assembled runtime configuration. Sensitive fields (password, secret, etc.) are redacted.

Response:

{
  "config": {
    "forecasting": { "horizon": 78, ... },
    "characterization": { ... },
    ...
  }
}


POST /api/config/update

Update one or more config keys and persist to the DB parameters table.

Body (single key):

{ "path": "forecasting.horizon", "value": 26 }

Body (batch):

{
  "updates": [
    { "path": "forecasting.horizon", "value": 26 },
    { "path": "characterization.intermittency.adi_threshold", "value": 1.5 }
  ]
}


POST /api/reload

Reload all in-memory data cache from PostgreSQL. Call after a pipeline run completes to refresh the API without restarting.

Response:

{ "status": "reloaded", "cache_sizes": { "forecasts": 12500, "series": 590 } }


GET /api/log-level

Return the current API log level.


PUT /api/log-level

Set the log level. Body: { "level": "DEBUG" }. Accepted values: DEBUG, INFO, WARNING, ERROR.


Series and Demand — /api/series/*, /api/items, /api/sites


GET /api/series

Paginated list of time series with characterization, best-method, classification, and cost data.

Query params: - skip (int, default 0) — offset for pagination - limit (int, default 200, max 50000) - scenario_id (int, optional) - pipeline_id (int, optional) - segment_id (int, optional) — filter to segment members

Response: Array of series objects:

[{
  "unique_id": "12_301",
  "item_id": 12,
  "site_id": 301,
  "item_name": "Wheel Assembly",
  "site_name": "Paris DC",
  "item_xuid": "WH-FA",
  "site_xuid": "FR-PAR",
  "item_image_url": null,
  "item_attributes": { "category": "Wheels", "weight_g": 850 },
  "site_attributes": { "country": "FR", "site_type": "central_wh" },
  "n_observations": 156,
  "date_range_start": "2021-01-04",
  "date_range_end": "2024-01-01",
  "mean": 45.2,
  "is_intermittent": false,
  "has_seasonality": true,
  "has_trend": false,
  "adi": 1.0,
  "zero_ratio": 0.0,
  "cov": 0.45,
  "complexity_level": "medium",
  "recommended_methods": ["AutoETS", "MSTL"],
  "best_method": "AutoETS",
  "best_score": 0.82,
  "runner_up_method": "MSTL",
  "classifications": { "Default (demand)": "A", "XYZ (order hits)": "X" },
  "avg_unit_cost": 125.50,
  "unit_price": 199.99
}]

avg_unit_cost and unit_price

These fields power the View Mode (Quantity / Cost / Price / Margin) dropdown in the Dashboard toolbar. When avg_unit_cost is 0, all monetary-mode panels display zero.


GET /api/series/type-counts

Counts of series by demand type for the Dashboard type-count cards.

Query params: - pipeline_id (int, optional) - segment_id (int, optional)

Response:

{ "intermittent": 42, "smooth": 158, "lumpy": 12, "causal": 5, "maintenance": 3 }


GET /api/abc/configurations

List all ABC classification configurations.

Response: Array of configuration objects:

[{
  "id": 1,
  "name": "Default (demand)",
  "class_labels": ["A", "B", "C", "D"],
  "is_active": true,
  "metric": "revenue",
  "thresholds": [0.2, 0.5, 0.8]
}]


GET /api/series/{unique_id}/data

Get the actual demand time series data for a specific series.

Path: unique_id — the series identifier (e.g. 12_301)

Response: Array of {ds: "2024-01-01", y: 42.0, corrected_y: 42.0} objects.


GET /api/series/{unique_id}/distributions

Get the fitted distribution parameters for a series.

Response:

{
  "distribution_type": "lognormal",
  "mean": 45.2,
  "std": 12.3,
  "params": { "s": 0.27, "scale": 44.1 },
  "ks_statistic": 0.04,
  "ks_pvalue": 0.82,
  "service_level_quantiles": { "0.9": 62.1, "0.95": 71.4, "0.99": 89.3 }
}


GET /api/series/{unique_id}/outliers

Get detected outliers for a series.


GET /api/series/{unique_id}/best-method

Get the selected best method for a series.


PUT /api/series/{unique_id}/locked-method

Lock a series to a specific forecasting method. Requires can_create_override.

Body: { "method": "AutoETS" }


DELETE /api/series/{unique_id}/locked-method

Remove a method lock.


GET /api/series/{unique_id}/parameters

Get the effective parameter configuration for this series (resolves the three-level hierarchy).


GET /api/series/{unique_id}/method-explanation

Get a natural-language explanation of why a particular method was selected for this series (uses Claude AI).


GET /api/series/{unique_id}/indirect

Get the indirect demand breakdown (bill-of-materials demand roll-up).


GET /api/series/{unique_id}/io-network

Get the inventory optimisation network graph for this series (nodes = sites, edges = routes).


PATCH /api/items/{item_id}/image

Upload or update the item image URL (admin only).

Body: { "image_url": "https://..." }


GET /api/items

List all items (master data).


GET /api/sites

List all sites (master data).


Forecasts and Metrics — /api/forecasts/*, /api/metrics/*


GET /api/forecasts/{unique_id}

Get forecast values for all methods for a series.

Query params: - scenario_id (int) - pipeline_id (int)

Response:

{
  "unique_id": "12_301",
  "forecasts": {
    "AutoETS": [{ "ds": "2024-01-08", "yhat": 48.3, "yhat_lower": 32.1, "yhat_upper": 64.5 }],
    "MSTL": [...]
  }
}


GET /api/metrics/{unique_id}

Get backtesting metrics for all methods for a series.

Response:

{
  "unique_id": "12_301",
  "metrics": {
    "AutoETS": { "mae": 8.2, "rmse": 11.4, "mase": 0.78, "bias": 0.03, "coverage_90": 0.91 },
    "MSTL":    { ... }
  }
}


GET /api/forecasts/{unique_id}/origins

List all backtest origin dates available for a series.


GET /api/forecasts/{unique_id}/origins/{origin_date}

Get the forecast made from a specific historical origin date (for forecast evolution analysis).


GET /api/series/{unique_id}/forecast-evolution

Get the full forecast evolution chart data — how forecasts have changed over time for a series.


GET /api/series/{unique_id}/forecast-convergence

Get forecast convergence data — how the forecast for a specific future date converged as new data arrived.


GET /api/series/{unique_id}/backtest-forecasts

Get all backtest forecast values across all windows and methods.


GET /api/series/{unique_id}/vega-spec

Get a Vega-Lite spec for rendering the series chart in the UI.


POST /api/sparklines

Batch sparkline data for the dashboard series table.

Body: { "unique_ids": ["12_301", "12_302", ...] }

Response: { "12_301": [[date, value], ...], ... } — minimal arrays for inline SVG rendering.


Analytics — /api/analytics/*, /api/best-methods


GET /api/analytics

Portfolio-level accuracy analytics across all series.

Query params: scenario_id, pipeline_id, segment_id


GET /api/analytics/accuracy-precision

Accuracy vs. precision scatter data for method comparison.

Query params: - pipeline_id (int, optional)

Response:

{
  "points": [
    { "unique_id": "12_301", "accuracy": 0.85, "precision": 0.72, "method": "AutoETS" }
  ],
  "summary": {
    "avg_accuracy": 0.78,
    "avg_precision": 0.65,
    "by_method": { "AutoETS": { "avg_accuracy": 0.82, "avg_precision": 0.70 } }
  }
}


GET /api/analytics/aggregate-demand

Historical + forecast + causal + maintenance aggregated demand for the Dashboard demand chart.

Query params: - pipeline_id (int, optional) - series_scenario_id (int, optional) - maintenance_scenario_id (int, optional) - causal_scenario_id (int, optional)

Response:

{
  "historical": [{ "date": "2024-01-07", "value": 1511.0 }],
  "forecast": [{ "date": "2026-04-12", "value": 3207.5 }],
  "forecast_causal": [{ "date": "2026-04-12", "value": 150.0 }],
  "forecast_maint": [{ "date": "2026-04-12", "value": 80.0 }]
}


POST /api/analytics/aggregate-demand

Same as GET but accepts unique_ids in body for large filtered sets.

Body:

{ "unique_ids": ["12_301", "5_102"], "pipeline_id": 1 }


GET /api/best-methods

Summary of best method distribution across all series.

Response:

{
  "method_counts": { "AutoETS": 222, "AutoTheta": 174, "AutoARIMA": 107, "MSTL": 38 },
  "total_series": 590
}


POST /api/methods/check-compatibility

Check which forecasting methods are compatible with a series's characteristics.


Hyperparameters — /api/hyperparams/*


GET /api/hyperparams/{unique_id}

Get per-SKU hyperparameter overrides for a series.


PUT /api/hyperparams/{unique_id}

Set per-SKU hyperparameter overrides. Requires can_create_override.

Body: { "method": "AutoARIMA", "overrides": { "season_length": 26 } }


DELETE /api/hyperparams/{unique_id}

Remove all overrides for a series.


Adjustments (Demand Corrections) — /api/adjustments/*


GET /api/adjustments/{unique_id}

Get all demand corrections for a series.


POST /api/adjustments/{unique_id}

Create or update a demand correction for a specific date.

Body:

{
  "ds": "2024-01-08",
  "corrected_y": 55.0,
  "reason": "Promotional uplift"
}


DELETE /api/adjustments/{unique_id}

Delete all corrections for a series.


POST /api/series/{unique_id}/copy-forecast

Copy a method's forecast values into the demand corrections for a future period (used to "accept" a forecast).


NPI — /api/npi/*


GET /api/npi/list

List all New Part Introduction items.

Query params: skip, limit, search


DELETE /api/npi/{unique_id}

Delete an NPI item.


Segments — /api/segments/*


GET /api/segments

List all segments.


GET /api/segments/by-uid/{unique_id}

Get all segments that contain a specific series.


POST /api/segments

Create a new segment with filter rules.

Body:

{
  "name": "High Value Items",
  "description": "Items with annual value > 10000",
  "rules": [
    { "field": "annual_value", "operator": ">", "value": 10000 }
  ]
}


GET /api/segments/fields

List available fields for building segment rules (with data types and example values).


POST /api/segments/parse-natural-language

Parse a natural-language segment description into structured rules (uses Claude AI).

Body: { "description": "All items with high seasonality and low stock" }


PUT /api/segments/{segment_id}

Update a segment's name, description, or rules.


DELETE /api/segments/{segment_id}

Delete a segment.


GET /api/segments/{segment_id}/members

List the series that belong to a segment.


GET /api/segments/{segment_id}/details

Get segment statistics (member count, coverage, etc.).


POST /api/segments/{segment_id}/preview

Preview which series would match a set of rules without saving.


POST /api/segments/{segment_id}/assign

Assign all matching series to the segment (runs the filter and writes to segment_membership).


ABC Classification — /api/abc/*


GET /api/abc/configurations

List all ABC classification configurations.


POST /api/abc/configurations

Create a new ABC configuration.

Body:

{
  "name": "Revenue ABC",
  "metric": "annual_revenue",
  "thresholds": { "A": 0.80, "B": 0.95 }
}


GET /api/abc/configurations/{config_id}

Get one configuration.


PUT /api/abc/configurations/{config_id}

Update a configuration.


DELETE /api/abc/configurations/{config_id}

Delete a configuration.


POST /api/abc/run/{config_id}

Run ABC classification for a configuration.


POST /api/abc/run-all

Run all ABC configurations.


GET /api/abc/results/{config_id}

Get classification results for a configuration.


GET /api/abc/results/by-series/{unique_id}

Get ABC class for a specific series across all configurations.


GET /api/abc/summary/{config_id}

Aggregate summary (count and value per class).


GET /api/abc/price-available

Check whether price data is available for value-based classification.


Parameters — /api/parameters/*

See Parameter System for full documentation.

Method Path Description
GET /api/parameters List all parameter sets (?parameter_type= to filter)
GET /api/parameters/{id} Get one parameter set
POST /api/parameters Create a parameter set
PUT /api/parameters/{id} Update a parameter set
DELETE /api/parameters/{id} Delete a parameter set
PUT /api/parameters/reorder Reorder within a type
POST /api/parameters/resolve Trigger assignment resolution
GET /api/parameters/{id}/segments Linked segments
PUT /api/parameters/{id}/segments Update segment links

Pipeline and Process Control — /api/pipeline/*, /api/process-log/*


GET /api/pipeline/steps

Return the available pipeline steps as [{ "id": "etl", "label": "ETL", "description": "..." }].


POST /api/pipeline/run/{step}

Launch a pipeline step as a background subprocess.

Path: step — one of etl, outlier, characterization, forecast, backtest, backtest-forecast, best-method, distribution, meio, supply

Body:

{
  "scenario_id": 1,
  "pipeline_id": 42,
  "segment_id": null,
  "force": false,
  "best_method_only": false,
  "force_full_run": false,
  "run_id": null,
  "n_workers": null
}

Requires: can_run_process permission or admin role.

Response:

{ "job_id": "uuid", "step": "forecast", "status": "queued" }


POST /api/pipeline/run-all

Launch the complete pipeline (all steps in sequence).

Body:

{
  "pipeline_id": 42,
  "scenario_id": 1
}

Requires: can_run_process permission or admin role.


POST /api/pipeline/run-forecast

Launch a forecast for specific series, optionally with all methods or a specific set.

Body:

{
  "series": ["SKU-A", "SKU-B"],
  "all_methods": false,
  "pipeline_id": 42,
  "methods": ["autoarima", "autoets"]
}

Requires: can_run_process permission or admin role.


GET /api/pipeline/jobs

List all jobs for the current account.

Response: Array of { job_id, step, status, started_at, finished_at, error }.


GET /api/pipeline/jobs/{job_id}

Get job status and tail of log output.


POST /api/pipeline/jobs/{job_id}/kill

Kill a running job.


POST /api/pipeline/jobs/kill-all

Kill ALL running/pending pipeline jobs for the current account and clear them. Used by the frontend "Kill & Restart" button.

Requires: can_run_process permission or admin role.


POST /api/pipeline/jobs/reset

Reset stale/zombie jobs to failed status.


GET /api/pipeline/jobs/{job_id}/stream

Server-Sent Events stream of job log lines. Connect with EventSource in the browser.


GET /api/process-log

Paginated process log with filtering.

Query params: limit, offset, step, status, run_id, date_from, date_to, pipeline_id, scenario_id, sort_by, sort_dir


GET /api/process-log/runs

List distinct pipeline runs.


GET /api/process-log/step-names

List all unique step names present in the log.


GET /api/process-log/{run_id}/steps

Get all steps for a specific run.


GET /api/process-log/step/{step_id}/tail

Get the log tail for a specific step execution.


Process Workflows — /api/process-workflows/*

Workflows define ordered sequences of pipeline steps that can be run as a single unit. They are the backend for the "Workflows" tab in the Process Runner UI.


GET /api/process-workflows

List all process workflows for the current tenant, ordered by sort_order.

Response:

[
  {
    "workflow_id": 1,
    "name": "E2E Full Pipeline",
    "steps": ["etl", "outlier", "characterization", "forecast", "backtest", "best-method", "distribution", "meio"],
    "segment_ids": [],
    "scenario_pipeline_id": null,
    "step_workers": {},
    "sort_order": 0,
    "last_run_at": "2026-04-22T14:30:00Z"
  }
]


POST /api/process-workflows

Create a new process workflow.

Body:

{
  "name": "E2E Full Pipeline",
  "steps": ["etl", "outlier", "characterization", "forecast", "backtest", "best-method", "distribution", "meio"],
  "segment_ids": [],
  "scenario_pipeline_id": null,
  "step_workers": {}
}

Requires: can_run_process permission or admin role.


PUT /api/process-workflows/{workflow_id}

Update an existing workflow (name, steps, segment IDs, etc.).

Body: same fields as create.

Requires: can_run_process permission or admin role.


DELETE /api/process-workflows/{workflow_id}

Delete a process workflow.

Requires: can_run_process permission or admin role.


PUT /api/process-workflows/reorder

Reorder workflows by providing an ordered list of IDs.

Body:

{ "ordered_ids": [3, 1, 2] }

Requires: can_run_process permission or admin role.


Import ETL — /api/import/*

These endpoints manage the import.* staging schema — the landing zone for ERP and external data before the ETL pipeline transforms it into scenario.* and master.*. Import tables are stateless; truncating or dropping them is safe (re-create via files/DDL/import_schema.sql).


GET /api/import/tables

List all import staging tables with current row counts and metadata.

Response:

[
  { "table_name": "import_demand_actuals", "row_count": 125000, "last_loaded": "2026-04-20T08:00:00Z" }
]


DELETE /api/import/tables/{table_name}

Truncate one import staging table. No-op if the table does not exist.

Requires: admin role.


POST /api/import/run

Trigger a full or partial import ETL run. The ETL reads from import.* tables, validates and transforms the data, and writes it into scenario.*.

Body:

{
  "tables": ["import_demand_actuals", "import_item"],
  "modes": { "import_demand_actuals": "upsert" }
}

Both fields are optional — omitting tables runs all import tables. modes overrides the default mode per table ("upsert" or "replace").

Requires: can_run_process permission or admin role.

Response:

{ "run_id": "abc123", "status": "running", "tables": ["import_demand_actuals", "import_item"] }


GET /api/import/runs

List recent import runs (newest first) with pagination.

Query params: limit (1–100, default 20), offset (default 0).


GET /api/import/runs/{run_id}

Get one import run including its per-table results (rows inserted, updated, errors).


GET /api/import/runs/{run_id}/log

Get the pipe_process_log tail for an import run.

Query params: limit (1–200, default 50).


Scenarios (Forecast) — /api/forecast/scenarios/*

Method Path Description
GET /api/forecast/scenarios List
POST /api/forecast/scenarios Create
GET /api/forecast/scenarios/{id} Get
PUT /api/forecast/scenarios/{id} Update
DELETE /api/forecast/scenarios/{id} Delete
POST /api/forecast/scenarios/{id}/clone Clone
GET /api/forecast/scenarios/{id}/results/summary Accuracy summary

Forecast Netting — /api/forecast/netting/*

Method Path Description
GET /api/forecast/netting/summary Aggregate netting summary (series_count, total_forecast, total_consumed, total_netted, total_firm_demand, total_supply_plan, consumption_rate, netting_direction)
GET /api/forecast/netting/series Series-level netting
GET /api/forecast/netting/{unique_id} Netting for one series
POST /api/forecast/netting/refresh Recompute netting

Query params for summary: pipeline_id (int), segment_id (int, optional)


Scenario Pipelines — /api/scenario-pipeline/*

See Pipeline Configuration for full documentation.

Method Path Description
GET /api/scenario-pipeline List all pipelines
POST /api/scenario-pipeline Create
GET /api/scenario-pipeline/{id} Get
PUT /api/scenario-pipeline/{id} Update
DELETE /api/scenario-pipeline/{id} Delete
POST /api/scenario-pipeline/{id}/copy-from/{src_id} Copy from source
POST /api/scenario-pipeline/{id}/segments Add segment
DELETE /api/scenario-pipeline/{id}/segments/{seg_id} Remove segment
GET /api/scenario-pipeline/{id}/baseline/{uid} Get baseline for series

Supply Planning — /api/supply/*

Pipeline and scenario required

Every supply endpoint requires both pipeline_id and scenario_id as query parameters. Use useSupplyParams() on the frontend. Exceptions: item-site and sales-returns CRUD — these are immutable master data.

Figure: pipeline_id guard decision flow — None and 0 are rejected; only master-data endpoints skip the PIPE_ filter.*

flowchart TD
    A["Endpoint receives request"] --> B{"pipeline_id provided?"}
    B -->|"None"| C["raise HTTPException 400<br/>pipeline_id is required"]
    B -->|"0"| D["reject, 0 is orphan sentinel"]
    B -->|"> 0"| E{"reads / writes PIPE_*?"}
    E -->|"yes"| F["filter WHERE pipeline_id = X"]
    E -->|"no, master data"| G["no pipeline filter needed"]
    F --> H["Return result"]
    G --> H

POST /api/supply/run

Launch a supply planning run for a scenario.

Body: { "scenario_id": 1, "pipeline_id": 42 }


GET /api/supply/runs

List supply planning runs.

Query params: pipeline_id, scenario_id


GET /api/supply/runs/{run_id}

Get details of a specific run.


DELETE /api/supply/runs/{run_id}

Delete a run.


GET /api/supply/orders/breakdown

Aggregated order breakdown by item, site, and period.

Query params: pipeline_id, scenario_id, run_id


GET /api/supply/orders

List replenishment orders.

Query params: pipeline_id, scenario_id, run_id, item_id, site_id, status


PUT /api/supply/orders/{order_id}

Update an order (e.g., change quantity, dates).


GET /api/supply/orders/{order_id}/pegging

Get demand pegging for a specific order (which demand lines drove this order).


GET /api/supply/pegging

Bulk pegging data.

Query params: pipeline_id, scenario_id, run_id


GET /api/supply/pegging/{order_id}

Pegging for a specific order.


GET /api/supply/inventory

Current inventory positions.

Query params: pipeline_id, scenario_id, run_id


GET /api/supply/exceptions

Supply exceptions (stockouts, excess, etc.).

Query params: pipeline_id, scenario_id, run_id


GET /api/supply/dashboard

Aggregated supply dashboard KPIs.

Query params: pipeline_id, scenario_id, run_id


GET /api/supply/capacity

Capacity utilisation by site and period.


GET /api/supply/scenarios

List supply scenarios.


POST /api/supply/scenarios

Create a supply scenario.


GET /api/supply/scenarios/{id}

Get one scenario.


PUT /api/supply/scenarios/{id}

Update a scenario.


PUT /api/supply/scenarios/{id}/param-overrides

Update parameter overrides for a scenario.


DELETE /api/supply/scenarios/{id}

Delete a scenario.


GET /api/supply/scenarios/{id}/segments

Get segment scopes for a scenario.


PUT /api/supply/scenarios/{id}/segments

Update segment scopes.


GET /api/supply/item-site

List item-site records (master data — no pipeline required).


PUT /api/supply/item-site/{item_id}/{site_id}

Update item-site parameters (cost, lead time, etc.). Master data.


GET /api/supply/sales-returns

List sales return records. Master data.


POST /api/supply/sales-returns

Create a sales return record.


PUT /api/supply/sales-returns/{return_id}

Update a return record.


DELETE /api/supply/sales-returns/{return_id}

Delete a return record.


POST /api/supply/sync-alerts

Sync supply alerts from the alert engine.


POST /api/supply/firm-orders/import

Bulk upload firm supply orders (JSON list). Each row obeys the same validation as POST /api/supply/firm-orders. Uses ON CONFLICT (external_id, source_system) upsert.

Body:

[
  { "external_id": "PO-001", "source_system": "ERP", "item_id": 1, "site_id": 1, "order_type": "BUY", "quantity": 100, "due_date": "2026-06-01" }
]

Response:

{ "inserted": 5, "updated": 3, "errors": [] }


GET /api/supply/consumption-preview

Preview consumption forecast for the supply planning horizon.


Routes — /api/routes, /api/route-types


GET /api/route-types

List route types (REPLENISHMENT, RETURN, REPAIR, etc.).


GET /api/routes

List all supply network routes.


POST /api/routes

Create a route.

Body: { "item_id": 12, "from_site_id": 100, "to_site_id": 305, "route_type": "REPLENISHMENT", "lead_time": 7, "priority": 1 }


PUT /api/routes/{route_id}

Update a route.


DELETE /api/routes/{route_id}

Delete a route.


MEIO — /api/meio/*

See MEIO Configuration for full documentation.

Method Path Description
GET /api/meio/scenarios List scenarios
POST /api/meio/scenarios Create
PUT /api/meio/scenarios/{id} Update
DELETE /api/meio/scenarios/{id} Delete
GET /api/meio/scenarios/{id}/segment-params Segment overrides
PUT /api/meio/scenarios/{id}/segment-params Upsert overrides
DELETE /api/meio/scenarios/{id}/segment-params/{seg} Remove override
DELETE /api/meio/scenarios/{id}/segment-params Clear all
GET /api/meio/scenarios/{id}/parameter-sets Parameter sets
POST /api/meio/scenarios/{id}/parameter-sets Add parameter set
PUT /api/meio/parameter-sets/{pset_id} Update
DELETE /api/meio/parameter-sets/{pset_id} Delete
PUT /api/meio/parameter-sets/{pset_id}/segments Link segments
PUT /api/meio/scenarios/{id}/parameter-sets/reorder Reorder
POST /api/meio/scenarios/{id}/run Run optimiser
GET /api/meio/scenarios/{id}/results SKU + group results
GET /api/series/{unique_id}/mc-distribution MEIO V3 MC distribution (demand × supply-LT → compound → SS)
GET /api/meio/fr-heatmap Fill-rate heatmap
GET /api/meio/diagnostic Diagnostic data

K-Curve (EOQ) — /api/kcurve/*

The K-Curve module implements EOQ (Economic Order Quantity) and Wagner-Whitin dynamic lot sizing.

Method Path Description
GET /api/kcurve/items List items eligible for EOQ
GET /api/kcurve/curve The cost curve (holding vs. ordering cost tradeoff). Query params: constrained: bool (default false) — when true, also returns constrained_curve
POST /api/kcurve/solve Solve EOQ for a set of items. Body: constrained: bool (default false) — when true, returns constrained_curve, alternatives, per-item freq_options. Optional scenario_id + persist (persist defaults on when scenario_id is present): when set, results are atomically jsonb-merged into scen_inventory_scenario.results for the active pipeline — the surface downstream MEIO/supply read for EOQ — making an explicit curve click authoritative without a separate EOQ process-step run. master.item.eoq write-back is intentionally not done here (stays exclusive to the EOQ process step, kcurve.runner.run_eoq_for_pipeline) so a dev/what-if click cannot clobber production master data. scenario_id is validated against config_scenario_pipeline (ownership/IDOR guard).
GET /api/kcurve/eoq EOQ results for all items
GET /api/kcurve/eoq/item EOQ for one item. Query params: respect_frequency_constraints: bool (default true) — when true, snaps EOQ to allowed frequency and returns freq_options
GET /api/kcurve/wagner-whitin Wagner-Whitin dynamic lot-sizing. Returns per-item eoq (annualised average order qty), eoq_override, orders_per_year, n_orders, avg_lot_size, schedule[], plus portfolio totals. Applies the same interact_io_overrides (scenario_id=0) as Classic EOQ/K-Curve. When a pipeline's linked inventory scenario is tag_code='wagner_whitin', the eoq pipeline step runs run_wagner_whitin_for_pipeline, persists results.items[] (consumed by MEIO/supply), and writes back item.eoq.
GET /api/kcurve/pipeline/{pipeline_id} K-curve results for a pipeline
GET /api/kcurve/scenarios List K-curve scenarios
POST /api/kcurve/scenarios Create a scenario
DELETE /api/kcurve/scenarios/{id} Delete a scenario

Order-frequency constraint response fields

When constrained=true or respect_frequency_constraints=true:

Field Location Description
constrained_curve kcurve/curve, kcurve/solve Stepped feasible frontier [{k, inv_val, orders}]
alternatives kcurve/solve Neighbouring feasible k points [{k, inv_val, orders_per_year}]
freq_options per-item in kcurve/solve, kcurve/eoq/item Allowed frequencies [{freq, freq_label, orders_per_year, eoq, cycle_stock_value, total_annual_cost, k_equivalent, is_selected}]
freq_constraint per-item {param_name, allowed_orders_per_year, selected_orders_per_year, constrained}
freq_constrained kcurve/eoq/item top-level true when EOQ was overridden by frequency snapping
freq_param_name kcurve/eoq/item top-level Name of the order_frequency parameter that applied

Causal Forecasting — /api/causal/*

Causal forecasting models demand driven by asset deployments and failure rates (MRO / aerospace use case).

Method Path Description
GET /api/causal/asset-types List asset types
POST /api/causal/asset-types Create asset type
GET /api/causal/assets List assets
POST /api/causal/assets Create asset
DELETE /api/causal/assets/{id} Delete
PUT /api/causal/assets/{id} Update
GET /api/causal/asset-deployments List deployments
POST /api/causal/asset-deployments Create deployment
DELETE /api/causal/asset-deployments/{id} Delete
PUT /api/causal/asset-deployments/{id} Update
GET /api/causal/coverage Asset coverage
POST /api/causal/coverage Create coverage
DELETE /api/causal/coverage/{id} Delete
GET /api/causal/bom Bill of materials
POST /api/causal/bom Create BOM row
DELETE /api/causal/bom/{id} Delete
PUT /api/causal/bom/{id} Update
GET /api/causal/bom/explosion BOM explosion
POST /api/causal/bom/import Import BOM from CSV
GET /api/causal/fleet-plan Fleet plan
POST /api/causal/fleet-plan Create fleet plan
PUT /api/causal/fleet-plan/{id} Update
DELETE /api/causal/fleet-plan/{id} Delete
POST /api/causal/fleet-plan/import Import fleet plan
GET /api/causal/customers List customers
GET /api/causal/usage-types List usage types
GET /api/causal/asset-usage List asset usage
POST /api/causal/asset-usage Create usage
PUT /api/causal/asset-usage/{id} Update
DELETE /api/causal/asset-usage/{id} Delete
GET /api/causal/usage-timeseries Usage as time series
GET /api/causal/failure-rate List failure rates
POST /api/causal/failure-rate Create
DELETE /api/causal/failure-rate/{id} Delete
PUT /api/causal/failure-rate/{id} Update
POST /api/causal/failure-rate/fit Fit failure rates from actuals
GET /api/causal/scenarios Causal scenarios
POST /api/causal/scenarios Create
PUT /api/causal/scenarios/{id} Update
DELETE /api/causal/scenarios/{id} Delete
POST /api/causal/scenarios/run Run causal forecast
GET /api/causal/results Results
GET /api/causal/results/summary Summary
GET /api/causal/results/breakdown Breakdown by asset/item
GET /api/causal/results/compare Compare scenarios
GET /api/causal/results/compare/deployments Deployment comparison
GET /api/causal/results/compare/usage Usage comparison
POST /api/causal/uncertainty Uncertainty quantification
GET /api/causal/templates/{name} Download import template

Maintenance — /api/maintenance/*

Scheduled maintenance event management and maintenance-driven demand.

Method Path Description
GET /api/maintenance/events-weekly Weekly maintenance event view
GET /api/maintenance/items-for-week Items needed for a specific week
GET /api/maintenance/scenarios Maintenance scenarios
GET /api/maintenance/demand/summary Maintenance demand summary
GET /api/maintenance/demand/series Demand series from maintenance
GET /api/maintenance/events-summary Event aggregation
GET /api/maintenance/events List all events
GET /api/maintenance/scenario/{id}/item-breakdown Item breakdown per scenario
GET /api/maintenance/scenario/{id}/schedule Schedule
PUT /api/maintenance/scenario/{id}/schedule Update schedule
GET /api/maintenance/events/{event_id}/occurrences Event occurrences
PUT /api/maintenance/events/{event_id}/bom Update event BOM
PUT /api/maintenance/events/{event_id}/scenario/{id}/dates Update event dates

Alerts — /api/alerts/*

The alert engine evaluates 32+ business rules against each series and flags anomalies.


POST /api/alerts/query

Query alerts using natural language or structured filters.

Body:

{
  "query": "items with low service level in the Paris region",
  "segment_id": null,
  "pipeline_id": 42
}


GET /api/alerts

List alerts with optional filters.

Query params: pipeline_id, segment_id, status, metric, page, page_size


POST /api/alerts/batch

Batch process (approve/reject) multiple alerts.


POST /api/alerts/{alert_id}/approve

Approve an alert (marks it handled with no action needed).


POST /api/alerts/{alert_id}/reject

Reject an alert (requires corrective action).


GET /api/alerts/ontology

List the alert rule ontology (all 32+ rules with thresholds).


PUT /api/alerts/ontology/{metric_name}

Update an alert rule threshold.


DELETE /api/alerts/ontology/{metric_name}

Remove an alert rule.


POST /api/alerts/ontology/seed

Seed the ontology with the default 32 rules (idempotent).


POST /api/alerts/synonyms

Add a synonym mapping for natural-language alert queries.

Body: { "term": "excess inventory", "canonical_metric": "high_stock" }


Exceptions — /api/exceptions/*, /api/exception-log/*


GET /api/exceptions

Get computed planner exceptions for a series (live compute from business rules).

Query params: pipeline_id, scenario_id, unique_id


GET /api/exceptions/{unique_id}

Get exceptions for a specific series.


POST /api/exceptions/{unique_id}/{exc_type}/snooze

Snooze an exception type for a series until a specified date.

Body: { "until_date": "2026-06-01" }


DELETE /api/exceptions/{unique_id}/{exc_type}/snooze

Remove a snooze.


GET /api/exception-log

Paginated exception log (persisted exceptions).

Query params: pipeline_id (required), status (open, resolved, snoozed), types (comma-separated: forecast,io,supply), segment_id, page, page_size

Response:

{
  "total": 1234,
  "page": 1,
  "page_size": 50,
  "items": [{
    "id": 1,
    "pipeline_id": 42,
    "item_id": 12,
    "site_id": 301,
    "exception_types": ["forecast"],
    "issues": [{ "type": "forecast", "severity": "high", "description": "..." }],
    "status": "open",
    "item_name": "Wheel Assembly",
    "item_code": "WA-001",
    "site_name": "Paris DC",
    "unique_id": "12_301"
  }]
}


GET /api/exception-log/preload

Bulk prefetch inline data for exception overlays (actuals, forecast, MEIO, supply negatives for multiple items in one request).

Query params: pipeline_id (required), item_ids (comma-separated integers)


PATCH /api/exception-log/{exception_id}/status

Update exception status.

Body: { "status": "resolved" }


POST /api/exception-log/generate

Generate (or refresh) exceptions for a pipeline.

Body: { "pipeline_id": 42 }


GET /api/exception-log/{exception_id}/forecast-data

Get forecast data inlined for an exception (used in the detail overlay).


GET /api/exception-log/{exception_id}/io-data

Get inventory optimisation data for an exception.


GET /api/exception-log/{exception_id}/supply-data

Get supply plan data for an exception.


IO Overrides — /api/io-overrides


POST /api/io-overrides

Upsert inventory optimisation parameter overrides for a specific item/site/pipeline/scenario combination.

Body:

{
  "pipeline_id": 42,
  "scenario_id": 1,
  "item_id": 12,
  "site_id": 301,
  "sl_override": 0.99,
  "eoq_override": 50.0,
  "fr_override": 0.97
}


Orders — /api/orders/*


PATCH /api/orders/{order_id}/approve

Approve a supply order with a specific quantity and availability date.

Body:

{
  "approved_quantity": 100.0,
  "approved_availability_date": "2026-05-01"
}


Calendars — /api/calendars/*

Working day and holiday calendars used by supply planning.

Method Path Description
GET /api/calendars List calendars
GET /api/calendars/{id} Get calendar
POST /api/calendars Create
PUT /api/calendars/{id} Update
PUT /api/calendars/{id}/entries Update calendar entries (holidays, etc.)
DELETE /api/calendars/{id} Delete

Audit Log — /api/audit-log


GET /api/audit-log

Paginated, filterable audit trail of all significant state changes (configuration, security, forecasting, pipeline, model events). All writes go through the centralized helper in files/utils/audit.py.

Query params:

param type description
entity_type string Filter by entity (e.g. user, pipeline, auth, override)
entity_id int Filter by entity PK
action string Filter by verb (e.g. create, delete, failed_login, promote_prod)
changed_by string Partial match on actor email/name
outcome string success / failure / denied (auth + run lifecycle)
date_from ISO date created_at >= lower bound
date_to ISO date created_at < upper bound (exclusive of next day)
sort_by string id / entity_type / entity_id / action / changed_by / created_at
sort_dir string asc / desc
limit int Page size (default 50)
offset int Pagination offset

Response: { items: [...], total, limit, offset } where each item carries id, entity_type, entity_id, action, old_value, new_value, changed_by, ip, user_agent, request_id, outcome, created_at.

See the sys_audit_log event catalog in database.md for the full (entity_type, action) matrix.


Indirect Demand — /api/indirect/*


GET /api/series/{unique_id}/indirect

Get indirect demand (BOM-driven demand roll-up) for a series.


POST /api/indirect/compute

Recompute indirect demand for the full BOM network.


Overview — /api/overview/*


GET /api/overview/map

Supply network map data (nodes = sites, edges = routes, with KPI values attached).


GET /api/overview/kpis

Portfolio-level KPI summary (total inventory value, weighted fill rate, coverage, etc.).


Features Catalogue — /api/features/catalogue


GET /api/features/catalogue

Return the feature flag catalogue — which optional features are enabled for this tenant.


Admin — /api/admin/*

SuperAdmin only. Defined in files/api/admin.py.

Method Path Description
GET /api/admin/accounts List all tenant accounts
POST /api/admin/accounts Provision a new tenant
GET /api/admin/accounts/{job_id}/provision-status Check provisioning job status
DELETE /api/admin/accounts/{account_id} Delete a tenant
POST /api/admin/accounts/{account_id}/clone Clone a tenant's schema
GET /api/admin/superadmins List superadmins
POST /api/admin/superadmins Create superadmin
GET /api/admin/users List users across all tenants
GET /api/admin/users/assignments User-account assignments
PUT /api/admin/users/{email}/accounts Update a user's account assignments
POST /api/admin/cache/refresh Refresh the account cache
POST /api/admin/run-migrations Run pending schema migrations
POST /api/admin/populate-received-dates Backfill received dates on orders

Allocation Overview — /api/allocation/overview/*

All allocation overview endpoints include avg_unit_cost in their response (a global average from master.item + master.item_location). This is used by the frontend View Mode (Quantity / Cost / Price / Margin) to convert qty-based values to monetary values.

Method Path Description
GET /api/allocation/overview/kpis Aggregate KPIs (fill rate, backlog, violations)
GET /api/allocation/overview/graph Supply–demand DAG (top-N nodes + edges)
GET /api/allocation/overview/backlog-heatmap Top-40 item/site backlog cells
GET /api/allocation/overview/waterfall Starting stock → allocations → shortages → remaining
GET /api/allocation/overview/fair-share Actual vs. target share per fairness group
GET /api/allocation/overview/routing-violations List of soft routing violations
GET /api/allocation/overview/aging Unfulfilled demand counts by age bucket
GET /api/allocation/overview/reservations Top-10 supplies by reservation penalty

Common query params: pipeline_id (int, default 1), scenario_id (int, default 1), segment_id (int, optional).

avg_unit_cost computation:

SELECT AVG(COALESCE(isit.unit_cost, isit.cost_price, i.unit_cost, i.cost, 0))
FROM master.item i
LEFT JOIN master.item_location isit ON isit.item_id = i.id
WHERE COALESCE(isit.unit_cost, isit.cost_price, i.unit_cost, i.cost, 0) > 0

Zero avg_unit_cost

When avg_unit_cost is 0 (no cost data in master.item / master.item_location), all allocation panels in Cost / Price / Margin mode will render as zero. This is the most common cause of "empty allocation charts in non-quantity mode."