API reference
This is the JSON REST API for the complete agent simulation loop:
author or import a model, solve and check it, then inspect, modify or re-submit the
returned model. The base URL is https://www.physicsbase.dev. The same
contract supports one-time simulation, verification and optimization workflows.
Introduction
Send a Problem object. You get a Result
object back. The endpoint or the analysis field selects static,
modal, buckling, or transient. There is no state between calls. Each solve is
independent. Thus agents can send many solves at the same time.
curl -X POST https://www.physicsbase.dev/v1/solve \
-H "authorization: Bearer $PB_KEY" \
-H "content-type: application/json" \
-d '{
"nodes": [[0,0],[2,0]],
"materials": {"steel": {"E": 200e9, "nu": 0.3}},
"elements": [{"type":"truss2d","nodes":[0,1],"material":"steel","section":{"A":0.01}}],
"supports": [{"node":0,"dofs":["ux","uy"]},{"node":1,"dofs":["uy"]}],
"loads": [{"node":1,"fx":50000}]
}'
Authentication
Each solve needs an API key. Send it as a bearer token. Make a free key at sign-up. It is unlimited in the research preview and needs no card. Send the key on each request:
Authorization: Bearer pk_live_…A missing key or an unknown key returns 401. Manage your keys and use data on your
dashboard. To try the API without a key, use the
keyless POST /demo/solve endpoint. It runs small problems, has a rate limit,
and needs no sign-up. It also runs the live demo on the home page.
Errors and status codes
Errors return a JSON body with a detail string. You can act on the string
directly. Examples are a bad support, a singular model, and an unknown element.
| Code | Meaning |
|---|---|
200 | OK. The results are in the body. |
401 | The API key is missing or not valid — {code: NO_API_KEY | INVALID_API_KEY}. |
422 | The problem is not valid. The body gives {code, message, hint}. It tells you what to correct. |
429 | This occurs only on the keyless /demo/solve endpoint. It has a limit of 30 requests each hour. |
500 | An unexpected solve error. You can send the request again. |
{ "detail": {
"code": "NO_SUPPORTS",
"message": "model has no supports; stiffness is singular",
"hint": "Add at least one support to restrain rigid-body motion." } }
Usage
In the research preview there is no solve quota. Each key is unlimited
and free. The system still counts use for each key. This lets us learn how agents
use the API. See your total at any time at GET /auth/usage. The
keyless /demo/solve endpoint has its own limit of 30 requests each hour
for each IP (429). See more on the pricing page.
The Problem object
This is the one input for all endpoints.
| Field | Type | Description |
|---|---|---|
nodes required | number[][] | Coordinates, [x,y] (2D) or [x,y,z] (3D). |
materials required | object | Name → {E, nu, rho?, alpha?}. Orthotropic lamina: add {E1, E2, nu12, G12} and set the fibre angle in the element section. |
elements required | object[] | {type, nodes, material, section, id?}. See element reference. |
supports | object[] | {node, dofs[], values?}. values = prescribed displacement. |
loads | object[] | {node, fx?, fy?, fz?, mx?, my?, mz?}. |
element_loads | object[] | {element, w[]} distributed load on a beam/frame. |
springs | object[] | {node, dof, k} grounded elastic support. |
thermal | object[] | {element, dT} temperature rise (needs material alpha). |
body_forces | object[] | {element, b[]} force per unit volume. |
tractions | object[] | {nodes[], traction[], thickness} edge/surface pressure. |
gravity | number[] | Acceleration vector for self-weight, e.g. [0,-9.81]. |
analysis | string | static (default), modal, buckling, transient. |
num_modes | int | Modes returned for modal/buckling (default 6). |
dt, n_steps | number, int | Time step and step count for transient. |
estimate_time_error, target_time_error | bool, number | Run a Richardson dt/dt/2 estimate for supported transients (default true) and set its relative target (default 0.02). |
record_iterations | bool | Attach genuine nodal iteration/time-step states for supported solvers. Use with return_model for playback. Direct solves are labelled initial/direct; they do not fabricate iterations. |
return_model | bool | Attach the self-describing mesh, fields, viewer metadata, animation timeline and self-check under model. |
damping | number[] | Rayleigh [alpha, beta] for transient. |
title | string | Optional label echoed in the summary. |
The Result object
Static and transient use the same displacement and reaction shape. Modal and
buckling return spectra. Each response has a summary.
{
"displacements": [ { "ux": 0.0, "uy": 0.0 }, … ],
"reactions": [ { "fx": -50000.0, "fy": 0.0 }, … ],
"elements": [ { "type": "truss2d", "axial_force": 50000.0,
"axial_stress": 5.0e6 }, … ],
"summary": { "n_dof": 4, "max_abs_displacement": 5e-5,
"analysis": "static", "solve_ms": 1.2 } }
POST /v1/solve
This endpoint runs the analysis in the analysis field of the problem.
The default is static. This is the main endpoint. The other endpoints are shortcuts.
/solve (no version prefix) is an alias.
Returns
A Result with displacements,
reactions, forces and stresses for each element, and a summary.
The element results depend on the type. Trusses give axial_force.
Beams give moment_i/j. Continuum elements and solids give a
stress vector and von_mises.
Identical normalized problems are cached. The cache is shared by REST,
MCP, async jobs, scalar fields, and the in-process SDK. It is TTL-limited,
LRU-bounded, and deep-copy isolated; concurrent identical misses run one solve.
Numerical failures are never cached. Inspect summary.cache.hit and
summary.cache.coalesced; aggregate counters are available at
GET /stats.
POST /v1/jobs GET /v1/jobs/{id}
Async solves for long-running problems (202 + poll). POST /v1/jobs takes
the same Problem body as /v1/solve and returns 202 at once:
{"job_id", "status": "queued", "poll": "/v1/jobs/{id}"}. Poll the
poll URL with the same key until status is done —
the full Result is in the poll body; failed carries
the usual error object. Polling costs nothing against the rate limit.
DELETE /v1/jobs/{id} cancels a queued job. Job ids expire about an hour
after completion.
POST /v1/solve itself auto-diverts problems over ~5,000
nodes/elements to a job (you get the 202 body). Force the behaviour with
?mode=async or ?mode=sync.
{
"job_id": "job_kX3…", "status": "done", "elapsed_s": 42.7,
"meta": { "analysis": "static", "n_nodes": 84211, "mode": "auto" },
"result": { … the same Result a synchronous solve returns … } }
POST /v1/solve · analysis: viscoelastic | creep
new Time-dependent materials (trusses).
viscoelastic is a Prony-series generalized Maxwell solid:
params.prony = [{"E", "tau"}, …], the material E is the
instantaneous modulus and E_inf = E − ΣE_i. The internal variables use
the exact exponential integrator, so stress relaxation under a held displacement
follows σ(t) = ε₀(E_inf + ΣE_i e^{−t/τᵢ}) to the step tolerance.
creep is Norton power-law creep
dε_c/dt = A·σⁿ (params.A, params.n), integrated
implicitly — for a constant-stress bar it reproduces
ε = σ/E + A·σⁿ·t exactly. Both use dt,
n_steps, and monitor, and return per-step histories plus
per-element stress, creep strain, and branch states.
POST /v1/solve · composite layup sections
new Laminated sections. Give
plate4, mitc4, shell4,
shell_mitc4, or quad4 a
section: {"layup": [{E1, E2, nu12, G12, thickness, angle}, …]}
instead of a thickness, and classical lamination theory drives the element:
the CLT A-matrix for membrane stiffness, D for bending. Plate and
shell results report moments from the laminate D; membrane elements report
force resultants (N/m) plus the thickness-average stress.
Symmetric stacks only — an unsymmetric layup (B ≠ 0) returns a clear
422 pointing at /laminate for the pure
CLT coupled response. Numerically verified against the Navier series for a [0/90]s
simply supported plate and the CLT effective-Ex of a
quasi-isotropic coupon.
POST /laminate
Solves the full classical-lamination-theory ABD response for a ply stack under
in-plane resultants N and moments M. Unlike the symmetric
element-section shortcut above, this endpoint retains membrane-bending coupling and
returns mid-plane strains, curvatures and recovered per-ply stresses.
POST /v1/solve · analysis: nltransient
new Nonlinear transient dynamics — implicit
Newmark (average acceleration) with full Newton on the co-rotational tangent.
Handles what linear transient cannot: dynamic snap-through, large
rotations, cable-like stiffening. Trusses for now. Same fields as
transient (dt, n_steps, optional Rayleigh
damping, monitor). Undamped runs return
summary.energy_check — the mechanical-energy drift relative to peak
energy; a large drift means dt is too coarse for the response, so the
result carries its own quality signal.
POST /v1/solve · analysis: contact
Unilateral frictionless contact by an exact active set — no penalty
parameter. Two constraint types mix freely in the contacts list:
a rigid barrier {node, dof, limit, side: 'upper'|'lower'}
stops one DOF at a fixed value, and a deformable-to-deformable pair
{node_a, node_b, dof, gap, sign?}new
lets two bodies meet: it enforces sign·(u_a − u_b) ≤ gap, both
bodies deform, and the transmitted force is solved for and reported per pair
(compression positive). Pairs with gap ≤ 0 start in contact.
For surfaces whose meshes do not line up, use
node-to-surface contact {node, segment: [t1, t2]} in 2D or
{node, facet: [t1, t2, t3]} in 3Dnew:
the contactor node is projected onto the target and the one contact force is
distributed to the target nodes by the projection shape functions (reported
under node_forces). The summary lists each contact with
in_contact and contact_force.
Opt-in spatial-convergence evidence. Set
estimate_contact_error: true to run the declared contacts on the
submitted mesh and on one uniformly h-refined supported mesh. The refined
solution is returned. summary.contact_convergence compares total and
per-contact force, maximum penetration/gap violation, and active-set stability.
Give a contact a stable id and positive contact_area to
include pressure and pressure convergence. Change the default 5% force/pressure
threshold with target_contact_error.
{
"analysis": "contact",
"estimate_contact_error": true,
"target_contact_error": 0.05,
"contacts": [{ "id": "interface-A", "node_a": 41,
"node_b": 86, "dof": "uy", "gap": 0.0,
"contact_area": 2.5e-4 }]
}
POST /modal
This endpoint gives natural frequencies and mode shapes. It solves (K − ω²M) φ = 0.
The materials must include rho. The system ignores applied loads.
{
"frequencies_hz": [ 9.1, 57.0, 159.6, … ],
"angular_frequencies": [ … ],
"mode_shapes": [ [ {"ux":0,"uy":0,"rz":0}, … ], … ],
"summary": { "num_modes": 6, "analysis": "modal" } }
POST /buckling
This endpoint does linear buckling. The applied loads give the reference
pattern. The load_factors multiply the pattern to reach instability. The
critical_loads give the magnitudes.
{
"load_factors": [ 1842.1, … ],
"critical_loads": [ 1842100.0, … ],
"mode_shapes": [ … ],
"summary": { "reference_load": 1000.0, "analysis": "buckling" } }
POST /transient
This endpoint gives the Newmark step response. The loads stay constant from t=0.
The initial conditions are zero. Set dt and n_steps. The
damping and monitor = [node, dof] fields are optional.
By default the solver also runs dt/2 and reports a second-order
Richardson estimate for the returned coarse response. Set
estimate_time_error:false to opt out or change the 2% threshold with
target_time_error.
{
"times": [ … ], "peak_displacement": 0.0521, "peak_time": 0.018,
"frames": [ {"t":…, "max_abs_displacement":…}, … ],
"monitor": { "node": 1, "dof": "uy", "history": [ … ] },
"summary": { "dt": 2e-4, "n_steps": 150, "analysis": "transient" } }
POST /bundle
This endpoint solves the problem and returns a complete, self-describing model.
It is the same as any solve with return_model: true. The model is one
JSON document. An agent can parse it, animate it, or send it back to the engine.
It holds the mesh, the materials, the boundary conditions, the results, per-node
colour fields, an animation block, and the full analysis-scoped self-check.
{ "model": {
"schema": "physicsbase.model/1.0",
"meta": { "analysis": "static", "units": "SI", "n_nodes": 105, "n_elements": 84 },
"mesh": { "nodes": [ … ], "elements": [ { "type": "quad4", "nodes": [ … ] }, … ], "materials": { … } },
"bcs": { "supports": [ … ], "loads": [ … ] },
"results": { "displacements": [ … ], "elements": [ … ] },
"fields": { "von_mises": [ … ], "displacement_magnitude": [ … ] },
"animation": { "kind": "deformation", "deformation": [ [dx,dy], … ],
"recommended_scale": 18.0, "color_field": "von_mises", "color_range": [ … ] },
"self_check": { "balanced": true, "equilibrium_residual": 2.9e-10 } } }
To animate, place each node at mesh.nodes[i] + t · recommended_scale · animation.deformation[i]
for t from 0 to 1, and colour it by fields[animation.color_field].
For modal and buckling, animation.modes holds one deformed shape and scale for each mode.
z=0. With record_iterations: true, applicable
animation.frames[] entries carry iteration,
relative_residual, current nodal deformation and
fields. The current implementation records real CG, geometric-Newton,
Navier-Stokes Picard, contact active-set and sampled structural-transient states.
Direct solves provide labelled initial/final states only. Modal and buckling playback
is a sinusoidal phase animation, not solver convergence.POST /v1/import
This endpoint reinterprets an external CAD or FE file as a native model,
solves it, and returns the result on the geometry. You send the file in the
request. CAD solids (.step, .stp, .iges,
.brep) are meshed into tetrahedra. Mesh files carry their own
elements: Abaqus .inp (also its materials, sets, and boundary
conditions), Nastran .bdf/.nas, Gmsh .msh,
CalculiX .frd, VTK/.vtu, STL, and more. Call
GET /v1/capabilities to read the exact extension registry and
whether each optional backend is installed in the running service.
{ "filename": "bracket.step",
"content_base64": "<the file, base64-encoded>",
"material": { "E": 2.1e11, "nu": 0.3 },
"supports": [ { "axis": "z", "op": "min", "dofs": ["ux","uy","uz"] } ],
"loads": [ { "axis": "z", "op": "max", "fz": -5000 } ],
"analysis": "static", "mesh_size": 0.5 }
202 with {job_id, poll}.
Poll GET /v1/jobs/{job_id} until status:"done"; the
completed simulation is then under result. Use
?mode=sync or ?mode=async to force either path.Boundary conditions
A raw CAD solid carries no loads or supports, so you name them with a small
selector: by node id ({"node": 12}), by named set from the file
({"nset": "TOP"}), or by a coordinate plane
({"axis": "z", "op": "min|max|==", "value": .., "tol": ..}).
A support adds "dofs"; a load adds force components
(fx/fy/fz) which are the total, split over
the selected nodes. An Abaqus .inp that already carries
*BOUNDARY and *CLOAD needs none of this. INP
material sections, generated sets, shell thickness, and direct/set BCs are
retained. Unsupported safety-critical load/contact cards fail explicitly;
they are never silently omitted.
Returns
The same result as /solve, plus the
model bundle under model — the
reinterpreted mesh, the results, and the self-check, all on the imported
geometry. summary.import and model.meta.import
include the source name and SHA-256, adapter/mesher, requested and resolved
mesh size, coordinate scale, translated element counts, named sets, and
linearisation warnings.
For security, remote clients should upload content_base64.
Server-side paths are disabled unless an administrator configures
PHYSICSBASE_IMPORT_ROOT, and are then confined beneath that root.
Uploads are size-limited. Quadratic cells are currently linearised to corner
nodes. Triangulated 3D surface-only STL/OBJ/PLY files need volume or
quad-shell remeshing before solve. Abaqus assemblies/instances and full
vendor-deck semantics remain outside this adapter version.
POST /demo/cad/{example_id}
Runs a curated, packaged CAD example without an API key. Unlike
/v1/import, this route accepts no path and no file content: the
server owns a hash-pinned STEP fixture and a reviewed material, mesh, support,
load, and size envelope. Discover examples with GET /demo/cad.
curl -X POST https://www.physicsbase.dev/demo/cad/2020-corner-bracket \
-H "content-type: application/json" \
-d '{"load_n":100}'
The current model is a CC BY 3.0 corner bracket for 20 mm aluminum
extrusion, assigned 6061-T6 properties. One mounting face is fixed and
25–200 N of lateral racking is distributed over the perpendicular face.
The response is the normal static result plus a complete model bundle.
summary.cad_demo and model.meta.cad_demo record the
upstream URL/commit, author, license, source SHA-256, material, boundary
conditions, load, coordinate scale, and coarse-mesh policy.
/v1/import with a refined mesh for release work.GET /demo/automotive
Returns provenance, mesh counts, execution status, published reference evidence and an explicit claim boundary for precomputed automotive cases. The route is keyless and read-only; it never starts an explicit job.
curl -s https://www.physicsbase.dev/demo/automotive
curl -s https://www.physicsbase.dev/demo/automotive/2015-toyota-camry-ncap
curl -s https://www.physicsbase.dev/demo/automotive/openradioss-bumper-impact-probeThe current Camry entry records 2,273,631 nodes and 2,277,283 elements,
the official archive SHA-384, OpenRadioss as the external numerical backend,
the official four-core runtime estimate, and which work physicsbase has and
has not completed. Published CCSA/LS-DYNA validation values are never returned
as physicsbase-computed fields. The component entry records an interrupted
trace through 14.9 ms, the 14.0 ms last complete field frame, solver hashes,
and normal_termination: false.
GET /capabilities
This is a machine-readable list of all functions of the engine. It gives the element types, DOF names, load components, analyses, and more. An agent must call this endpoint first to build a valid problem.
curl -s https://www.physicsbase.dev/capabilities
GET /health
This endpoint shows that the service is available. It returns
{"status":"ok","version":"…"}. It needs no key.
fem_solve, fem_modal,
fem_buckling, fem_transient) and a Python SDK. See
the docs.