Base URL
https://api.mlip-bench.dev/v1
All responses are static JSON files served with Content-Type: application/json.
Authentication
The API is protected by Cloudflare Access using a service token. Include both headers in every request:
| Header | Value |
|---|---|
Cf-Access-Client-Id |
Your service-token Client ID |
Cf-Access-Client-Secret |
Your service-token Client Secret |
Contact the maintainers to obtain a service token.
Index & discovery
| Endpoint | Description |
|---|---|
/v1/index.json |
Catalog: API version, data version, endpoint listing |
/v1/openapi.json |
OpenAPI 3.1 schema for all endpoints |
/v1/llms.txt |
Plain-text summary for LLM consumption (llms.txt standard) |
curl examples
1 — Fetch the catalog
curl -s \
-H "Cf-Access-Client-Id: $CF_CLIENT_ID" \
-H "Cf-Access-Client-Secret: $CF_CLIENT_SECRET" \
https://api.mlip-bench.dev/v1/index.json | jq .
2 — Fetch a potential record
curl -s \
-H "Cf-Access-Client-Id: $CF_CLIENT_ID" \
-H "Cf-Access-Client-Secret: $CF_CLIENT_SECRET" \
https://api.mlip-bench.dev/v1/potentials/gracefm_GRACE-2L-OAM.json | jq .
3 — Fetch a track leaderboard
curl -s \
-H "Cf-Access-Client-Id: $CF_CLIENT_ID" \
-H "Cf-Access-Client-Secret: $CF_CLIENT_SECRET" \
https://api.mlip-bench.dev/v1/tracks/elastic/leaderboard.json | jq '.entries[:5]'
Python client (mlip_bench)
A typed Python client wraps every endpoint on this page — typed structures and
workflow instances, ase.Atoms geometry, pandas leaderboard/parity DataFrames,
bulk dataset download, and client-side point lookups into the hash-sorted
geometry/corpus stores over plain HTTP Range reads. It lives at
clients/python/ in this repo; the walkthrough below is the full tour, and
clients/python/README.md + examples/quickstart.py mirror it.
Install
Not yet on PyPI — install from a checkout of this repo:
pip install -e clients/python[pandas,datasets]
| Extra | Adds | Needed for |
|---|---|---|
| (base) | httpx, pyarrow, ase |
Client, structures, geometry, workflows, gbseg/corpus, store lookups |
pandas |
pandas>=2 |
Track.leaderboard() / Track.parity() (DataFrames) |
datasets |
fsspec[http] |
Datasets.filesystem() (stream bulk parquet without downloading) |
pymatgen |
pymatgen |
geometry.to_pymatgen() |
Credentials
Client() takes no required arguments; it resolves the same Cloudflare Access
service token documented above, from the first fully-specified source of:
- explicit args —
Client(client_id="...", client_secret="...") - env vars —
MLIP_BENCH_CLIENT_ID/MLIP_BENCH_CLIENT_SECRET ~/.config/mlip-bench.toml—[auth]client_id/client_secret~/.config/mlip-bench.env— shell-styleCF_CLIENT_ID=/CF_CLIENT_SECRET=
With none found it proceeds unauthenticated (emitting a UserWarning; pass
anonymous=True to silence). The secret is never logged, and is redacted from
repr().
Pulling data — a full tour
from mlip_bench import Client
client = Client() # credentials resolved automatically (see above)
Structures — by atoms_hash, mp_id, or a {namespace}:{structure_id}
alias (aliases 302-redirect transparently wherever a hex hash is accepted):
s = client.structure("mp-132") # -> Structure
s.record.element # 'Fe'
s.record.formula # 'Fe'
s.record.workflow_ids # ['elastic:allegro_MP-L', 'relax-full:...', ...]
s.record.geometry_available # True
Geometry — as an ase.Atoms (the input / DFT-reference cell):
atoms = s.geometry() # ase.Atoms
atoms.get_chemical_formula() # 'Fe'
atoms.cell, atoms.positions, atoms.numbers
Workflow instances — one typed record per (workflow, potential):
inst = s.workflow("elastic:allegro_MP-L") # -> WorkflowInstance
inst.status # 'done'
unit = inst.units[0]
unit.outcomes["k_vrh"] # e.g. bulk modulus (GPa)
unit.geometry["mlip_relaxed"].availability # 'served' | 'not_computed' | 'bulk_only' | 'sp_only' | 'not_persisted'
Each unit's geometry is a set of honestly labelled references. Follow a
served one to its ase.Atoms; a non-served one raises rather than hand you a
dangling link:
from mlip_bench import GeometryUnavailable
ref = unit.geometry["mlip_relaxed"]
try:
relaxed = client.fetch_geometry(ref) # ase.Atoms, only if availability == 'served'
except GeometryUnavailable:
print(ref.availability, "—", ref.note) # e.g. 'not_computed — no geometry persisted for this unit'
Most workflows expose a single relaxed cell (ref.href is one URL).
surface-slab and gb-energetics expose several — the relaxed slab and
its reference bulk, several GB cells — so ref.artifacts is a
{name: url} dict and you pass artifact=:
ref = unit.geometry["mlip_relaxed"] # surface-slab unit (one Miller/termination)
ref.artifacts # {'slab_relaxed': ..., 'bulk_relaxed': ...}
slab = client.fetch_geometry(ref, artifact="slab_relaxed") # ase.Atoms — the relaxed slab
bulk = client.fetch_geometry(ref, artifact="bulk_relaxed") # the relaxed reference bulk
The input / initial cell is always unit.geometry["input"] (equivalently
s.geometry()); note that for surfaces this is the bulk seed cell — the
unrelaxed slab is not persisted. Calling fetch_geometry without an artifact
on a multi-artifact ref (or with one on a single-artifact ref) raises
ValueError telling you the available names.
Leaderboards & parity — as pandas DataFrames (needs the pandas extra):
lb = client.track("elastic").leaderboard() # DataFrame indexed by `potential`
lb.head()
client.track("ordering").leaderboard(scheme="sp") # the ordering track is published per-scheme
ASSYST corpus (~7.29M single-point structures) — client.structure(hash)
transparently falls back to the corpus for a corpus-only hash (s.corpus is
then populated, and s.geometry() still works, served from the corpus-geometry
store); or fetch the raw record directly:
rec = client.corpus_record("a3f…64-hex") # dict: {structure, potentials{}, geometry}
Grain-boundary segregation (Worker-served from a hash-sorted store, ~10M instances):
gb = client.gbseg_instance("aa…hash", "GRACE-2L-OAM-L") # -> WorkflowInstance
Bulk datasets — list the catalog, download a parquet (Range-resumable), or stream it without downloading:
cat = client.datasets.list() # {"bulk": [{name, url, size}, ...], "subsets": [...]}
name = cat["bulk"][0]["name"]
path = client.datasets.download(name, dest_dir="/tmp") # Range-resumable
fs = client.datasets.filesystem() # fsspec 'http' fs (needs [datasets])
Direct store point-lookups — resolve one row of any hash-sorted store client-side (manifest → prefix-shard index → one row group → filter by key), over plain HTTP Range reads, with no server-side query route:
atoms = client.query_geometry("geom-elemental-relax", "…::relax-full::…::-::relaxed")
row = client.stores.lookup("wf-gbseg-instances", "…::gbseg-relax::…::bfgs")
Errors
| Exception | Raised when |
|---|---|
NotFoundError |
404 — unknown structure/workflow/alias id (subclass of ApiError) |
ApiError |
any other non-2xx after retries (GET retries 429/500/502/503/504 with backoff) |
GeometryUnavailable |
fetch_geometry(ref) on a non-served ref (not_computed, bulk_only, …) — inspect ref.availability / ref.note first |
ParityNotPublished |
Track.parity() where per-structure parity shards aren't published as API resources here |
DataVersionMismatch |
Client(expect_data_version=…) and the server's /v1/index.json reports a different data_version |
from mlip_bench import (
NotFoundError, ApiError, GeometryUnavailable,
ParityNotPublished, DataVersionMismatch,
)
Property reference — every field the site serves
Everything the website plots is reachable here. The data comes in four families, each with a fixed set of named properties:
- Structure → workflow outcomes — the physical result of running one
potential on one structure (elastic Cᵢⱼ, vacancy formation energy, surface
γ, …). Keyed
(atoms_hash, workflow, potential). - Track leaderboards — the aggregate score of one potential on one
benchmark track, ranked. Keyed
(track, potential). - Potential records — metadata about one potential (architecture, training set, params, links).
- Element aggregates — coverage counts per chemical element.
Scalars only. Every property below is a scalar (or a small fixed vector like the 21 elastic constants). Curve/array data — phonon DOS spectra, band structures, QHA F(T) curves — is not served by this API; it lives as bulk assets behind the
/datapage. See Curve data below.
Family 1 — Structure → workflow outcomes
Record path (static, one file per structure×potential):
/v1/structures/{atoms_hash}/workflows/{workflow}:{potential}.json
Each record has status, a method block, provenance, and a units[] list.
Every unit carries an outcomes{} dict (the properties) and a geometry{}
block of honestly-labelled cell references. Multi-unit workflows (surfaces,
vacancies, GB) split one record into several units, discriminated by a key{}.
The complete outcome catalog, workflow by workflow:
Stability ordering — ordering-sp · 1 unit · live
| Property | Type | Unit / meaning |
|---|---|---|
spearman |
num | Spearman ρ of predicted vs DFT polymorph energy ranking (−1…1) |
ground_state_correct |
bool | did the potential put the true ground state first |
dE_mae |
num | mean abs. error of relative polymorph energies (eV/atom) |
n |
int | number of polymorphs ranked |
status |
str | done / error / … |
Cell relaxation — relax-hydro (hydrostatic) & relax-full (full cell+ions) · 1 unit · live
| Property | Type | Unit / meaning |
|---|---|---|
final_energy_ev_atom |
num | relaxed energy (eV/atom) |
volume_ang3 |
num | relaxed cell volume (ų) |
pressure_gpa |
num | residual pressure (GPa) |
converged |
bool | optimizer reached fmax |
n_steps |
int | optimizer steps |
status |
str | run status |
(relax-fixedcell — legacy fixed-cell variant — carries the same fields minus
volume_ang3/pressure_gpa; superseded, not currently emitted.)
Elastic tensor — elastic · 1 unit · live
| Property | Type | Unit / meaning |
|---|---|---|
c11…c66 |
num | the 21 independent stiffness constants (GPa), Voigt notation |
k_vrh |
num | Voigt–Reuss–Hill bulk modulus (GPa) |
g_vrh |
num | Voigt–Reuss–Hill shear modulus (GPa) |
mechanically_stable |
bool | Born stability criteria satisfied |
k_pct_err / g_pct_err |
num | % error of K/G vs DFT reference |
status |
str | run status |
Vacancy formation — vacancy-site · 1 unit per inequivalent site (key.site_index) · live
| Property | Type | Unit / meaning |
|---|---|---|
formation_energy |
num | vacancy formation energy (eV) |
wyckoff_symbol |
str | Wyckoff label of the removed site |
site_element |
str | element removed |
deviation_from_median |
num | Eᶠ − median over potentials (eV) — MLIP-spread reference |
lit_dev_ev / dft_dev_ev |
num | deviation from literature / DFT (eV) |
converged |
bool | ionic relaxation converged |
status |
str | run status |
Surface energy — surface-slab · 1 unit per (Miller, termination) (key.miller_index, key.termination_index) · live
| Property | Type | Unit / meaning |
|---|---|---|
gamma_j_per_m2 |
num | surface energy γ (J/m²) |
slab_converged |
bool | slab relaxation converged |
slab_n_atoms |
int | atoms in the slab |
status |
str | run status |
(surface-bulk — the reference-bulk relaxation feeding γ — is registered
(bulk_energy_ev_atom, bulk_converged, bulk_n_atoms) but not separately
emitted; its bulk cell is reachable via any slab unit's bulk_relaxed geometry.)
GB energetics — gb-energetics · 1 unit per GB (key.gb_id) · live
| Property | Type | Unit / meaning |
|---|---|---|
gb_energy |
num | grain-boundary energy (J/m²) |
excess_volume |
num | GB excess volume (ų) |
surface_energy |
num | free-surface energy (J/m²) |
wsep_rigid / wsep_relaxed |
num | work of separation, rigid / relaxed (J/m²) |
cleavage_profile |
str | cleavage-curve descriptor |
status |
str | run status |
GB segregation — gbseg-relax · 1 unit per optimizer (key.algo = BFGS/FIRE) · Worker-served
Reached via the Worker route (not a static file) — see the query recipes below.
| Property | Type | Unit / meaning |
|---|---|---|
n_steps |
int | optimizer steps |
fmax_target |
num | force convergence target (eV/Å) |
error_msg / error_class |
str | failure detail, if any |
status |
str | run status |
(The relaxation runtime — runtime_ms, gpu_mem_mb, device — rides in the
record's separate runtime{} block. The segregation energy itself, E_seg, is
a leaderboard-level scored quantity — see Family 2, track segregation.)
ASSYST corpus single-point — sp · ~7.29M structures · Worker-served
Reached via /v1/query/corpus/{atoms_hash}.
| Property | Type | Unit / meaning |
|---|---|---|
energy_err_per_atom |
num | MLIP−DFT energy error (eV/atom) |
energy_err_per_atom_corrected_global |
num | after global linear-reference correction (eV/atom) |
energy_err_per_atom_corrected_elemental |
num | after per-element correction (eV/atom) |
stress_rmse |
num | stress RMSE (GPa) |
max_force |
num | max force component (eV/Å) |
status |
str | eval status |
Not yet emitted (registered, gated until their instance tables land):
phonon-fc2 (omega_max_thz, has_imaginary, frac_imaginary,
most_negative_freq_thz), qha (alpha_300k_per_k, b_300k_gpa,
cv_300k_j_per_mol_k, qha_converged), and per-structure wsep
(wsep_rigid_j_per_m2, wsep_relaxed_j_per_m2) whose scalar rows have no store
export today (the cleavage leaderboard below is served, the per-structure
records are not). The scalar phonon/QHA summaries are downloadable in bulk
from /data.
Geometry references. Each unit's geometry{} gives input, dft_relaxed,
and mlip_relaxed cells, each tagged with an honest availability label:
| Label | Meaning |
|---|---|
served |
fetch it now (href present) |
bulk_only |
exists, but only inside a bulk parquet (not per-id addressable) |
not_persisted |
was computed, never saved |
not_computed |
never produced by this workflow |
sp_only |
this potential only ran single-point, never relaxed |
How to query one outcome
# curl — the elastic tensor of mp-20 under uma_M-1p1
curl -s -H "Cf-Access-Client-Id: $CF_CLIENT_ID" -H "Cf-Access-Client-Secret: $CF_CLIENT_SECRET" \
"https://api.mlip-bench.dev/v1/structures/$(
curl -s -H "Cf-Access-Client-Id: $CF_CLIENT_ID" -H "Cf-Access-Client-Secret: $CF_CLIENT_SECRET" \
-o /dev/null -w '%{redirect_url}' https://api.mlip-bench.dev/v1/structures/mp-20 | sed -E 's#.*/structures/##')/workflows/elastic:uma_M-1p1.json" \
| jq '.units[0].outcomes | {k_vrh, g_vrh, mechanically_stable}'
from mlip_bench import Client
c = Client()
# scalar outcomes, any workflow, by mp_id / atoms_hash / alias:
inst = c.structure("mp-20").workflow("elastic:uma_M-1p1")
o = inst.units[0].outcomes
o["k_vrh"], o["g_vrh"], o["mechanically_stable"] # 122.4, 73.5, True
# per-site / per-facet workflows expose one unit each — read the key:
vac = c.structure("mp-20").workflow("vacancy-site:GRACE-2L-OAM-L")
for u in vac.units:
print(u.key["site_index"], u.outcomes["site_element"], u.outcomes["formation_energy"])
# Worker-served corpus & gbseg:
c.corpus_record("a3f…64hex") # sp outcomes for a corpus structure
c.gbseg_instance("…hash", "GRACE-2L-OAM-L") # gbseg-relax instance
Discover which (workflow:potential) pairs a structure has before fetching:
s = c.structure("mp-20")
s.record.tracks_present # ['elastic', 'lattice', 'phonon', ...]
s.record.workflow_ids # ['elastic:uma_M-1p1', 'relax-full:...', ...]
Family 2 — Track leaderboard metrics
/v1/tracks/{track}/leaderboard.json → {track, scheme, rows[]}; each row is
one potential. The metric columns differ per track — the full set:
| Track | Row metrics (besides potential, n) |
|---|---|
pes |
E_rmse_corrected, E_mae_corrected, E_rmse_corrected_elemental, E_mae_corrected_elemental, F_rmse_comp, F_mae_comp, F_radial_rmse, F_radial_mae, F_ang_med_deg, S_rmse_voigt |
ordering |
mean_spearman, gs_hit_rate, median_dE_mae — per scheme (sp/hydro/full) |
elastic |
median_abs_k_pct, median_abs_g_pct, frac_mech_stable |
lattice |
median_edge_mape, median_vol_pct, frac_edge_1pct, median_strain |
surface |
surface_mae, surface_rmse, surface_mbe (J/m²) |
vacancy |
vacancy_consensus_mae, vacancy_consensus_mbe, vacancy_dft_mae, vacancy_lit_mae (eV) |
phonon |
phonon_omega_consensus_mae, phonon_imag_frac, phonon_qha_bulk_mae, phonon_qha_bulk_mbe, phonon_mp_omega_mae |
segregation |
Eseg_rmse, Eseg_mae, Eseg_spearman, n_best_from_bfgs, n_best_from_fire, algo_used |
cleavage |
Wsep_rmse, Wsep_mae, Wsep_spearman, n_best_from_bfgs, n_best_from_fire, algo_used |
The ordering track publishes per-scheme sub-leaderboards instead of a
single leaderboard.json:
/v1/tracks/ordering/leaderboard-{sp,hydro,full}.json.
curl -s -H "Cf-Access-Client-Id: $CF_CLIENT_ID" -H "Cf-Access-Client-Secret: $CF_CLIENT_SECRET" \
https://api.mlip-bench.dev/v1/tracks/phonon/leaderboard.json | jq '.rows[:3]'
lb = c.track("elastic").leaderboard() # DataFrame indexed by `potential`
lb.sort_values("median_abs_k_pct").head()
c.track("ordering").leaderboard(scheme="sp") # ordering is per-scheme
Family 3 — Potential records
/v1/potentials.json (all) and /v1/potentials/{id}.json (one). Fields:
| Property | Type | Meaning |
|---|---|---|
id |
str | canonical potential id (used everywhere as :{potential}) |
display_name |
str | human label |
architecture |
str | model family |
checkpoint |
str | exact weights / release used |
version |
str | version string |
params |
str | parameter count |
training_set / dataset_group |
str | training data + its group |
repo_url / paper_url |
str | source + reference |
matbench_discovery |
str|null | Matbench-Discovery entry name, if any |
affiliation |
obj | {slug, name, short, url, logo} |
flag |
str|null | caveat flag (e.g. known defect), if any |
perf |
obj|null | technical-performance metrics, if measured |
c.potentials() # list of all records
c.potential("gracefm_GRACE-2L-OAM") # one record
Family 4 — Element aggregates
/v1/elements.json → {elements: [{element, n_potentials}, …]} — how many
potentials have data for each chemical element.
curl -s -H "Cf-Access-Client-Id: $CF_CLIENT_ID" -H "Cf-Access-Client-Secret: $CF_CLIENT_SECRET" \
https://api.mlip-bench.dev/v1/elements.json | jq '.elements[] | select(.element=="Fe")'
Curve data (not in the API)
Phonon DOS spectra, band structures, and QHA F(T)/α(T)/Cv(T) curves
are the one thing the website renders that this API does not serve per-id —
only the scalar summaries above (omega_max_thz, alpha_300k_per_k, …) are
exposed. The full curves are downloadable in bulk from the /data page
(phonon parquet bundles, per-structure band shards, the raw FC2 archive). A
per-material curve endpoint (client.phonon_dos(id, potential) → (freq, dos))
is designed but not yet built.
Endpoint reference
Terse index of every route (properties for each are catalogued above).
Potentials
| Endpoint | Description |
|---|---|
/v1/potentials.json |
Full list of all potentials with metadata |
/v1/potentials/{id}.json |
Single potential record (Family 3 fields) |
Structures → workflows
| Endpoint | Description |
|---|---|
/v1/structures/{atoms_hash}.json |
Structure summary (tracks_present, workflow_ids, geometry_available) |
/v1/structures/{atoms_hash}/workflows.json |
All workflow instances for a structure |
/v1/structures/{atoms_hash}/workflows/{wf}:{pot}.json |
One instance record (Family 1 outcomes + geometry) |
/v1/structures/{atoms_hash}/geometry.{json,cif,poscar} |
Input/reference cell |
/v1/query/corpus/{atoms_hash} |
Worker: one ASSYST-corpus sp record |
/v1/query/instance/gbseg/{atoms_hash}/{potential} |
Worker: one gbseg-relax instance |
/v1/query/geom/{store}/{key} |
Worker: one geometry row from any hash-sorted store |
A non-hex id (mp-132, elemental:59, …) 302-redirects to its atoms_hash.
Tracks
Available track ids: pes, ordering, elastic, lattice, surface, vacancy, phonon, segregation, cleavage, technical-performance.
| Endpoint | Description |
|---|---|
/v1/tracks/{track}/leaderboard.json |
Per-track ranked leaderboard (Family 2 metrics) |
/v1/tracks/ordering/leaderboard-{mode}.json |
Ordering sub-leaderboards: sp, hydro, full |
Elements
| Endpoint | Description |
|---|---|
/v1/elements.json |
Per-element aggregate metrics across all potentials |
Datasets
| Endpoint | Description |
|---|---|
/v1/datasets.json |
Catalog of bulk DFT parquet files and any declared partitioned subsets |
OPTIMADE
An additive OPTIMADE v1.2 Tier-1 facade lives alongside the native API above, for tooling
that speaks the OPTIMADE standard (e.g. optimade-client,
aggregators, the official validator). It is honest about scope: fully conformant where we
can be, and a proper OPTIMADE 501 error object — never a silent guess — everywhere our
serverless architecture can't yet do more. The native API above stays the primary product;
OPTIMADE _mlipbench_-prefixed fields link back into it for anything OPTIMADE has no room
to express (workflow outcomes — elastic/vacancy/phonon/etc. results).
Base URL
https://api.mlip-bench.dev/optimade/v1
(versions is the one endpoint the OPTIMADE spec pins at the unversioned base:
https://api.mlip-bench.dev/optimade/versions.)
Endpoints
| Endpoint | Description |
|---|---|
/optimade/v1/info |
Entry-point info: api_version, available_api_versions, available_endpoints, is_index: false |
/optimade/v1/info/structures |
Property definitions for the structures entry type (incl. _mlipbench_* custom fields) |
/optimade/v1/links |
Related-database links (empty — no external OPTIMADE providers registered; that's a Tier-2 decision) |
/optimade/versions |
Supported major API versions, text/csv; header=present (version\n1\n) — unversioned base, per spec |
/optimade/v1/structures |
Collection listing — page 1 unfiltered (full entries), or dynamic pages with ?filter=/?page_limit=/?page_offset=/?response_fields= (elemental set only, reduced 4-field subset, see below) |
/optimade/v1/structures/{id} |
Single entry. {id} is atoms_hash (sha256 hex). Covers all 834 elemental structures and the full ~7.29M-structure ASSYST corpus (fetched on demand from the corpus geometry store) |
Note on page 1 shape: The static unfiltered page at /optimade/v1/structures (no query string) returns full OPTIMADE entries. Dynamic pages (any query parameters: filter=, page_offset>0, page_limit, etc.) return the reduced 4-field index subset; clients walking links.next will observe this shape change. Full OPTIMADE attributes are always available via the per-id route /optimade/v1/structures/{id}.
Single-entry coverage is total: every corpus/elemental structure the native API knows by
atoms_hash is reachable here too, one entry at a time. The collection route
(structures?filter=...), by contrast, only ever indexes the 834-structure elemental set —
filtering the full corpus is out of scope for Tier-1 (see Non-goals in the design doc).
A non-hex id (an mp_id like mp-132, or a namespaced alias) 302-redirects to the canonical
atoms_hash path, same alias resolution the native API uses. An uppercase-hex id
canonicalizes (302) to lowercase.
Supported filter subset (collection route only)
Tier-1 supports exactly these atoms, combined with AND only (no OR, NOT, parentheses,
or range comparisons):
| Filter | Example |
|---|---|
id="..." |
filter=id="a3f...64hexchars" |
nelements=N |
filter=nelements=2 |
elements HAS "X" |
filter=elements HAS "Fe" |
elements HAS ALL "X","Y" |
filter=elements HAS ALL "Fe","Ni" |
elements HAS ANY "X","Y" |
filter=elements HAS ANY "Fe","Ni" |
chemical_formula_reduced="..." |
filter=chemical_formula_reduced="Fe2O3" |
chemical_formula_anonymous="..." |
filter=chemical_formula_anonymous="AB2" |
| AND combinations of the above | filter=nelements=2 AND elements HAS "Fe" |
response_fields= on the collection route is similarly limited to the four attributes the
listing actually indexes (elements, nelements, chemical_formula_reduced,
chemical_formula_anonymous) — fetch /structures/{id} for the full mandatory attribute set
(positions, cell, species, ...). An empty response_fields= is treated as absent (default
four-field set), not "zero fields."
The 501 policy (honest Tier-1)
Anything recognized as valid OPTIMADE filter grammar but outside the subset above — OR,
NOT, comparison operators (!=, <, <=, >, >=), CONTAINS/STARTS/ENDS/LENGTH,
IS KNOWN/IS UNKNOWN, HAS ONLY, parenthesized/grouped expressions, or a real OPTIMADE
property we simply don't index for filtering (lattice_vectors, species, ...) — returns
501 with an OPTIMADE error object (title: "UnsupportedFilterFeature"), not a silent
partial match or a fabricated result:
{
"errors": [{ "status": "501", "title": "UnsupportedFilterFeature", "detail": "..." }],
"meta": { "api_version": "1.2.0", "provider": { "prefix": "mlipbench", ... } }
}
A filter that doesn't parse as valid OPTIMADE grammar at all (unterminated quote, stray
character, right property/wrong shape) returns 400 (title: "FilterSyntaxError") instead —
501 is reserved for constructs we recognize but don't (yet) implement, 400 for ones that
are simply malformed. Filtering the ASSYST corpus collection (as opposed to a single-entry
lookup by id) is always 501 — collection filtering is elemental-only in Tier-1. The
references/files OPTIMADE entry types are not implemented at all — any request under
/optimade/v1/references or /optimade/v1/files gets the same 404 OPTIMADE error object
every unknown route gets.
Auth
Same gate as the native API: Cloudflare Access via a service token, identical
Cf-Access-Client-Id/Cf-Access-Client-Secret headers (see Authentication
above). There is no separate, more-open auth tier for OPTIMADE — public-read exposure and
registration with providers.optimade.org are recorded
Tier-2 decisions, not taken here.
Workflow outcomes: use the native API
OPTIMADE has no vocabulary for benchmark results (elastic constants, vacancy formation
energies, phonon spectra, ...). Every entry carries _mlipbench_workflows_url, a link back
into the native structure→workflow API (/v1/structures/{id}/workflows.json for the 834
elemental structures, /v1/query/corpus/{atoms_hash} for ASSYST-corpus structures) — follow
it for anything beyond geometry and chemistry. The Python client
above wraps that structure→workflow API (typed WorkflowInstances, honest geometry labels);
see /v1/openapi.json for the full schema of
every workflow/query route.
Regenerating the API
The static files under api_dist/api/v1/ are generated locally and pushed to R2:
# Regenerate all JSON endpoints (routes heavy Python builders via srun)
npm run build:api
# Push to R2 (requires rclone configured with r2:benchmark-assets)
PUSH_API=1 bash tools/push_to_r2.sh
The light-weight node-only step (skips Python builders) is also available:
npm run build:api:node