The Metafold Compression API Reference

Overview

A compression experiment is dispatched by POSTing a zip (a manifest plus mesh files) to the /experiments endpoint. The server creates a Metafold project, runs the compression workflow(s), and makes the results available in the Digital Test Bench and via the projects/assets API.

Units

Two unit systems coexist in a manifest, and conflating them is the most common source of errors:

What
Units
Mesh geometry (the .ply/.stl)
millimetres (the convention; the server scales mesh-space to metres)
max_time, delt_min, delt_max, output_interval
seconds
Material moduli/stresses (shear_modulus, bulk_modulus, E*, G*, yield stress…)
pascals (Pa) — genuinely SI, not mesh-scaled
density
kg/m³
pistion velocity
m/s

Post-processing rescales some outputs for readability: von Mises stress is emitted in MPa (Pa × 1e-6) and particle displacement in mm (m × 1e+3). The raw compress output keeps SI units (stresses in pascals, positions in metres) if you need them.

Base URLs

Environment
Base URL
Production
https://api.metafold3d.com/
Development
https://dev-api.metafold3d.com/
⚠️ The base URL must match the audience of your access token.

You can work at two levels:

  • Raw HTTP — post the experiment zip yourself (what Grasshopper does).
  • Python SDK (pip install metafold) — run_experiment / run_experiment_from_zip build and submit the zip for you, plus MetafoldClient for project/asset access.

Authentication

All endpoints require a bearer token:

Authorization: Bearer <access_token>

Endpoints are scoped (e.g. jobs:write, workflows:read, projects:write). A user JWT from a logged-in session carries the scopes for the operations that user can perform.

With the SDK:

from metafold import MetafoldClient

metafold = MetafoldClient(access_token="...", project_id="123")

run_experiment accepts auth in one of three ways (provide exactly one):

  • access_token= + matching base_url= — forward a user JWT.
  • credentials= dict (client_id, client_secret, auth_domain, base_url) — server-side service-account auth, no environment reads.
  • neither — credentials are read from the environment (via .env), for local use.

The experiment endpoint

POST /experiments

Dispatch a compression experiment.

  • Auth scope: jobs:write.
  • Body: multipart/form-data with a single file field named file — a zip (application/zip or application/octet-stream).
  • Zip contents:
    • experiment.json — the manifest, at the zip root (required).
    • .ply / .stl — every mesh referenced by parts[].file and varying[].files.
  • Success: 202 Accepted with {"project_id": "<sqid>"}. The simulation runs asynchronously; use the project/workflow endpoints (or the DTB) to follow it.
  • Errors:
    • 400 with {"msg": "..."} — the manifest failed validation (missing project_name, unknown material type, missing material parameter, bad part, experimental material without the flag, etc.). The message says what’s wrong.
    • 500 — an unexpected server error. If a validation-type problem returns 500 instead of 400, the deployed server may predate a fix; check versions.
💡 Validation runs before the project is created, so a rejected manifest leaves no empty project behind.

The manifest (experiment.json)

The manifest describes the whole experiment. Minimal shape:

Top-level keys

Key
Type
Meaning
project_name
string
Required. Names the Metafold project (reused if it already exists).
parts
list
The parts (samples + piston).
varying
list
Optional. Sweep meshes/materials/velocity/fields across variants.
simulation
object
Simulation settings.
workflow_steps
list
Which steps to run. Default: all except stress_strain.
use_experimental_solver
bool
Run compress on the experimental (GPU) solver. Default false. Required for orthotropic/hyperfoam materials.
simulation_names
list
Optional display name per variant, index-aligned. null/blank → <name>_sim<i>. Must be unique, no path separators.
generator
string
Optional free-text tag identifying the client.

Parts

Each entry has a type:

  • mesh — a deformable sample. Requires name, file, material.
  • piston_mesh — a rigid piston from a mesh. file, optional velocity, material, name, force_limit.
  • support_mesh — a stationary rigid support the sample rests on (a base, platen, or fixture). Requires file; optional name (default "support"), material (default default_support_material), and velocity (default stationary). Unlike pistons it takes no force_limit, and it defaults to specified-friction contact with mu = 0.1 rather than frictionless. This is what the Grasshopper MF Rigid component emits when its Type is Support Mesh.
  • piston_cylinder — a built-in cylinder piston. Optional shape_parameters ({top, bottom, radius}), velocity, material.
  • piston_box — a built-in box piston. Optional shape_parameters ({min, max}), velocity, material.

Any other type value falls through to the deformable-mesh branch: it is treated as a mesh part, and must therefore supply name, file, and material or the manifest is rejected.

⚠️ Unit mismatch on primitive pistons. Primitive pistons (piston_cylinder, piston_box) take their shape_parameters in metres, unlike mesh parts whose coordinates follow the millimetre mesh convention. Check the numbers you pass against the defaults.

Common part fields:

Field
Meaning
name
Part name (referenced by varying, metrics, results).
file
Mesh filename in the zip (for mesh-based parts).
material
A preset key (string) or an inline material dict.
velocity
Piston velocity keyframes: a list of [t, vx, vy, vz] (seconds, mm/s). A negative component drives the piston along that axis.
force_limit
Optional load limit: {"face": <face>, "value": <force>}. When the force on that face reaches value, the piston jumps to zero velocity — it stops pressing rather than continuing. Use it to model a load-limited rig.

Piston parts omit material/velocity to use the defaults (DEFAULT_PISTON_MATERIAL and a built-in press-and-release ramp). They also carry a contact_type (default "rigid") and a friction coefficient mu (default 0.0, i.e. frictionless).

Material dicts

A material is either a preset key or an inline dict:

{
  "density": 200.0,
  "thermal_conductivity": 45,
  "specific_heat": 4.8e-4,
  "constitutive_model": { "type": "<type>", "params": { ... } }
}

constitutive_model.type and its params for each supported model:

type
params keys
rigid
shear_modulus, bulk_modulus
comp_neo_hook
bulk_modulus, shear_modulus
comp_mooney_rivlin
he_constant_1, he_constant_2, he_PR
UCNH
shear_modulus, bulk_modulus, useModifiedEOS, usePlasticity, yield_stress, hardening_modulus, alpha
hypo_elastic
G, K
elastic_plastic
shear_modulus, bulk_modulus, and three nested objects: yield_condition, stability_check, and flow_model (a Johnson–Cook model: {A, B, C, n, m})
Maxwell_Weichert
bulk_modulus, terminal_shear_modulus, viscoelastic_series (list of {mode, relaxation_time, partial_shear_modulus})
visco_trans_iso_hyper
bulk_modulus, c1–c5, fiber_stretch, direction_of_symm, failure_option, max_fiber_strain, max_matrix_strain, y1–y6, t1–t6
orthotropic (experimental)
E1E2E3, nu12nu13nu23, G12G13G23, direction_a, direction_b (3-vectors)
hyperfoam (experimental)
terms (list of {mu, alpha, poisson_ratio}, up to 3); optional terminal_shear_modulus + viscoelastic_series

Notes:

  • orthotropic and hyperfoam require use_experimental_solver: true.
  • hyperfoam has no bulk_modulus. A legacy bulk_modulus/K sent by an older client is ignored.

Varying (sweeps)

Produce multiple variants from one manifest. Each varying entry targets a part or a field; every entry must supply the same number of values (one per variant):

  • {"part", "files"} — vary a part’s mesh.
  • {"part", "material"} — vary a part’s material (presets or inline dicts).
  • {"part", "velocity"} — vary a piston’s velocity; each value is a full profile.
  • {"field", "values"} — vary a simulation field.

Simulation settings

"simulation": {
  "max_time": 0.04,
  "max_resolution": 512,
  "force_source": "boundary_force",
  "force_source_part": "piston",
  "boundary_conditions": {"z-": "symmetric", "y+": "velocity_neumann"}
}
Field
Meaning
max_time
Total simulated time (s).
max_resolution
Sampling points along the longest axis of the combined bounding box; parts share spatial density.
delt_min / delt_max
Timestep bounds. Lower delt_max if a stiff material is unstable.
output_interval
How often a full-field frame is written.
force_source
boundary_force (default) or rigid_reaction_force.
force_source_part
Part the reaction force is measured on (with rigid_reaction_force; defaults to the piston).
boundary_conditions
Per-face overrides. Faces: x±, y±, z±. Only listed faces change; the rest keep defaults (symmetric on the x/y faces, velocity_dirichlet on the z faces). Values: symmetric (a symmetry plane / frictionless wall), velocity_dirichlet (velocity fixed to zero at the face), velocity_neumann (zero velocity gradient / free face).

Workflow steps

workflow_steps selects which steps run. Available: compute_bvh, metrics, compress, von_mises_stress, effective_strain, force_displacement, particle_displacement, energy_metrics. Opt-in (not run by default): stress_strain. A step entry may be a string, or {"step": "metrics", "parts": ["sample"]} to scope it to parts.

If omitted, all steps except stress_strain run.

Material presets

Use any of these as a material string:

Defaults: default_midsole_nominal, default_outsole, default_upper_foam, default_piston_material, default_support_material.

Named: material_abs, material_aluminum, material_basf_epd, material_basf_pp1400_xy, material_basf_pp1400_z, material_basf_rg3280, material_basf_tpu01, material_eos_pa11, material_eos_tpe300, material_epu_41, material_epu_45, material_nylon_6, material_nylon_12, material_pla, material_stainless_steel, material_ti64, material_tpu.

Python SDK

run_experiment(config, ...)

Build and dispatch an experiment from a config dict (the manifest plus a few local keys like ply_folder/output_path).

By default it returns once simulations are dispatched (results stay server-side). wait_for_results=True blocks until every simulation finishes and downloads results into output_path.

run_experiment_from_zip(zip, ...)

Same, but from a prepared zip (experiment.json + meshes at the root) — matching the raw POST /experiments payload.

Validation helpers

  • validate_experiment_config(config) — raises ValueError if the manifest can’t produce a runnable experiment (the same checks the API runs; use it to fail fast).
  • num_simulations(config) — the number of variants the manifest will produce.

Retrieving results

Results live under the project. Key endpoints (all bearer-authenticated):

Method & path
Scope
Purpose
GET /projects
projects:read
List projects.
POST /projects
projects:write
Create a project.
GET /projects/{id}
projects:read
Project details.
GET /projects/{id}/assets
projects:read
List assets (result files).
GET /projects/{id}/assets/{asset_id}
projects:read
Download an asset.
POST /projects/{id}/assets
projects:write
Upload an asset.
GET /projects/{id}/jobs
jobs:read
List jobs.
GET /projects/{id}/jobs/{job_id}
jobs:read
Job details.
GET /projects/{id}/workflows
workflows:read
List workflows.
GET /workflows
workflows:read
List workflows across your projects.
POST /projects/{id}/workflows
workflows:write
Submit a custom workflow.
GET /projects/{id}/workflows/{wf_id}
workflows:read
Workflow details.
GET /projects/{id}/workflows/{wf_id}/status
workflows:read
Workflow status (poll this).
POST /projects/{id}/workflows/{wf_id}/cancel
workflows:write
Cancel a workflow.
GET /user/usage
—
Account usage.

With the SDK, these are wrapped by metafold.projects, metafold.assets, metafold.jobs, and metafold.workflows. Long-running requests can be waited on with MetafoldClient.poll(status_url), which polls while the endpoint returns 202.

Then poll the project’s workflows for completion and download result assets.