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.

bash · a complete requestcopy
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:

http · authorization headercopy
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.

CodeMeaning
200OK. The results are in the body.
401The API key is missing or not valid — {code: NO_API_KEY | INVALID_API_KEY}.
422The problem is not valid. The body gives {code, message, hint}. It tells you what to correct.
429This occurs only on the keyless /demo/solve endpoint. It has a limit of 30 requests each hour.
500An unexpected solve error. You can send the request again.
json · 422 bodycopy
{ "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.

FieldTypeDescription
nodes requirednumber[][]Coordinates, [x,y] (2D) or [x,y,z] (3D).
materials requiredobjectName → {E, nu, rho?, alpha?}. Orthotropic lamina: add {E1, E2, nu12, G12} and set the fibre angle in the element section.
elements requiredobject[]{type, nodes, material, section, id?}. See element reference.
supportsobject[]{node, dofs[], values?}. values = prescribed displacement.
loadsobject[]{node, fx?, fy?, fz?, mx?, my?, mz?}.
element_loadsobject[]{element, w[]} distributed load on a beam/frame.
springsobject[]{node, dof, k} grounded elastic support.
thermalobject[]{element, dT} temperature rise (needs material alpha).
body_forcesobject[]{element, b[]} force per unit volume.
tractionsobject[]{nodes[], traction[], thickness} edge/surface pressure.
gravitynumber[]Acceleration vector for self-weight, e.g. [0,-9.81].
analysisstringstatic (default), modal, buckling, transient.
num_modesintModes returned for modal/buckling (default 6).
dt, n_stepsnumber, intTime step and step count for transient.
estimate_time_error, target_time_errorbool, numberRun a Richardson dt/dt/2 estimate for supported transients (default true) and set its relative target (default 0.02).
record_iterationsboolAttach 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_modelboolAttach the self-describing mesh, fields, viewer metadata, animation timeline and self-check under model.
dampingnumber[]Rayleigh [alpha, beta] for transient.
titlestringOptional 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.

json · static resultcopy
{
  "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.

json · poll response (done)copy
{
  "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.

json · contact convergence requestcopy
{
  "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 }]
}
Scope: this is a one-level two-grid sensitivity check over truss/beam/frame, CST and quad4 refinement. It does not claim an asymptotic convergence rate, large-sliding accuracy, mortar contact, or physical validation.

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.

json · responsecopy
{
  "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.

json · responsecopy
{
  "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.

json · responsecopy
{
  "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.

json · the model objectcopy
{ "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.

3D and recorded-state contract. The Copilot viewer always opens in an orbitable 3D camera; native 2D meshes are shown honestly as midsurfaces at 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.

json · requestcopy
{ "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 }
Long-running imports. STEP/IGES/BREP and uploads over the configured threshold return 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.

bash · real STEP live solvecopy
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.

Scope. This endpoint is a transparent concept demo, not design certification. Always inspect the returned spatial-error estimate; use authenticated /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.

bashcopy
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-probe

The 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.

bashcopy
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.

Do you prefer direct tools? The same functions are available over MCP (fem_solve, fem_modal, fem_buckling, fem_transient) and a Python SDK. See the docs.