REST API Reference¶
This page documents the notebook REST surface, mounted under /v1/notebooks, plus the core routes a client meets most: materialize, streams, health and metrics (Core API), and the log buffer and artifact listing (Observability). It does not list every route. The rest are documented with their feature:
- Registry and names (
/v1/registry/*,/v1/names*): Registry - Publications (
/v1/artifacts/{id}/v/{n}/publish,/v1/publications*,/p/*): Publishing - Admin, debug and cache routes (
/v1/admin/*,/v1/debug/*,/v1/cache/*): Service Mode and Observability - Garbage collection (
POST /v1/artifacts/gc): Operations & Lifecycle
For materializing from Python, see the Library Quickstart. The live OpenAPI schema (below) lists every route.
The remote-worker contract - /v1/execute, /v1/notebook-execute, /v1/execute-manifest, /health - is documented separately on the Executor Protocol page. Those endpoints live on a different process (strata-worker), not the main server.
Session ID vs Notebook ID
Route parameters use the session ID (a UUID generated when the notebook is opened), not the persistent notebook_id from notebook.toml. The session ID is returned by the open and create endpoints.
Conventions¶
Canonical machine-readable spec¶
The server exposes a live OpenAPI document at runtime:
| Path | What it serves |
|---|---|
GET /openapi.json |
Full OpenAPI 3.1 schema - request and response models for every endpoint, generated from the FastAPI route definitions. |
GET /docs |
Swagger UI for interactive try-it-out. |
GET /redoc |
ReDoc browser for the same schema. |
When this page and the live spec disagree, the live spec wins - it's generated from the source of truth. This page exists for orientation and walks through the high-traffic flows.
Authentication¶
Authentication depends on deployment_mode:
| Mode | Default auth | Required headers |
|---|---|---|
personal |
None (single user) | None |
service + auth_mode="trusted_proxy" |
Proxy-injected identity | X-Strata-Principal, X-Strata-Scopes, X-Strata-Proxy-Token; X-Tenant-ID if multi-tenant |
service + auth_mode="api_key" |
Strata verifies the key itself | Authorization: Bearer strata_<key_id>_<secret>; the key carries the principal, tenant and scopes |
Under trusted_proxy, every /v1/* endpoint requires X-Strata-Principal (the proxy-asserted user identity) and the X-Strata-Proxy-Token shared secret; under api_key, a valid bearer key. The exceptions are the health and metrics endpoints, the signed build-transport routes (the signature is the credential) and public publication reads. Two further restrictions narrow what each endpoint accepts:
Personal-mode-only endpoints. These four return 403 Forbidden outside personal mode. They expose either a filesystem delete or the in-memory session table, neither of which fits a multi-tenant service deployment. Artifact and registry writes, by contrast, are available in service mode when service_writes_enabled is on, with the artifacts:write scope - see Service Mode.
| Endpoint | Why personal-mode-only |
|---|---|
DELETE /v1/notebooks/{session_id} |
Filesystem delete of a notebook directory |
POST /v1/notebooks/delete-by-path |
Same, addressed by path |
GET /v1/notebooks/sessions |
Lists in-memory session ids and notebook filesystem paths |
GET /v1/notebooks/sessions/{session_id} |
Same, for one session |
POST /open, POST /create and GET /discover are not restricted - they work in service mode. With multi_tenant_enabled, all three (and the imports) are confined to the caller's tenant subdir of the storage root.
Tenant-scoped sessions. A session records the tenant of whoever opened, created or imported it, and every /v1/notebooks/{session_id}/... route answers 404 to a caller from another tenant, as for an unknown session. admin:* reaches every session; a session opened by a caller with no tenant belongs to the default tenant (_default) and is reached only by tenantless callers and admin:*.
Scope-gated endpoints. Under principal auth, every route on the /v1/notebooks and /v1/projects routers requires a notebook scope in X-Strata-Scopes. The gate is a router-level dependency keyed on the matched path template, so a route added later is covered without anyone remembering to gate it, and it returns 403 Forbidden naming the scope it wanted.
| Endpoint | Required scope |
|---|---|
Every GET and HEAD, plus the two environment/*/preview posts |
notebook:read |
Content and configuration changes that run nothing: /open, /create, /import, /import-snapshot, /recents/validate, session close, notebook delete, cell add/edit/reorder/delete, a cell's test source, mounts, connections, workers (except provisioning an SSH worker), env, secret manager, name, timeout, variants, quiesce/release, promote |
notebook:write |
| Anything that runs code - execute, cancelling a run, running tests, dependency and Python-version changes (uv runs build scripts), provisioning an SSH worker - and any route nobody has classified | notebook:execute |
POST /v1/cache/clear |
admin:cache |
GET /v1/logs, GET /v1/logs/stream (the ring buffer holds every tenant's records) |
admin:* |
Artifact and registry writes, including name on POST /v1/materialize |
artifacts:write |
GET /v1/cache/entries, GET /v1/cache/histogram, GET /v1/debug/cache/inspect, POST /v1/metadata/cleanup |
admin:cache |
Minting, editing and withdrawing a publication (POST /v1/artifacts/{id}/v/{n}/publish, PATCH / DELETE /v1/publications/{token}) |
artifacts:publish |
Pinning and unpinning a version (POST / DELETE /v1/artifacts/{id}/v/{n}/pin) |
artifacts:pin |
GET /v1/admin/tenants, GET /v1/admin/tenants/{tenant_id} |
admin:tenants |
/v1/admin/notebook-workers* (the server-managed worker registry) |
admin:notebook-workers |
POST /v1/registry/pending/approve and .../reject |
admin:registry |
Quiesce and release (POST /v1/notebooks/{id}/quiesce / release, POST /v1/projects/{path}/quiesce / release) |
admin:notebooks |
POST /v1/artifacts/gc, and another tenant's GET /v1/artifacts/usage / stats |
admin:* |
GET /metrics/tables, GET /metrics/tables/{table_id} (every tenant's table names), and /v1/debug/* other than cache inspection |
admin:* |
notebook:execute is the default on purpose: an operation nobody classified is treated as the most dangerous kind. The per-route table is src/strata/notebook/scopes.py, and the same table checks WebSocket frames - a principal that cannot run a cell over the socket cannot run it over REST either.
Without principal auth (personal mode, or auth_mode="none"), the scope gate returns immediately and none of this applies.
Personal mode has no authentication: anyone who can reach the server can use it. Deploying personal mode to a public URL without an auth proxy is a trust-model decision; see Fly.io deployment for the load-bearing details.
Error shape¶
Most 4xx and 5xx responses use FastAPI's standard JSON shape:
Some carry an object in detail instead. The environment, cell-lock and
quiesce 409s name a code and a message, plus context for that code:
environment_job on ENVIRONMENT_BUSY, cell_id and held_by on
cell_locked. Some core-route 400s and 403s name an error
(writes_disabled, transform_unknown, …) and a message. A 409 because a
cell is already running (execute, close, delete) carries a plain string.
Four middleware responses are plain text, not JSON: the host check's 400 (a
Host the server does not answer to, see STRATA_ALLOWED_HOSTS), the tenant
check's 400 (missing or invalid tenant header) and 403 (tenant not
enabled), and the rate limiter's 429, which also sets Retry-After (whole
seconds) and X-RateLimit-Limit-Type. A stream refused by QoS admission is a
JSON 429 ({"error", "tier"}) with Retry-After.
Validation errors (422) come from Pydantic and contain structured field info:
{
"detail": [
{
"loc": ["body", "python_version"],
"msg": "String should have at most 16 characters",
"type": "string_too_long"
}
]
}
Status codes you'll see¶
| Status | Common cause |
|---|---|
200 |
Success |
307 |
GET /v1/streams/{id} on a node that does not hold the stream, redirecting to the one that does (STRATA_NODE_ADVERTISED_URL) |
400 |
Malformed request (invalid path, bad enum value, a table URI that names no namespace.table, a scan of a column the table does not have) |
401 |
Service mode auth header missing or proxy-token mismatch |
403 |
Authenticated, but missing the required scope (e.g. admin:cache), or a personal-mode-only endpoint called in service mode; also a browser write whose Origin the server does not allow. A table the ACL denies, or another tenant's artifact, build or stream, is 404 instead while STRATA_HIDE_FORBIDDEN_AS_NOT_FOUND=true (the default) |
404 |
Notebook session not found (or another tenant's), a table its catalog does not have, a local warehouse directory that does not exist, a snapshot id the table does not have, or a hidden 403 (see above) |
409 |
Conflict - concurrent environment job, a cell already running in the notebook (execute, close, delete), a cell someone else is editing (cell_locked), or a quiesced notebook (NOTEBOOK_QUIESCED) |
413 |
Request body or scan response exceeded the configured byte cap (STRATA_MAX_UPLOAD_BYTES for artifact uploads, STRATA_MAX_RESPONSE_BYTES for scans) |
422 |
Pydantic validation error on the request body, or a table input Strata refuses to read (an unreadable delete file, too many pending equality deletes); the detail says which. A table the ACL denies is refused first, so its caller gets the 403/404 instead |
429 |
Rate limit exceeded - global, per-client, or per-tenant |
500 |
Server bug - captured to logs with the request ID |
503 |
The server is draining for shutdown and takes no new materialize requests |
504 |
Planning a scan exceeded its timeout |
Request IDs¶
Every request gets an X-Request-ID response header (and the same value is echoed if the client sent one in). Log lines and traces include this ID - copy it when filing bugs.
Tenancy¶
In multi-tenant deployments, the X-Tenant-ID header (1–64 alphanumeric, _, -) scopes every endpoint to that tenant's namespace. The tenant ID is hashed into cache keys, artifact paths, and QoS limiter pools. ACL evaluation is deny-first: explicit denies cannot be overridden by allows. See Service Mode for the full contract.
Notebook Lifecycle¶
Create Notebook¶
{
"parent_path": "/path/to/directory",
"name": "My Notebook",
"python_version": "3.13",
"starter_cell": true
}
Returns notebook state with session_id as soon as the notebook exists. Unlike
open, it does not wait for the environment: the new venv's first uv sync
runs as an environment job, which the response reports in environment_job
(status: "running") and environment.sync_state ("pending"), so a client
can show the notebook while it builds. Until the job finishes, running a cell,
starting another environment job or deleting the notebook answers 409 with
code: "ENVIRONMENT_BUSY". To run cells straight after creating, wait for the
environment_job_finished WebSocket frame, or poll
GET /v1/notebooks/{session_id}/environment/jobs/current
until environment_job.status is completed (or failed). POST
/v1/notebooks/import behaves the same way.
Open Notebook¶
path must be inside STRATA_NOTEBOOK_STORAGE_DIR (default
~/.strata/notebooks; with multi-tenancy, the tenant's own subdirectory of it),
and a relative path is taken from there. Any other path is a 400.
Returns notebook state with session_id and dag. A session already open on
the path is returned rather than a second one: in personal mode any open
session, in service mode only one the same principal opened. Before opening, when
STRATA_NOTEBOOK_SESSION_MIN_AVAILABLE_MB is set and available memory is below
it, idle sessions are closed (see
Session lifetime).
Close Notebook Session¶
Closes the session and stops its warm processes, leaving the notebook and its
results on disk; open it again by path to continue. Connected clients get a
session_closed frame with reason: "closed". 409 while a cell runs or an
environment job is in progress. Needs notebook:write, the scope open needs.
Import Jupyter Notebook¶
Form fields:
| Field | Type | Description |
|---|---|---|
file |
file (required) | The .ipynb upload. Hard cap of 50 MB. |
name |
string | Override the notebook name (defaults to the upload's filename stem). |
parent_path |
string | Override the storage location (must lie inside the configured storage root). |
Converts the upload through strata import and opens a session on
the result. Returns the same notebook state shape as POST
/v1/notebooks/create plus an import_report field with the
converter's per-cell findings (translated magics, captured deps,
warnings, full report markdown). See
Import from Jupyter for the magic
translation table and limitations.
Import Snapshot¶
Form fields as for the Jupyter import (file, name, parent_path), where
file is a bundle from GET …/export?fmt=snapshot. Without name, the
notebook keeps the bundle's name. The upload is streamed to
disk and capped at 2 GiB, since an include=all snapshot is as large as the
notebook's store. A bundle whose files would expand to more than 16 GiB, or
holding a file the import reads whole (manifest, notebook.toml, a cell, a
lockfile, a console log) over 16 MiB, is refused with 400 before anything is
extracted.
Turns the bundle into a notebook directory and opens a session on it. Cells whose artifacts the bundle carried are cache hits before anything runs; cells it only described open idle. With a team store configured, running one of those is a pull rather than a recompute.
A notebook id already in use under the caller's storage root is replaced, and every artifact id and lineage edge that embeds it is rewritten to match. Two copies of one notebook sharing an id would collide as soon as both publish to a shared store.
import_report carries imported_artifacts, replaced_notebook_id (the
bundle's id when it was replaced, else null), and by_reference_cells.
A zip that is not a snapshot is a 400; a snapshot exported before format 2,
which lacked the artifact records an import needs, is refused with a request
to export it again.
Delete Notebook¶
Deletes the notebook directory and closes the session; connected clients get
session_closed with reason: "deleted".
Quiesce and Release¶
POST /v1/notebooks/{session_id}/quiesce
POST /v1/notebooks/{session_id}/release
POST /v1/projects/{path}/quiesce
POST /v1/projects/{path}/release
Holds a notebook, or every notebook under a project directory, still so that a
copy of the directory is consistent: sources, runtime.json and artifacts from
the same moment.
Quiesce waits for running cells to finish. Any still running at
timeout_seconds are cancelled and named in cancelled_cells. Then, until
release, runs and edits are refused with 409 and code: NOTEBOOK_QUIESCED,
over REST and the WebSocket alike, and so is any write that reaches the
notebook's files another way (runtime state, a landing artifact). The hold
ends on its own after max_hold_seconds, so a caller that dies mid-copy
cannot freeze the notebook. A project hold also covers notebooks that are not
open, and overlapping holds are refused with 409.
{
"path": "/srv/notebooks/project",
"notebooks": ["/srv/notebooks/project/analysis"],
"cancelled_cells": {},
"hold_expires_in_seconds": 599.9
}
Release answers {"path", "released"}, with released: false when there was
no hold. Under principal auth both need the admin:notebooks scope (admin:*
satisfies it); personal mode allows them. The project {path} is resolved
inside the storage root like any other notebook path. Holds live in the server
process and do not survive a restart.
Discover Notebooks¶
Lists notebook directories under the configured storage root. Returns
{ "root", "notebooks": [{ "path", "name", "notebook_id", "updated_at" }] }
sorted newest-first. Used by the "Open existing" UI so users pick from a list
instead of typing a filesystem path. Available in both modes; requires
notebook:read under principal auth. On a multi-tenant server the scan root is
<notebook_storage_dir>/<tenant>/ (the whole root for admin:*).
Validate Recent Notebooks¶
Returns {"valid": [...]}, the subset of the (at most 100) paths that still
hold a notebook.toml and lie inside the caller's notebook root (the tenant's
folder on a multi-tenant server); any other path is left out like a missing
one. The UI uses it to prune its recent-notebooks list. Needs notebook:write.
Delete Notebook By Path¶
Deletes a notebook directory by filesystem path. Personal mode only (403 in
service mode). An open session on the path is closed first; 409 while it runs
a cell or an environment job.
Rename Notebook¶
Sessions¶
List Sessions¶
Returns { "sessions": [{ "session_id", "name", "path", ... }] }.
Get Session¶
Returns full notebook state (same shape as open). Used for page refresh reconnection.
Cells¶
List Cells¶
Add Cell¶
language may be python, prompt, markdown, sql, r, or widget. Defaults to python. An after_cell_id the notebook does not have is a 400. author is honored only without principal auth, as on Update Cell Source.
Update Cell Source¶
Returns updated cell, DAG, and all cells (with refreshed staleness). A cell
someone else changed within STRATA_NOTEBOOK_CELL_LOCK_SECONDS is refused
with 409 and code: cell_locked, naming the holder; force: true takes it
over. author is honored only without principal auth, where there is no
authenticated caller to credit.
Delete Cell¶
Reorder Cells¶
Execute Cell (REST)¶
The optional mode query parameter selects the run mode: normal (default -
use the cache and materialize stale upstreams), rerun (bypass the target
cell's cache, still materialize upstreams), or force (run against whatever
upstream artifacts already exist). An unrecognized mode returns 400.
While another cell in the notebook runs, the request is refused with 409.
Tip
For interactive use, prefer the WebSocket cell_execute message. The REST endpoint is for programmatic access.
A run cancelled before it finishes returns 200 with "status": "error" and
"error_code": "cancelled".
Cancel Cell (REST)¶
Cancels the cell's run, however it was started (REST, MCP, or the browser), as
the WebSocket cell_cancel message does: the run stops (a local cell's process
group is killed) and the cell goes back to idle. Needs notebook:execute,
like the frame. Returns { "cell_id", "cancelled", "status" }. cancelled is
false when no run of the cell was in flight; a finished cell keeps its state,
and status says what it is. An unknown cell returns 404.
Run Cell Tests (REST)¶
Runs the committed cells/{cell_id}.test.py via pytest against a re-executed
copy of the cell and returns per-test outcomes: { "cell_id", "passed",
"failed", "errored", "skipped", "pytest_unavailable", "ran_at", "tests": [{
"name", "nodeid", "outcome", "message" }] }. Python cells only - a non-Python
cell or one with no test source returns 400. The REST twin of the WebSocket
cell_run_tests message, for clients (the CLI, agents) that don't drive a
socket.
Set Cell Tests (REST)¶
Writes the committed cells/{cell_id}.test.py and returns the updated cell.
Python cells only (400 otherwise). The authoring twin of the POST above,
which runs them.
List Loop Cell Iterations¶
Lists stored iteration artifacts for a @loop cell. The variable query
parameter defaults to the loop's carry variable if omitted. Non-loop cells
and loops with no completed iterations return an empty list, safe to poll
from the inspect panel.
Returns { "cell_id", "variable", "iterations": [{ "iteration", "artifact_uri",
"artifact_id", "version", "content_type", "byte_size", "row_count",
"created_at" }] }.
Browse a Cell's Table Output¶
GET /v1/notebooks/{session_id}/cells/{cell_id}/data?artifact_uri=<uri>
GET /v1/notebooks/{session_id}/cells/{cell_id}/data/summary?artifact_uri=<uri>
GET /v1/notebooks/{session_id}/cells/{cell_id}/data/export?artifact_uri=<uri>&fmt=csv
Read the full Arrow artifact behind a table-shaped display output, where the
inline preview stops at 20 rows. artifact_uri is the strata://artifact/...
URI the display output carries.
datareturns a page:offset,limit(capped at 500),sort_by,sort_dir(asc/desc),search, andfilters(a JSON array of{col, op, value, value2}). The response hascolumns,rowsandtotal, orpageable: falsefor an output that is not a table.summaryreturns per-column dtype, null, distinct and min/max figures.exportdownloads the same filtered and sorted view asfmt=csvorfmt=parquet.
Get a Display Output's Bytes¶
Serves one display output (a plot's PNG, say) under its own content type, with
the resolved index in X-Strata-Output-Index. A type Strata does not render
(anything but PNG, Arrow, JSON or Markdown) downloads as
application/octet-stream. What strata cell output --out
and the MCP save_cell_output tool read.
Variants¶
Add a Variant¶
Adds a sibling variant to an existing group. Returns new_variant_name,
new_cell_id, variant_groups and cells.
Switch Variant or Mode¶
Either field may be sent alone, but not neither (400). mode is switch or
sweep. Returns variant_groups and cells.
DAG¶
Get DAG¶
Returns edges, roots, leaves, and topological order.
Environment¶
List Dependencies¶
Add Dependency¶
Remove Dependency¶
Get Environment State¶
Sync Environment¶
Runs uv sync synchronously and invalidates any stale cell runtimes. Returns
the full environment payload plus lockfile_changed, operation_log
(command, duration, stdout/stderr), and the per-cell staleness map.
For long syncs prefer the background POST /environment/jobs path, this
endpoint blocks the request until the sync finishes.
Get Current Environment Job¶
Start Environment Job¶
Actions: add, remove and r_add (each needs package), sync, import,
r_init. A Python version change goes through
Change Python Version. Returns 202 with the job.
For import, send exactly one of requirements or environment_yaml.
Change Python Version¶
Rewrites requires-python, removes .venv/ and starts a background uv sync
against the new interpreter, rolling back if that sync fails. Returns 202
with the job (200 and accepted: false when the notebook is already on that
version); progress arrives as environment_job_progress frames. A version
not in the server's available_python_versions is a 400.
List R Packages¶
The packages installed in the notebook's renv project library.
Export Requirements¶
Import Requirements¶
Preview Requirements Import¶
Import environment.yaml¶
Preview environment.yaml Import¶
Workers¶
List Workers¶
Update Notebook Worker¶
Update Worker Catalog¶
Replaces the notebook's [[workers]]. A worker named local is refused with
400: the name is reserved for the built-in worker.
SSH Workers¶
GET /v1/notebooks/{session_id}/workers/ssh
POST /v1/notebooks/{session_id}/workers/ssh
DELETE /v1/notebooks/{session_id}/workers/ssh/{worker_name}?stop_remote=false
POST provisions strata-worker on an SSH target, tunnels to it and registers
it as a notebook worker:
Optional fields are remote_port, local_port, extras (default
notebook), pin and install (default true). GET reports each tunnel's
live status; DELETE closes the tunnel and removes the registration. See
Run cells on a machine you can SSH to.
Mounts¶
Update Notebook Mounts¶
Connections¶
List Notebook Connections¶
Returns:
Replace Notebook Connections¶
The list is canonical: sending an empty list deletes the entire [connections]
block. Literal auth values are blanked on disk during the write round-trip, but
kept in-memory until the session reloads.
Returns:
Enumerate Connection Schema¶
Enumerates the tables and columns visible through the named connection. Used by
the schema sidebar. Opens the connection read-only; an unknown connection is a
404; an unknown driver, a database the server may not open, or credentials it
cannot resolve a 400; and a driver that fails to connect or
enumerate a 502 carrying its message, so auth / driver / connectivity
failures are visible to the UI.
Artifacts¶
List a Notebook's Published Artifacts¶
Returns {"cells": {<cell_id>: [...]}, "readers": {<name>: [...]}}: the
registry artifacts each cell published through the ambient strata client.
Answered by the team store when STRATA_NOTEBOOK_REMOTE_STORE_URL is set,
since that is where a cell's put(name=...) went. readers is read from the
notebook's own store: per registry name, the cells whose stored results read
it with # @dataset, as {"notebook_id", "cell_id", "reference"}. The rows of
GET /v1/registry/summary carry the same readers list for the results in
the registry's store, next to the row's tenant (null for the default
tenant). A personal server or an admin:* caller gets every tenant's names,
so one name can have a row per tenant, each with that tenant's readers.
Promote an Artifact¶
Copies one of the notebook's artifacts, and the chain that produced it, into
the team store and names it there. name is optional: without it the chain is
copied and nothing is named or stamped with the promotion tag, which is what a
platform publishing the result needs; an alias without a name is refused
with 400. table also writes it into that Iceberg table in the team store's
catalog. Returns name, artifact_uri, copied,
alias, alias_pending, table, table_snapshot and store. 409 when no
team store is configured, 404 for an artifact the notebook's store does not
hold, 502 when the team store refuses or cannot be reached.
Export¶
Export Notebook¶
One endpoint, four output formats:
fmt |
Returns |
|---|---|
zip (default) |
Reproducible bundle, notebook.toml, pyproject.toml, uv.lock, cells, provenance.json. Each cell's entry in provenance.json lists its stored outputs (name, artifact_id, version, content_sha256), the same shape as strata run --format json, so a rerun's report can be diffed against the export. |
snapshot |
The zip's members plus outputs, per-cell provenance and timings, an artifact index, and bytes. |
markdown |
Single-file rendering for sharing / docs ingestion. Same engine as strata export. |
html |
Standalone HTML with embedded CSS + Pygments syntax highlighting. |
Markdown and HTML renderings additionally accept include_inactive_variants=true to stack all variants of every group, and app_view=true to render only what the read-only app view shows (widgets, markdown and display outputs, with no sources or console). Prompt-cell responses are intentionally excluded from rendered formats (see Export).
Snapshots¶
A snapshot is the ZIP plus what a reader and a machine each need:
outputs/<cell id>/0.png each display output as a file
outputs/<cell id>/console.json stdout / stderr
artifacts.json per-cell provenance, timings, and every ready
cell's artifacts with their content digests
artifacts/<id>@v=<n> the bytes, for whichever were included
fetch/<sha256>/<file> with include=all, the bytes each @fetch last read
fetch/index.json with include=all, which URL served which bytes
include chooses how much travels:
include |
Carries |
|---|---|
all |
Every artifact's bytes and the notebook's .strata/fetch/: the form for moving a project between servers. |
selected (default) |
Only the cells named in cells=a,b; the rest are described in artifacts.json by id, version and digest. The form a review snapshot uses. |
none |
Description only. |
artifacts.json names everything either way, carried lists what is actually
in the file, and records holds each carried artifact's full record: its
transform spec (the content type a value is read back as) and its lineage
edges. Importing the bundle needs all three. Cells whose
artifacts were only described open idle rather than stale. They ran and their
result is current; it just isn't here.
fetches lists every @fetch with its cell, name, URL, refetch policy and
pinned. sha256 is the pin, or else the digest last read. An unpinned fetch
is the input a snapshot cannot vouch for, since its bytes are whatever the URL
serves at the next run, so a preflight can flag them from this list. A pinned
or refetch=never fetch carried under include=all runs on the importing side
without its URL.
The same bundle offline: strata export <path> --to snapshot --out snap.zip
--include all.
Runtime¶
Get Server Runtime Config¶
Returns deployment mode, available Python versions, and default paths for the server as a whole. Not notebook-scoped.
Update Notebook Default Timeout¶
timeout is seconds (0 < t ≤ 86400) or null to clear back to the system
default. Returns the new timeout and the refreshed cell list.
Update Notebook Default Env¶
Replaces the [env] block in notebook.toml. Sensitive values (keys matching
KEY/SECRET/TOKEN/PASSWORD/CREDENTIAL) are blanked on disk but kept
in-memory for the session so key-dependent cells keep working. Returns the
merged env, per-key sources, and refreshed cell list.
No response or WebSocket frame carries a secret value: a non-empty value under a
sensitive name, or one fetched from the secret manager, is sent as
"__strata_masked__" (in the notebook's env and in each cell's env and
env_overrides). Send that marker back to keep a key's current value; any other
value replaces it.
Update Secret Manager Config¶
{
"provider": "infisical",
"project_id": "your-project-id",
"environment": "dev",
"path": "/",
"base_url": null
}
Persists the [secret_manager] block to notebook.toml and immediately
refetches. An empty payload (all fields null) removes the block:
"disconnect from secret manager". Credentials are never part of this payload;
they must be exported in the server's shell environment.
Refresh Secret Manager¶
Re-fetches secrets from the configured manager and merges them into env.
Never returns 500 on fetch failure, the error surfaces in
env_fetch_error so the UI can display it next to the Refresh button.
Observability¶
Server logs¶
GET /v1/logs returns a snapshot of the in-memory log ring buffer (the most
recent limit matches, oldest first; pass the returned cursor back as
since to get only newer records); GET /v1/logs/stream is a
Server-Sent Events stream that tails new records live. Both accept optional
level (minimum severity), notebook (exact notebook id) and regex filters.
Powers the web UI Logs page.
The buffer is server-wide, so under principal auth both routes require
admin:* (403 otherwise). Without principal auth they stay open.
Artifacts listing¶
GET /v1/artifacts (personal mode only) lists stored artifacts, with optional
limit, offset, state, name_prefix, since (epoch seconds), sort
(created_at, byte_size or row_count) and order query parameters; GET /v1/artifacts/stats returns summary
counts and byte totals, and GET /v1/artifacts/usage adds unique-artifact,
name and unreferenced counts. Both count total_versions and its split by
state: ready_versions, building_versions, superseded_versions (a version
a refresh or a duplicate build overtook) and failed_versions. Powers the web UI Artifacts page. Their
total_bytes counts every version that still holds bytes, superseded ones
included until a sweep collects them: the measure the byte cap uses.
In personal mode, stats and usage cover the whole store. In service mode they
cover one tenant's holdings: the caller's own, from the trusted-proxy
identity, which is what a platform reads to meter storage per tenant. The
tenant's figure counts only rows stamped with that tenant, so legacy tenantless
rows are not charged to anyone. A caller with admin:* may pass
?tenant=<id> to read another tenant (or omit it for the whole store). Service
mode refuses a caller with no tenant (400), a non-admin naming another tenant
(403), and a store with no principal auth configured (403), since without an
authenticated caller the only possible answer is every tenant's at once.
Core API¶
Materialize¶
{
"inputs": ["file:///warehouse#db.events"],
"transform": {
"executor": "scan@v1",
"params": { "columns": ["id", "value"] }
},
"mode": "stream",
"name": "my_result"
}
A table with no snapshots yet streams zero rows with the table's schema
(projected by columns). Nothing read from it is reused: each request is a
miss, as is a transform that takes the table as an input.
A transform's table inputs take every URI form scan@v1 reads (file://,
s3://, gs://, abfs://, a schemeless /wh#ns.t, a named catalog's
cat:ns.t, a bare ns.t). An input that does not resolve (denied, missing,
unreadable, or a plan that failed) answers 4xx with the planner's message;
the request never becomes a pending build.
Get Stream¶
Returns Arrow IPC stream. With an artifact store, the response carries
X-Arrow-Row-Count and X-Strata-Artifact-Uri, the canonical
strata://artifact/{id}@v={n} of the rows it serves. That URI can differ from the
materialize response's: when two misses for one scan were in flight together,
one artifact becomes the ready result and the other reads superseded. The
superseded one keeps no bytes of its own: its GET /v1/artifacts/{id}/v/{n}/data
serves the canonical artifact's blob, the store counts those bytes once, and
retention collects the two together (or neither, while the superseded one is
held). Deleting the canonical artifact first gives a published or pinned
superseded one its own copy of the bytes. Read the header to keep the canonical
one.
Health¶
Readiness (GET /health/ready) and per-dependency checks (GET /health/dependencies)
are covered in Observability.
Metrics¶
Both are unauthenticated, for scrapers. Under principal auth
/metrics/prometheus leaves out the per-table series, since table names are
admin:* data (GET /metrics/tables), and the per-tenant series, since
tenant ids are admin:tenants data (GET /v1/admin/tenants). The AI usage
series carry no tenant or principal label there: they are per-model
totals.
Artifacts, names, cache and diagnostics¶
The routes the client SDK and the web UI call that no feature page covers. The gate column uses these terms:
- read: open in both modes. Under principal auth, a route that takes an
artifact id (
/v1/artifacts/{id}/v/{n}and itslineageanddependents) answers for the caller's tenant's versions and tenantless ones, refuses a caller with no tenant (400), and letsadmin:*reach every tenant. Name, alias, tag, pending, tag-search and provenance lookups resolve in the caller's own tenant (the default tenant when it has none),admin:*included. - write: personal mode, or service mode with
STRATA_SERVICE_WRITES_ENABLEDand theartifacts:writescope. - personal: personal mode only;
403in service mode. - build: personal mode, or service mode with transforms enabled.
- open: no gate beyond the server's authentication.
On a server with STRATA_NOTEBOOK_REMOTE_STORE_URL set, the name, alias,
tag and registry routes, writes included, and artifact lineage are answered
by that team store, after this server's own gate.
| Method | Path | Gate | What it does |
|---|---|---|---|
GET |
/v1/artifacts/{id}/v/{n} |
read | An artifact version's metadata, including input_versions |
DELETE |
/v1/artifacts/{id}/v/{n} |
personal | Delete a version, its blob and its name pointers; a published version is a 409 until withdrawn |
GET |
/v1/artifacts/{id}/v/{n}/lineage |
read | The transitive input graph (artifacts and tables), up to max_depth |
GET |
/v1/artifacts/{id}/v/{n}/dependents |
read | Ready artifacts that take this one as a direct input |
GET |
/v1/artifacts/{id}/v/{n}/tags |
read | The version's tags |
PUT |
/v1/artifacts/{id}/v/{n}/tags |
write | Set one key/value tag |
DELETE |
/v1/artifacts/{id}/v/{n}/tags/{key} |
write | Delete one tag |
GET |
/v1/artifacts/by-provenance/{hash} |
read | A ready artifact by provenance hash (the team-cache lookup) |
PUT |
/v1/artifacts |
write | Store a locally computed result (client.put) under its inputs and transform, optionally naming it. The name is written in the commit that makes the version ready, so a name that cannot be written fails the request with 500 and leaves nothing ready |
PUT |
/v1/artifacts/by-provenance/{hash} |
write | Store a result under a provenance key the caller computed; an artifact_id another tenant holds, at any version, is a 409 |
POST |
/v1/artifacts/upload/{id}/v/{n} |
personal | Upload a version's Arrow IPC bytes |
POST |
/v1/artifacts/finalize |
personal | Mark an uploaded version ready, optionally naming it |
POST |
/v1/artifacts/explain-materialize |
read | Dry-run materialize: hit or miss, and why a rebuild would be needed |
POST |
/v1/artifacts/materialize |
build | The cached artifact on a provenance hit, else a building artifact and its build spec |
GET |
/v1/artifacts/builds/{build_id} |
build | Poll an asynchronous build started by materialize (the caller's own builds) |
GET |
/v1/artifacts/names/{name}/status |
read | A named artifact's state and whether any input has a newer version |
GET |
/v1/names |
read | Every name pointer and its artifact |
POST |
/v1/names |
write | Set or move a name pointer |
GET |
/v1/names/{name} |
read | Resolve a name to its artifact URI |
DELETE |
/v1/names/{name} |
personal | Delete a name pointer |
GET |
/v1/names/{name}/aliases |
read | The aliases a name holds |
GET |
/v1/names/{name}/aliases/{alias} |
read | Resolve name @ alias to its version |
PUT |
/v1/names/{name}/aliases/{alias} |
write | Point an alias at a version; a protected alias answers 202 and queues |
DELETE |
/v1/names/{name}/aliases/{alias} |
personal | Delete an alias; a protected alias answers 202 and queues |
GET |
/v1/registry/artifacts |
read | Ready artifacts carrying one tag (tag_key, optional tag_value) |
GET |
/v1/registry/pending |
read | Protected-alias changes awaiting approval |
GET |
/v1/cache/stats |
open | Disk cache statistics |
GET |
/v1/cache/evictions |
open | Eviction rate and pressure level; include_events=true adds recent events |
POST |
/v1/cache/warm |
table ACL | Warm the cache for some tables and wait until every row group is fetched |
GET |
/v1/cache/warm/jobs/{job_id} |
caller's tenant | A warming job's progress |
DELETE |
/v1/cache/warm/jobs/{job_id} |
caller's tenant | Cancel a warming job; what it cached stays |
GET |
/v1/metadata/stats |
open | Hit/miss counters and entry counts for the metadata store and its caches |
GET |
/v1/config/timeouts |
open | Every timeout setting, grouped by planning, scanning, QoS queue, fetching and S3 |
GET |
/v1/debug/latency |
admin:* |
Latency histograms per stage (plan, first byte, fetch, total) |
GET |
/v1/debug/gc/pauses |
admin:* |
Recent Python GC pauses and their totals |
GET |
/v1/debug/pools |
admin:* |
Planning and fetch thread pool use and queue depth |
GET |
/v1/debug/connections |
admin:* |
HTTP connection metrics: in flight, totals, peak, rate, keep-alive share |
GET |
/v1/debug/memory |
admin:* |
Arrow pool, Python GC and process memory |
GET |
/v1/debug/rate-limits |
admin:* |
Rate limiter allowed and rejected counts |
GET |
/v1/debug/cache/inspect |
admin:cache |
Disk cache entries: key hash, file path, size and stored metadata |
The admin:* and admin:cache gates apply under principal auth; without it
those routes are open.