Server API
barca serve starts a long-running HTTP server (the barca-server crate, built on
axum) that exposes the orchestrator as a JSON API.
It is the foundation for programmatic triggering, scheduling, and a future web UI.
The server reuses barca-core directly — no subprocess, no separate daemon. Core commands
are async and run on the server’s runtime; runs execute in background tasks, are tracked in
memory, and can be cancelled mid-flight via DELETE /run/{run_id}.
Starting the server
Section titled “Starting the server”barca serve pipeline.py # serve a DAG, default port 8274barca serve pipeline.py --port 8400 # custom portbarca serve pipeline.py --watch # dev mode: re-parse DAG on file changebarca serve pipeline.py --no-schedule # disable the cron schedulerbarca serve pipeline.py --timezone utc # evaluate cron in UTC (default: local)barca serve a.py b.py # multiple source filesThe server binds to 127.0.0.1 (local only). There is no authentication in v1 — do not
expose it to untrusted networks.
--watch is a local development convenience: it re-parses the DAG when a source file
changes so /assets and /plan reflect edits without a restart. It is off by default and
has no effect on the production serving path.
Endpoints (v1)
Section titled “Endpoints (v1)”All responses are JSON.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness + version. |
GET |
/assets |
List every node with kind, freshness, and upstream inputs. |
GET |
/assets/{name} |
One asset’s summary joined with timing/cache stats. |
GET |
/plan |
Execution plan (phases and streams) as JSON. |
POST |
/run |
Trigger a full run. Returns a run_id immediately. |
POST |
/run/{target} |
Trigger a task run. Returns a run_id. |
POST |
/get/{target} |
Trigger a run scoped to one target asset. Returns a run_id. |
DELETE |
/run/{run_id} |
Cancel an in-flight run (workers terminated, status → cancelled). |
GET |
/status/{run_id} |
Poll the status and result of a run. |
GET |
/schedule |
List scheduled jobs with next fire time and last run status. |
Async runs
Section titled “Async runs”Runs are asynchronous. POST /run and POST /get/{target} return immediately with a
server-side polling handle:
{ "run_id": "1b942bb33182" }Poll GET /status/{run_id} until status reaches a terminal state (complete, failed, or
cancelled):
{ "handle": "1b942bb33182", "status": "complete", "result": { "run_id": "1b9422cf12f3", "elapsed_seconds": 0.115, "steps_executed": 2, "phases": 1, "final_output": { "path": ".barca/artifacts/…", "format": "json", "size_bytes": 8 } }, "error": null, "started_at": 1780721263.05, "finished_at": 1780721263.17}status is one of pending, running, complete, failed, cancelled. The handle is
the server’s polling id; result.run_id is the persisted database run id (the run is also
written to .barca/metadata.db, same as a CLI run).
In-flight run state is held in memory and is not persisted across a server restart. The run
history in the database persists regardless. A background sweep evicts finished runs
(complete/failed/cancelled) from memory once they are more than an hour old (checked every 5
minutes), so GET /status/{run_id} for an old run eventually returns 404 even though its row
remains in barca history.
Cancelling a run
Section titled “Cancelling a run”DELETE /run/{run_id}Cancels a pending or running run: its Python workers are terminated, partial results from
already-completed steps are persisted, and the run’s status transitions to cancelled
(both in /status/{run_id} and in the runs history table). The response is
{ "run_id": "...", "status": "cancelling" }; poll /status/{run_id} to observe the
transition. Cancelling a run that already finished returns 409. Runs that exceed the
server’s 10-minute timeout are stopped the same way and reported as failed.
Health
Section titled “Health”GET /health{ "status": "ok", "version": "0.6.1" }Assets
Section titled “Assets”GET /assets → [AssetSummary, ...]GET /assets/{name} → { "asset": AssetSummary | null, "stats": AssetStats }AssetSummary is { id, kind, freshness, inputs }. {name} matches by asset name or full
node id; an unknown name returns 404. AssetStats carries run counts, timing percentiles,
and cache hit rate.
GET /plan → { total_steps, phases: [{ reason, streams: [{ stream_id, steps }] }] }Scheduling
Section titled “Scheduling”barca serve runs a cron scheduler — the piece that gives
@asset(freshness=Schedule("...")) teeth. It is on by default; pass --no-schedule
to turn it off.
At startup the server enumerates every node whose freshness is Schedule(cron), parses
each 5-field cron expression, and logs the schedule (invalid or empty cron strings are
logged and skipped, not fatal). A background task then wakes at each minute boundary and,
for every job whose cron matches the current minute, triggers a run through the same path
as POST /run / POST /run/{target}:
- Assets and sensors are materialized via the
getpath. - Tasks are executed via the
runpath.
Each scheduled run gets a normal run_id, is visible via GET /status/{run_id}, and is
persisted to .barca/metadata.db (barca history) — identical to a manually triggered run.
Inspect the live schedule with GET /schedule or, statically, with barca list <files>
(scheduled definitions show their next fire time).
Behavior:
- Timezone. Cron is evaluated in the machine’s local time by default. Pass
--timezone utcor--timezone America/New_York(any IANA name) to change it. - Catch-up. The scheduler persists the last fire time of each job. On startup, if a scheduled tick elapsed while the daemon was down, the job fires once to catch up (jobs never seen before are anchored to “now” — no first-launch stampede). Individual ticks missed during a long outage are not replayed one-for-one.
- Concurrency. Independent runs execute in parallel (bounded by a run pool sized to the
machine’s CPUs); their writes to the shared
metadata.dbare serialized by a process-wide DB lock. A scheduled job never overlaps itself: if its previous run is still pending/running when the next tick arrives, that tick is skipped. - Reload. Under
--watch, editing a source file re-reads the schedule live (within a minute). Without--watchthe job set is fixed for the process lifetime.
GET /schedule
Section titled “GET /schedule”GET /schedule → [ScheduleEntry, ...]Each ScheduleEntry is:
{ "id": "pipeline.py:daily_report", "cron": "0 5 * * *", "kind": "asset", "next_fire": 1780740000, "last_fired": 1780653600, "last_run": "1b942bb33182", "last_status": "complete"}next_fire/last_fired are unix epoch seconds (last_fired is null until the first
fire); last_run is the most recent scheduled run_id and last_status its state
(pending/running/complete/failed/cancelled, or null if none yet).
Python client
Section titled “Python client”The barca.Client SDK (standard-library only) wraps this API:
from barca import Client
c = Client("http://127.0.0.1:8274")run = c.get("daily_report") # POST /get/{target}, returns immediatelyresult = run.wait(timeout=30) # poll /status until complete/failedprint(result["status"])
for job in c.schedules(): # GET /schedule print(job["id"], job["cron"], job["next_fire"])Client methods map to the endpoints above: health(), assets(), asset(name),
plan(), schedules(), status(run_id), cancel(run_id) (also available as
Run.cancel()), plus the two trigger verbs that mirror the CLI —
get(target=None) (barca get [TARGET]; omit the target for a full-DAG run) and
run(target) (barca run TARGET). The trigger methods return a Run whose .wait() blocks
until the run reaches a terminal state. This complements barca.api (barca.get/run/…),
which shells out to the binary for one-shot commands rather than talking to a server.
Errors
Section titled “Errors”Errors return a JSON body { "error": "..." } with an appropriate status code: 404 for an
unknown asset or run, 400 for parse/DAG errors, 409 for conflicts (an ambiguous {name} match
in GET /assets/{name}, or cancelling a run that already finished), and 500 for execution or
database failures.
Not in v1
Section titled “Not in v1”No authentication, no WebSocket/SSE streaming (poll /status), no web UI, no distributed
execution, and no persistence of the in-memory run queue across restarts. A future UI is a
separate package that consumes this API; it could later be served from the same server via a
static-file route.