Service API
In one minuteThe REST API offers two ways to compute — batch jobs for one-off analyses and warm sessions for design loops — plus a mesh store for uploaded mesh files, case validation, progress streams, artefacts and metrics. Responses are JSON, except /metrics (Prometheus text), artefact downloads and the ?stream=true event stream; every error carries a type, a message and a hint.
Base URL: http://HOST:8600/api/v1. The interactive explorer at /api/docs lists every
endpoint with its schema.
Jobs: one-shot analyses#
# submit
curl -s $API/jobs -H 'content-type: application/json' -d '{
"case": "naca0012_euler",
"overrides": {"flow.alpha_deg": 3.0},
"gradients": {"of": ["cl", "cd"], "wrt": ["alpha", "mach"]},
"error_estimate": ["cd"]
}'
# → 202 {"id": "job-20261110-121500-a1b2c3", "state": "queued", "device": "cpu", "digest": "…", …}
curl -s "$API/jobs/$ID?wait=60" # long-poll until finished (or 60 s)
curl -s $API/jobs/$ID/result # full result record
curl -N "$API/jobs/$ID/events?stream=true" # live Server-Sent Events
| Method and path | Purpose |
|---|---|
POST /jobs |
queue an analysis (case, overrides, gradients, error_estimate, adapt, device, use_cache, label) |
GET /jobs |
recent jobs (?state=, ?limit=) |
GET /jobs/{id} |
state, progress, result summary (?wait= seconds) |
GET /jobs/{id}/result |
outputs, history, gradients, error estimates, surface data, provenance |
GET /jobs/{id}/events |
progress events (?since=N, ?stream=true) |
GET /jobs/{id}/artifacts/{name} |
result.json, solution.npz, flow.vtu, field.json, events.jsonl, error.txt |
DELETE /jobs/{id} |
cancel |
With adapt the service chooses the polynomial degree: starting at discretization.order it
raises the degree, warm-starting each level from the previous one, until the adjoint-based
error estimate of one output is within a tolerance.
curl -s $API/jobs -H 'content-type: application/json' -d '{
"case": "bump_euler",
"overrides": {"discretization.order": 1},
"adapt": {"output": "cd", "tolerance": 1e-4, "max_order": 4}
}'
adapt |
||
|---|---|---|
output |
required | scalar output whose estimated error drives the selection |
tolerance |
required, > 0 | absolute tolerance on that estimate |
max_order |
1 to 8, default 5 | highest degree to try |
The result is that of the final degree (spec, case, outputs, gradients,
error_estimate, artefacts) and has an adaptation section:
{output, tolerance, met, order, value, corrected, error, levels}, where levels has one
record per degree tried. Check met: a job whose tolerance was not reached at max_order
is still succeeded, with the result of the highest degree that converged. The job record
carries requested.adapt and result_summary.adaptation (without levels). The event log
shows every degree in turn: a case event, the iteration events of its solve (each with
order), and one adapt event (the level record) that closes it; the last case event is
that of the result. timing.solve is the wall time of the whole selection and
timing.setup is 0.
A degree whose solve fails from the interpolated lower-degree solution is tried again before
it counts as failed: from the same guess with the full continuation (skipped when the guess is
not an admissible state), then from the free stream. Each further attempt is an adapt_retry
event with order, reason (why the previous attempt failed) and start (interpolated or
free_stream).
| Field of a level record | Meaning |
|---|---|
order |
polynomial degree of this level |
degrees_of_freedom |
size of the discretization at this degree |
converged |
whether the flow solve of this degree converged |
value |
the output at this degree |
estimate |
adjoint-based estimate of its discretization error |
corrected |
the output corrected by the estimate |
error |
the estimated error that is compared with tolerance (by absolute value) |
adjoint_converged |
whether the adjoint solve of the estimate converged |
newton_steps |
steps of the flow solve |
wall_time |
seconds spent on this degree, estimate included |
A degree whose flow solve did not converge has order, degrees_of_freedom,
converged: false, the solver’s reason and wall_time only.
met is true only when the estimate is a finite number, its adjoint solve converged and its
absolute value is at most tolerance; an estimate below the tolerance from an adjoint solve
that did not converge does not end the selection. The selection also ends at the first degree
whose flow solve does not converge, below max_order too: met is false and order,
value, corrected and error are those of the last degree that converged. If the starting
degree itself does not converge, these four are null and the result has converged: false.
Unknown keys and invalid values are a 422 ValidationError. A selection that cannot start —
discretization.order is 0, or max_order is below discretization.order — is a 422
InvalidCase. An output that the case does not have is found by the worker: the job fails.
queued → running → succeeded | failed | cancelled, plus interrupted for jobs that were active
when the service restarted. A job identical to an earlier successful one is answered immediately
from the cache with cached_from naming the original; pass "use_cache": false to force a new
run. The cache key is the specification, the gradient, error-estimate and adapt requests (an
omitted max_order equals 5), the contents of a mesh file the case names, and the code
(version, revision and a hash of the package sources): a result computed by different code, for
example before an upgrade on the same data directory, is not reused, and replacing a stored
mesh under the same name is a cache miss. On a cache hit device is
the device of the original run. A job that ran but did not converge is succeeded with
"converged": false — check the flag. Its outputs are those of the iterate the solve ended
at, and it has no gradients or error_estimate: the result and the job’s result_summary
carry skipped, which names each requested section that is missing with the reason.
Non-finite values (a diverged solve) are reported as
null in job records, results, events and session replies.
Sessions: warm solvers for design loops#
SID=$(curl -s $API/sessions -H 'content-type: application/json' \
-d '{"case": "naca0012_euler"}' | jq -r .id)
curl -s $API/sessions/$SID/evaluate -H 'content-type: application/json' \
-d '{"inputs": {"alpha_deg": 2.5, "mach": 0.52}}'
# → {"converged": true, "nonlinear_iterations": 5, "outputs": {"cl": 0.36, …}, "wall_time": 0.8}
curl -s $API/sessions/$SID/gradients -H 'content-type: application/json' \
-d '{"of": ["cl"], "wrt": ["alpha_deg", "x_surface"]}'
curl -s -X DELETE $API/sessions/$SID
| Method and path | Purpose |
|---|---|
POST /sessions |
start a solver process for a case; returns its interface (inputs, outputs, shapes) |
GET /sessions, GET /sessions/{id} |
list / describe |
POST /sessions/{id}/evaluate |
set inputs, converge (warm start), return outputs |
POST /sessions/{id}/gradients |
total derivatives at the current state |
POST /sessions/{id}/adjoint |
reverse total product for output seeds (coupling) |
POST /sessions/{id}/error-estimate |
adjoint-weighted-residual estimates |
GET /sessions/{id}/surface |
surface-node coordinates and current displacement |
GET /sessions/{id}/distributions |
wall , |
GET /sessions/{id}/field |
field sample for visualisation |
GET /sessions/{id}/state |
current inputs, solved, converged and residual norm |
POST /sessions/{id}/tangent |
forward total product for input perturbations |
POST /sessions/{id}/residual |
norms of the residual of the session’s state, at its inputs or at given ones |
POST /sessions/{id}/revert |
after an evaluation that did not converge: back to the last converged point |
POST /sessions/{id}/get-state, …/set-state |
download / upload the flow state (bytes, application/octet-stream) |
POST /sessions/{id}/checkpoint, …/restore |
save / restore inputs, surface displacement and state as one blob; a checkpoint also rebuilds a session that was closed or reaped (GyreClient.restore_session) |
DELETE /sessions/{id} |
close |
The last five rows go through one generic route, POST /sessions/{id}/{command}, which
passes any command of the solver process through (- and _ are the same in the name);
GET /sessions/{id} lists the commands a session supports.
One command runs at a time per session. The first evaluate compiles kernels; later ones are
warm-started and typically need a handful of Newton steps. Idle sessions close after 30 minutes;
at most four are open at once by default.
alpha_deg is accepted wherever alpha (radians) is: in evaluate inputs and in the wrt of
/gradients, /adjoint and job gradients. The derivative is returned under the name asked
for, per degree for alpha_deg. With wrt omitted the result has alpha (radians) only, and
/state and the session interface list alpha. Non-finite or non-numeric inputs are refused
with 422.
If an evaluation does not converge, the reply has converged: false with the outputs and
residual of the iterate the solve ended at, and the session keeps its last converged state as
the warm start of the next evaluation. Until an evaluation converges again, /gradients,
/adjoint, /error-estimate, /distributions and /field answer 422 RuntimeError; the
last two do so before the first evaluation as well. converged of /state is true when a
state exists and the last evaluation converged.
case is a preset name or a CaseSpec object, for jobs, sessions and validation alike; a string
is never read as a file path (file specifications are a CLI and Python feature). A case on a
mesh file names a mesh of the mesh store. A session reads that mesh once, when it
starts: it keeps running on it, and reports the digest of that moment, if the mesh is
replaced or deleted afterwards.
Meshes#
A case with mesh: {generator: file, params: {path: NAME}} refers to a mesh that was uploaded
to the service’s mesh store under NAME. Mesh files has the
formats, examples for curl, Python and MCP, and the limits.
curl -s -X PUT --data-binary @wing.msh -H 'content-type: application/octet-stream' \
$API/meshes/wing.msh
# → 201 {"name": "wing.msh", "bytes": …, "sha256": "…", "dim": 2, "elements": …,
# "geometry_order": …, "nodes": …, "boundaries": {"wall": …, "farfield": …}, …}
| Method and path | Purpose |
|---|---|
GET /meshes |
{"meshes": [summary, …]}, sorted by name |
PUT /meshes/{name} |
store the request body (the file as it is on disk) under name; 201 with the summary; replaces an existing mesh of that name |
GET /meshes/{name} |
summary: name, bytes, sha256, dim, elements, geometry_order, nodes, boundaries (faces per group), bounding_box, the reader’s quality numbers |
DELETE /meshes/{name} |
remove; {"name": name, "deleted": true} |
- A name matches
^[A-Za-z0-9][A-Za-z0-9_.-]{0,79}\.(msh|npz)$. Anything else, including a name with directories, is a 422InvalidMeshNameon every mesh route and inmesh.params.pathof a case; nothing is opened or written. - The upload is read and validated in full. A file the reader cannot use is a 422
InvalidMeshwith the reader’s message and is not stored; a mesh already stored under the name is unchanged. - A case that names a mesh which is not stored is a 404
UnknownMesh, at validation, job submission and session creation; the hint names thePUTto make. A case whosediscretization.boundariesdoes not give a condition for every boundary group of the mesh, or names a group the mesh does not have, is a 422InvalidCase. - An upload above
GYRE_MESH_MAX_BYTES(64 MiB by default) is a 413MeshTooLarge; a full store is a 507MeshStoreFull(limits). - A file that was placed in the store’s directory by hand and that the reader rejects is
listed by
GET /meshesas{"name", "error"}and answered 422InvalidMeshbyGET /meshes/{name}.
Pipelines#
| Method and path | Purpose |
|---|---|
GET /pipelines |
pipeline definitions available to the deployment (stages, parameters); each entry has runnable and, when that is false, a reason |
POST /pipelines/{name}/runs |
start a run (params, force); unchanged stages come from the cache. String params stay strings. Every stage’s case, after the run’s parameters are substituted, must be a preset name or a CaseSpec object (404 UnknownPreset otherwise, whatever the parameter is called). Runs of one pipeline are executed one after another |
GET /pipelines/runs, GET /pipelines/runs/{id} |
list runs / status; record is the per-stage record this run wrote, or null if the run ended before the engine finished |
Definitions with shell stages are not runnable through the service (403 PipelineNotRunnable)
unless it was started with GYRE_PIPELINES_ALLOW_SHELL=1. A run leads its own process group:
signals to the service’s process group do not reach it, and at service shutdown each run is
terminated together with its stage processes.
See Pipelines for the YAML vocabulary.
Cases, metadata, metrics#
| Method and path | Purpose |
|---|---|
GET /presets, GET /presets/{name} |
built-in cases |
GET /schema/case, GET /schema/job |
JSON Schemas |
POST /cases/validate |
dry run: resolved spec, size estimate, device, warnings |
GET /info |
version, commit, process id (pid), deployment name, uptime, documentation build |
GET /health |
liveness |
GET /metrics |
Prometheus text format |
Errors#
Every 4xx and 5xx answer under /api/v1 has the same shape, with type, message and hint
always present:
{"error": {"type": "UnknownPreset",
"message": "unknown preset 'naca0013'",
"hint": "available presets: ['bump_euler', 'cylinder_re40', …]"}}
| Status | type |
Meaning |
|---|---|---|
| 401 | Unauthorized |
bearer token required (GYRE_API_TOKEN is set) |
| 403 | PipelineNotRunnable |
the pipeline has shell stages (or an invalid definition) and may not be started through the service |
| 404 | UnknownPreset, UnknownPipeline, NotFound |
no such preset, pipeline, job, session, artefact, pipeline run or API route; UnknownPreset is also the answer to a pipeline run in which a stage’s case is a string that is not a preset name |
| 404 | UnknownMesh |
no mesh of that name in the mesh store: on the mesh routes, and for a case that names it |
| 405 | MethodNotAllowed |
the path exists but not with this method; for GET, POST, PUT, PATCH and DELETE the Allow header lists the methods of the path (HEAD and OPTIONS are answered 405 on every API path, with an Allow header that is not complete) |
| 409 | NotReady |
no result: the job has not finished, or did not succeed |
| 410 | SessionDead |
the session process has terminated |
| 422 | ValidationError |
the request does not match the route’s schema (body, query, malformed JSON), or the case specification is invalid: mesh parameters the generator does not accept, order 0 for a viscous case, missing boundary data |
| 413 | MeshTooLarge |
the uploaded mesh file exceeds GYRE_MESH_MAX_BYTES |
| 422 | BadOverride, InvalidCase |
unknown override path; an empty case; a mesh-file case whose discretization.boundaries does not match the boundary groups of the mesh; an adapt request that cannot start |
| 422 | InvalidMeshName, InvalidMesh |
a mesh name that is not a bare store name (in a URL or in mesh.params.path); a mesh file the reader cannot use, with the reader’s message |
| 422 | KeyError, ValueError, RuntimeError |
a session command failed: unknown input or output name, bad input value, no flow state yet |
| 429 | TooManySessions |
session limit reached |
| 500 | InternalError |
unexpected fault of the service. The message is the service failed while handling this request (incident <8 hex digits>); the exception type and text are in the service log (logger gyre.service) under the same incident id and are not sent to the client |
| 500 | SessionStartFailed |
the session process died while building the case |
| 503 | MeshStoreInactive |
a case that names a stored mesh was sent to an application that was not started with its start-up phase (never the case under gyre serve); the name check (422 InvalidMeshName) and the store lookup (404 UnknownMesh) come first |
| 504 | Timeout |
a session command, or session start-up, exceeded its time limit |
| 507 | MeshStoreFull |
the mesh store holds GYRE_MESH_STORE_MAX_FILES meshes or would exceed GYRE_MESH_STORE_MAX_BYTES |
Session command errors carry the exception class name as type; classes other than the three
listed are answered with 500. An exception raised while a new session builds its case is a 422
with the class name as type; its hint, like that of a start-up Timeout, points to
POST /cases/validate, since there is no session to inspect. Case errors are reported by POST /cases/validate and, identically,
when a job or session is submitted. The error object inside a failed job record is
{"type", "message"} without a hint; the traceback of a worker exception is the error.txt
artefact.
gyre.client.GyreClient wraps the job, session, mesh, preset, validation and info endpoints
(submit, job, wait, run, result, events, artifact, cancel, session, meshes,
upload_mesh, mesh, delete_mesh, presets, validate, info) and raises GyreError
carrying the status and the structured error. submit(..., adapt={"output": "cd",
"tolerance": 1e-4}) and run(..., adapt=...) send an order selection;
upload_mesh(path, name=None) stores a local file under its own name unless name is given. Pipelines, metrics and
schemas are reached with plain HTTP. See MDO integration for
RemoteDiscipline.