HTTP API¶
The algomancy-api package exposes the same scenario- and data-management
surface used by the Dash GUI as an HTTP service. A remote process —
browser SPA, native desktop app, another Python client — can drive an
Algomancy backend over JSON-over-HTTP instead of importing it in-process.
The shape mirrors the GUI:
An
ApiConfigurationcarries the same domain wiring (ETL factory, algorithm/KPI templates, schemas, data paths) asAppConfig.core.An
ApiLauncherturns that configuration into a FastAPI app and serves it with uvicorn.
Tip
Once a server is running, point your browser at /docs for the interactive
Swagger UI generated from the live OpenAPI schema. Every endpoint listed below
is discoverable there.
Launching¶
from algomancy_api import ApiConfiguration, ApiLauncher
from algomancy_data import DataSource
from myapp.etl import MyETLFactory
from myapp.templates import kpis, algorithms
from myapp.schemas import all_schemas
cfg = ApiConfiguration(
etl_factory=MyETLFactory,
kpis=kpis,
algorithms=algorithms,
schemas=all_schemas,
data_object_type=DataSource,
has_persistent_state=True,
data_path="data",
autocreate=False,
autorun=False,
host="127.0.0.1",
port=8051,
)
app = ApiLauncher.build(cfg)
ApiLauncher.run(app) # blocks; host/port come from cfg
ApiLauncher.build accepts an ApiConfiguration, a bare CoreConfig, or a
plain dict with the same keys. It returns a standard FastAPI instance — for
production deployments you can hand that to your own uvicorn / gunicorn
process manager instead of using ApiLauncher.run.
To try the API quickly against the bundled example wiring (data lives in
./example/data):
from algomancy_api import ApiLauncher
from algomancy_api.example import build_example_config
ApiLauncher.run(ApiLauncher.build(build_example_config()))
Configuration¶
ApiConfiguration extends CoreConfig (see the
Scenario reference) with HTTP-specific fields. Inherited
fields like etl_factory, kpis, algorithms, schemas,
data_object_type, data_path, has_persistent_state, autocreate,
autorun, and title behave exactly as they do for the GUI.
Field |
Type |
Default |
Notes |
|---|---|---|---|
|
|
|
Bind address |
|
|
|
Bind port |
|
|
|
URL prefix for all routes (must start with |
|
|
|
Allowed CORS origins; empty disables CORS middleware |
Routes are always scoped by session under /sessions/{session_id}/.... The
SessionManager auto-creates a default "main" session when none exists yet,
so single-tenant deployments still have a working URL shape.
Sessions¶
The API always exposes routes under /sessions/{session_id}/.... A
session is a self-contained ScenarioManager with its own data and
scenarios — useful for serving multiple users or experiment workspaces from
one process.
Identity. Every session has a stable UUID id and a mutable
display_name. The URL path uses the UUID; the display_name is what
you show in UIs. For convenience, the URL path also accepts a session’s
current display_name as a soft-compat alias — useful for single-tenant
deployments and clients migrating from pre-M14 algomancy. Authoritative
clients should always use the UUID returned by GET /sessions.
Verb |
Path |
Description |
|---|---|---|
|
|
List |
|
|
Create — body |
|
|
Copy — body |
|
|
Rename — body |
|
|
Delete a session and all its scenarios, runs, KPIs, and data |
Status codes:
201on a successful create/copy.200on a successful rename or delete; the rename response is the updated{id, display_name}, the delete response is the refreshed session list.404when the targeted session doesn’t exist.409when the requesteddisplay_nameis already taken by another session.422when the request body fails Pydantic validation (e.g. emptydisplay_name).
Deleting the last remaining session never leaves the manager empty: a
fresh "main" session is auto-created in its place so subsequent
scenario writes still have somewhere to land.
Algorithm + KPI discovery¶
These endpoints let a remote frontend render a scenario-creation form for an algorithm it has never seen before.
Verb |
Path |
Description |
|---|---|---|
|
|
List algorithm template names |
|
|
Per-parameter descriptors |
|
|
List KPI template names |
The /parameters response is a list of descriptors, one per parameter:
{
"name": "Slow",
"parameters": [
{
"name": "duration",
"type": "integer",
"required": true,
"value": 1,
"default": 1,
"min": 1,
"max": 60
}
]
}
Optional fields (choices, min/max, default) appear only when they
apply to the parameter’s type — enums get choices, numerics get min/max,
and so on.
Scenarios¶
CRUD plus the run-and-poll lifecycle. Run is fire-and-forget on the API; clients
should poll /status for progress.
Verb |
Path |
Description |
|---|---|---|
|
|
List scenarios (full |
|
|
Create scenario — body |
|
|
Full scenario including KPIs + result |
|
|
Remove scenario |
|
|
Enqueue for processing (returns |
|
|
Lightweight |
|
|
The scenario currently processing, or |
Tip
The /status endpoint is intentionally small: just id, tag, status, and
progress. It is safe to poll at a high frequency without serializing the full
scenario (with KPIs, algorithm config, and result payload) every time.
POST /sessions/{sid}/scenarios — create a scenario¶
Binds an input dataset + algorithm + parameter values into a runnable scenario. The body is JSON.
Request body
Field |
Type |
Required |
Notes |
|---|---|---|---|
|
string |
yes |
Human-readable scenario name. Must be unique within the session — duplicates return |
|
string |
yes |
Must exist in this session’s data manager. Discover via |
|
string |
yes |
Must exist in |
|
object |
no |
Parameter values keyed by parameter name. Omit or set to |
The algo_params keys come from the algorithm’s parameter set — the
/parameters endpoint described above returns the schema. Values are
validated against that schema by BaseParameterSet; bad values raise
ParameterError and the route returns 400 with the detail.
Example
import httpx
scenario = httpx.post(
"http://127.0.0.1:8051/api/v1/sessions/main/scenarios",
json={
"tag": "slow-5s",
"dataset_key": "Master data",
"algo_name": "Slow",
"algo_params": {"duration": 5},
},
).json()
Response (201)
Returns the full Scenario.to_dict() payload (see
Scenario response shape below). At this point
status is "CREATED", result is null, and each KPI’s value is None.
Error cases
Status |
When |
|---|---|
|
|
|
|
|
A parameter value failed validation ( |
|
A required field is missing or has the wrong JSON type (Pydantic-level). |
POST /sessions/{sid}/scenarios/{id}/run — enqueue for processing¶
Fire-and-forget. Returns 202 Accepted immediately and returns the scenario
in its QUEUED state — actual computation happens on a worker thread. Clients
must poll GET /status to observe progress and completion.
The status state machine, in order:
CREATED → QUEUED → PROCESSING → COMPLETE
↘ FAILED
COMPLETE and FAILED are terminal. A subsequent /run on a terminal
scenario re-enqueues it from the current state — the manager allows re-runs.
At any moment, at most one scenario per session is in PROCESSING; check
GET /processing to see which one.
Polling
GET /status returns just {id, tag, status, progress} and is safe to call
at high frequency. progress is a float in [0.0, 1.0]. Once status is
COMPLETE or FAILED, fetch the full result with GET /scenarios/{id}.
Error cases
Status |
When |
|---|---|
|
|
|
The algorithm raised an exception during processing. Note: the HTTP response for |
Scenario response shape¶
GET /scenarios and GET /scenarios/{id} (and the create/run endpoints)
return the dict produced by Scenario.to_dict():
{
"id": "01HZX...",
"tag": "slow-5s",
"input_data_id": "Master data",
"algorithm": {
"name": "Slow",
"parameters": [
{"name": "duration", "type": "integer", "value": 5, "default": 1, "min": 1, "max": 60, "required": true}
]
},
"kpis": {
"throughput": {"name": "throughput", "value": 42.0, "unit": "items/s"}
},
"status": "complete",
"result": { "...domain-specific Result.to_dict() payload..." }
}
resultand each KPI’svaluearenulluntil the scenario reachesCOMPLETE.kpisis keyed by the KPI template name registered inkpis.algorithm.parametersis the same descriptor shape returned byGET /algorithms/{name}/parameters, plus thevaluechosen for this run.resultis whatever your domain’sBaseResult.to_dict()returns. The framework does not impose a shape — see the Scenario reference.
There is no separate /kpi-measurements endpoint; KPI values are part of
this payload.
Data management¶
Verb |
Path |
Description |
|---|---|---|
|
|
List dataset keys |
|
|
Parsed JSON for a dataset |
|
|
Remove a dataset |
|
|
Derive a new dataset — body |
|
|
Add a dataset from a |
|
|
Run ETL over an uploaded multipart bundle |
Deleting a dataset that is referenced by a scenario returns 409. To delete
the underlying data, delete its referencing scenarios first.
POST /sessions/{sid}/etl — run ETL over uploaded files¶
Stages a bundle of uploaded files into a temp directory, wraps each one in the
appropriate algomancy_data.File subclass, and calls
ScenarioManager.etl_data(file_map, dataset_name). The result is a new (or
overwritten) dataset accessible under the chosen dataset_name.
This is the only endpoint that uses a multipart/form-data body — every
other write accepts JSON. There is no Pydantic model for the request because
FastAPI’s UploadFile handling is what converts the multipart parts into
file-like objects on the server. The OpenAPI schema at /openapi.json does
describe the dataset_name form field and the files array.
Form fields
Field |
Repeated? |
Type |
Notes |
|---|---|---|---|
|
once |
string |
Logical key the resulting dataset is stored under. Required, non-empty. |
|
one or more |
file upload |
Each upload’s filename stem becomes the logical name the ETL factory expects. Extension picks the |
The filename stem mapping means the ETL factory does NOT receive uploaded
filenames as opaque blobs — they must match the logical names declared by your
schemas. For example, if your schemas declare a sku_data group, the
corresponding upload must be sku_data.csv (or .json, or .xlsx).
Curl example
curl -X POST http://127.0.0.1:8051/api/v1/sessions/main/etl \
-F dataset_name=uploaded \
-F files=@./sku_data.csv \
-F files=@./inventory.xlsx
Note: each -F files=@... repeats the same form field name files; that is
how multipart “array” fields are encoded.
Python example (httpx)
import httpx
with open("sku_data.csv", "rb") as csv, open("inventory.xlsx", "rb") as xlsx:
r = httpx.post(
"http://127.0.0.1:8051/api/v1/sessions/main/etl",
data={"dataset_name": "uploaded"},
files=[
("files", ("sku_data.csv", csv, "text/csv")),
(
"files",
(
"inventory.xlsx",
xlsx,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
),
),
],
)
r.raise_for_status()
Response
{
"dataset_name": "uploaded",
"success": true,
"keys": ["uploaded", "Master data"]
}
success reflects ETLResult.is_success — a successful HTTP request can
still report success=false when extraction or validation produced ERRORs but
the request itself completed cleanly. Inspect the dataset and the manager’s
log to diagnose.
Error cases
Status |
When |
|---|---|
|
No files uploaded, an upload is missing a filename, or the filename extension is not in |
|
|
|
|
|
The ETL pipeline itself raised an unhandled exception (logged with traceback). |
POST /sessions/{sid}/data/from-json — ingest a serialized DataSource¶
Accepts the raw JSON produced by DataSource.to_json() as the request body
(not wrapped in any envelope). The framework parses the body with
DataSource.from_json, so the bytes are forwarded verbatim and the route does
no re-encoding. Both an object-rooted and array-rooted payload are accepted —
whichever your DataSource subclass produces.
Returns 201 with the full key list. Common errors: 400 on an empty body or
malformed JSON, 404 on unknown session.
Polling pattern¶
A complete run-to-completion flow from a client:
import time
import httpx
base = "http://127.0.0.1:8051/api/v1"
session = httpx.get(f"{base}/sessions").json()["default"]
scenario = httpx.post(
f"{base}/sessions/{session}/scenarios",
json={
"tag": "my-run",
"dataset_key": "Master data",
"algo_name": "Slow",
"algo_params": {"duration": 5},
},
).json()
httpx.post(f"{base}/sessions/{session}/scenarios/{scenario['id']}/run")
while True:
status = httpx.get(
f"{base}/sessions/{session}/scenarios/{scenario['id']}/status"
).json()
if status["status"] in ("complete", "failed"):
break
time.sleep(0.5)
result = httpx.get(
f"{base}/sessions/{session}/scenarios/{scenario['id']}"
).json()
print(result["kpis"])
Error mapping¶
The API translates framework exceptions to HTTP status codes consistently:
Exception |
HTTP |
Source |
|---|---|---|
|
|
Generic bad input — also used for duplicate-tag scenarios (handled explicitly in the route) |
|
|
Out-of-range / wrong-type parameter values |
|
|
Framework precondition failure (e.g. deleting a dataset that is used by a scenario) |
Manual route raises |
|
Explicit |
Anything else |
|
Unexpected; logged with a traceback |
Note
The error response shape is consistent: {"detail": "<message>"}. Use the
HTTP status code to branch, not the message text — messages are written for
humans and may change between versions.
Cross-references¶
Scenario reference —
CoreConfig,ScenarioManager,SessionManager.Data reference —
DataSource,ETLFactory,Schema.