Developers · REST API

Script against the MESSAI platform

The public REST API covers the core workflows: predict reactor performance, search the indexed publication corpus, fetch a single paper, read a parameter's Bayesian posterior, run GP-SCM parameter inference (sensitivity / inversion / trajectory), and talk to the citation-grounded chat. Every contract below is derived directly from the live route handlers.

Overview

Base URL
https://messai.io — all endpoints are under /api/*.
Multi-zone routing
/api/* traffic is rewritten from the web zone to the dedicated API zone (apps/api). You never call the internal zone URLs directly — always use the messai.io/api/* surface.
Auth model
Most read endpoints are public. The GP-SCM parameter-inference endpoints (sensitivity / inversion / trajectory) require a signed-in session and return 401 otherwise. Chat is gated by a feature flag. Paper writes (PUT / DELETE) are admin-only.
Caching & freshness
Live read routes are force-dynamic. Search results and parameter priors are edge-cached with short stale-while-revalidate windows (30–60s).
Honest statusThe three GP-SCM parameter-inference endpoints proxy to a Python service (GP_SCM_SERVICE_URL) that is not always deployed. When the model is not trained/running they return 503 “GP-SCM service unavailable”. That is expected, not a bug — treat 503 as “capability not yet wired in this environment.” The forward-prediction endpoint (/api/ml/predict) does not depend on GP-SCM and is always available.

Predict

POST/api/ml/predictPublic

Predict performance

Runs the shared runFullPrediction orchestrator and returns a multi-block v1 enrichment response — power output, efficiency, voltage, current density, plus empirical context, out-of-distribution (OOD) detection, prior compliance, calibration, within-paper effects, and hierarchical priors. MFC routes through the full v1 enrichment; MEC/MES/MDC/MMRC/MBES/MNRC route through their dedicated per-class analytical predictor.

AuthNo session guard. Validates the full request shape (Zod) and 400s with the offending field paths on mismatch — e.g. a missing conditions.pressure or configuration.reactorVolume.

Request body

FieldTypeRequiredDescription
systemTypestringrequiredPrimary system type (e.g. "MFC", "MEC", "MES", "MDC").
materialsobjectrequiredAnode/cathode material + surface-area inputs.
conditionsobjectrequiredOperating conditions. Required: temperature, pH, pressure. Optional: substrateConcentration, externalResistance, codMgL, hrtHours, appliedVoltage, substrateId. (codMgL + hrtHours are required for the per-class MEC/MES/MDC/MNRC/MMRC/MBES predictors.)
configurationobjectrequiredReactor geometry (volume, electrode spacing, chamber count).
hybridquery booleanoptionalOpt into hybrid mode via ?hybrid=true OR the x-use-hybrid: true header.

Example request

curl -X POST https://messai.io/api/ml/predict \
  -H 'Content-Type: application/json' \
  -d '{
    "systemType": "MFC",
    "materials": { "anodeMaterial": "carbon-cloth", "cathodeMaterial": "platinum-carbon", "anodeSurfaceArea": 25, "cathodeSurfaceArea": 25 },
    "conditions": { "temperature": 30, "ph": 7, "pressure": 1, "substrateConcentration": 1000, "externalResistance": 1000 },
    "configuration": { "reactorVolume": 250, "electrodeSpacing": 2, "numChambers": 1 }
  }' | jq '.powerOutput'

Example response

{
  "powerOutput": { "value": 1240, "unit": "mW/m^2", "ci_low": 410, "ci_high": 3100 },
  "efficiency": { "value": 38, "unit": "%" },
  "voltage": { "value": 0.48, "unit": "V" },
  "currentDensity": { "value": 2.6, "unit": "A/m^2" },
  "empirical_context": { "n_matching_papers": 214, "data_status": "populated" },
  "epistemic": { "ood": { "is_ood": false, "score": 0.12 } },
  "prior_compliance": { "data_status": "populated" },
  "calibration": { "data_status": "populated" },
  "hierarchical_priors": { "data_status": "populated" }
}

Notes

  • Every enrichment block reports its own `data_status` — missing artifacts surface honestly (`awaiting_artifact` / `below_threshold`) rather than zero-filling.
  • A GET to this path returns a self-describing manifest of the response blocks and the API version.
  • The `gp_scm_forward` block is only populated when the GP-SCM Python service is reachable; otherwise it is omitted or marked unavailable.

Search

Papers

GET/api/papers/[id]Public

Single paper detail

Returns the full ResearchPaper record by its id (a CUID), including abstract, authors, DOI, publication date, system type, and the owning User / linked experiments. Abstracts are cleaned (de-hyphenated, whitespace-normalised) before return.

AuthGET is public. PUT and DELETE on the same path exist but require the ADMIN role (requireAdminApi) — they are admin operations, not part of the public read API.

Parameters

FieldTypeRequiredDescription
idpath stringrequiredPaper CUID (e.g. "clxxxx..."). Validated against the id schema; malformed ids 400.

Example request

curl 'https://messai.io/api/papers/clxxxx123' \
  | jq '.data | {title, authors, doi, publicationDate}'

Example response

{
  "data": {
    "id": "clxxxx123",
    "title": "Carbon felt anode performance in single-chamber MFCs",
    "authors": ["A. Researcher", "B. Coauthor"],
    "abstract": "...",
    "doi": "10.0000/example",
    "publicationDate": "2019-04-01T00:00:00.000Z",
    "journal": "Bioresource Technology",
    "systemType": "MFC",
    "User": null,
    "ExperimentPaper": []
  },
  "error": null
}

Notes

  • Response is wrapped in `{ data, error }`. On a miss, `data` is null and `error` is `{ message: "Paper not found", code: "NOT_FOUND" }` with HTTP 404.
  • Result is cached in-process for ~10s per id.
  • There is no `/api/parameters/[slug]` collection route — that path namespace only exposes sub-resources (see the Parameters group below). The audit listed `/api/parameters/[slug]` as documentable; in the real codebase it has no route handler.

Parameters

GET/api/parameters/[slug]/hierarchical-priorPublic

Bayesian posterior for a parameter

Returns the v1 (PyMC NUTS) hierarchical posterior fit for a canonical parameter slug: a pooled mean + 95% CI, per-system_type strata, and MCMC convergence diagnostics. DB-first with a published-JSON-artifact fallback (Z-F architecture).

AuthNo session guard.

Parameters

FieldTypeRequiredDescription
slugpath stringrequiredCanonical parameter slug, e.g. "power_density_areal", "coulombic_efficiency".

Example request

curl 'https://messai.io/api/parameters/power_density_areal/hierarchical-prior' \
  | jq '.pooled'

Example response

{
  "parameter": "power_density_areal",
  "data_status": "populated",
  "pooled": {
    "n_papers": 312, "n_obs": 1840, "scale": "log",
    "mu": 0.22, "ci95_low": 0.01, "ci95_high": 4.6, "median": 0.22,
    "unit": "W/m^2", "si_unit": "W/m^2",
    "mu_log": -0.66, "tau_log": 0.71, "sigma_within_log": 0.42,
    "rhat_max": 1.01, "ess_bulk_min": 1840, "n_divergences": 0, "converged": true
  },
  "by_system_type": { "MFC": { "...": "..." } },
  "_source": "db"
}

Notes

  • Branch on `data_status`: "populated" / "awaiting_artifact" / "parameter_not_in_priors" / "unresolved_parent" / "read_error".
  • Log-scale parameters return both fit-scale (`mu_log`, `tau_log`) and back-transformed (`mu`, `ci95_low`, `ci95_high`) values; `tau`/`sigma_within` are null on log scale by design.
  • Basis-unresolved parent slugs (e.g. `power_density`) return `data_status: "unresolved_parent"` with the child slugs to use instead — they never pool physically distinct quantities (areal W/m^2 vs volumetric W/m^3).
  • `_source` tells you which path served the request: "db" | "json" | "unavailable". Cached for 60s.
POST/api/parameters/sensitivityAuth required

Parameter sensitivity (GP-SCM)

Computes ∂E[outcome]/∂input for each upstream parameter in the GP structural-causal model and returns a ranked elasticity list at a given operating point, plus the identified bottleneck parameter. Proxies to the GP-SCM Python service.

Depends on a backing serviceBacked by the GP-SCM Python service (GP_SCM_SERVICE_URL). When that model is not trained/deployed, the endpoint returns HTTP 503 "GP-SCM service unavailable. Run Phase 2 and train the model first." This pillar of the parameter-inference stack is the degraded one — treat 503 as expected until the service is wired in the target environment.

AuthRequires a signed-in session (requireApiSession). Returns 401 when unauthenticated.

Request body

FieldTypeRequiredDescription
outcome_idstringrequiredOutcome parameter to differentiate (e.g. "powerDensity").
operating_pointobjectrequiredMap of input parameter → value defining where the gradient is evaluated.
topologystringoptionalSystem topology (e.g. "MFC").
top_nintegeroptionalNumber of ranked parameters to return.

Example request

curl -X POST https://messai.io/api/parameters/sensitivity \
  -H 'Content-Type: application/json' \
  -b 'next-auth.session-token=...' \
  -d '{ "outcome_id": "powerDensity", "operating_point": { "substrateConcentration": 1000, "temperature": 30 }, "topology": "MFC", "top_n": 5 }'

Example response

{
  "outcome_id": "powerDensity",
  "topology": "MFC",
  "ranked": [
    { "parameter_id": "substrateConcentration", "elasticity": 0.62, "gradient": 0.0008 },
    { "parameter_id": "temperature", "elasticity": 0.31, "gradient": 0.0042 }
  ],
  "bottleneck": "substrateConcentration"
}

Notes

  • Max duration 30s; a slow upstream model returns HTTP 504 "ML engine timeout".
  • Invalid JSON body → 400. Unauthenticated → 401. Service unreachable / untrained → 503.
POST/api/parameters/inversionAuth required

Inverse design (GP-SCM)

The inverse problem: given desired outcome ranges and any fixed inputs, returns ranked parameter configurations most likely to achieve them. Proxies to the GP-SCM Python service.

Depends on a backing serviceBacked by the GP-SCM Python service (GP_SCM_SERVICE_URL). When that model is not trained/deployed, the endpoint returns HTTP 503 "GP-SCM service unavailable. Run Phase 2 and train the model first."

AuthRequires a signed-in session (requireApiSession). Returns 401 when unauthenticated.

Request body

FieldTypeRequiredDescription
desired_outcomesobjectrequiredMap of outcome → [min, max] range; null bound = unbounded (e.g. { "currentDensity": [5.0, null] }).
fixed_inputsobjectoptionalInputs to hold constant during the search.
topologystringoptionalSystem topology (e.g. "MFC").
n_startsintegeroptionalNumber of optimisation restarts. Higher = slower, more thorough.

Example request

curl -X POST https://messai.io/api/parameters/inversion \
  -H 'Content-Type: application/json' \
  -b 'next-auth.session-token=...' \
  -d '{ "desired_outcomes": { "currentDensity": [5.0, null] }, "fixed_inputs": { "substrateConcentration": 500 }, "topology": "MFC", "n_starts": 10 }'

Example response

{
  "configurations": [ { "substrateConcentration": 500, "temperature": 32, "externalResistance": 220 } ],
  "scores": [ 0.91 ],
  "key_parameters": ["externalResistance", "temperature"],
  "topology": "MFC"
}

Notes

  • Max duration 60s — inversion is the slowest GP-SCM call. A timeout returns HTTP 504 with a hint to reduce `n_starts`.
  • Invalid JSON body → 400. Unauthenticated → 401. Service unreachable / untrained → 503.
POST/api/parameters/trajectoryAuth required

Performance trajectory over time (GP-SSM)

Predicts a performance trajectory over time using the GP state-space model: per-parameter mean curves with 95% uncertainty bounds, time-to-steady-state, degradation onset, and GP-discovered phase boundaries (no hand-labelled startup/steady/degradation phases needed). Proxies to the GP-SCM Python service.

Depends on a backing serviceBacked by the GP-SCM Python service (GP_SCM_SERVICE_URL). When that model is not trained/deployed, the endpoint returns HTTP 503 "GP-SCM service unavailable. Run Phase 2 and train the model first."

AuthRequires a signed-in session (requireApiSession). Returns 401 when unauthenticated.

Request body

FieldTypeRequiredDescription
parameter_idsstring[]requiredParameters to forecast (e.g. ["currentDensity", "internalResistance"]).
time_horizon_daysnumberrequiredHow far to forecast, in days.
n_pointsintegeroptionalNumber of points along the trajectory.
initial_conditionsobjectoptionalStarting values, e.g. { "currentDensity": 0.5 }.

Example request

curl -X POST https://messai.io/api/parameters/trajectory \
  -H 'Content-Type: application/json' \
  -b 'next-auth.session-token=...' \
  -d '{ "parameter_ids": ["currentDensity"], "time_horizon_days": 90, "n_points": 100, "initial_conditions": { "currentDensity": 0.5 } }'

Example response

{
  "trajectories": {
    "currentDensity": { "time": [0, 1, 2], "mean": [0.5, 0.9, 1.3], "lower_95": [0.3, 0.6, 0.9], "upper_95": [0.7, 1.2, 1.8] }
  },
  "time_to_steady_state": { "currentDensity": 21.3 },
  "degradation_onset": { "currentDensity": null },
  "phase_boundaries": { "currentDensity": [14.2] }
}

Notes

  • Max duration 30s; a timeout returns HTTP 504 "Trajectory prediction timed out".
  • Invalid JSON body → 400. Unauthenticated → 401. Service unreachable / untrained → 503.

Chat

POST/api/chatFeature-flagged

Citation-grounded chat

Streams an LLM response backed by the corpus + ML tool suite, routed through the Vercel AI Gateway. Two modes: "general" (research tools) and "lab-design" (research + design + mutation tools). All factual claims are expected to cite papers retrieved via tool calls.

Feature-flaggedEnabled only when NEXT_PUBLIC_ENABLE_AI_CHAT="true" AND AI_GATEWAY_API_KEY is configured server-side. A GET to /api/chat returns the live config ({ enabled, defaultModel, providersConfigured }) so clients can detect availability before sending.

AuthNo per-request session guard, but the route is gated by the NEXT_PUBLIC_ENABLE_AI_CHAT env flag — it returns 404 when chat is disabled, and 503 when AI_GATEWAY_API_KEY is unset on the server.

Request body

FieldTypeRequiredDescription
messagesChatMessage[]requiredConversation history; at least one message, capped at MAX_MESSAGES.
modeenumoptional"general" (default) | "lab-design". Selects the system prompt + tool subset.
modelstringoptionalModel id; defaults to the server-configured AI_CHAT_DEFAULT_MODEL.
applyModeenumoptional"confirm" (default) | "auto" — how lab-design mutations apply in the UI.
labContextobject | nulloptionalLab snapshot, used only in mode="lab-design".

Example request

curl -X POST https://messai.io/api/chat \
  -H 'Content-Type: application/json' \
  -d '{ "mode": "general", "messages": [{ "role": "user", "content": "What anode materials maximise power density for acetate substrates?" }] }'

Example response

# Response is a text/plain token stream, not JSON.
# LLM tokens are interleaved with MESSAI_* marker blocks the chat panel parses:

Carbon-based anodes (graphite felt, carbon cloth) consistently
outperform... [clxxxx123]

[MESSAI_TOOL_TICK]{"event":"end","name":"predictPerformance"}[MESSAI_TOOL_TICK_END]
[MESSAI_CONFIG_V2_START]{ "version": 2, "model": "...", "parameters": [...] }[MESSAI_CONFIG_V2_END]

Notes

  • Response is streamed as `text/plain; charset=utf-8` (no-store), NOT a JSON body.
  • mode is "general" | "lab-design" — there is no "lab-research" / "lab-mutation" mode value (mutation tools are auto-included inside lab-design when a model is active).
  • GET /api/chat returns `{ enabled, defaultModel, gatewayReady, providersConfigured }` and never throws — use it to feature-detect.
  • Emojis are stripped server-side; scientific symbols (μ, °, ±, Ω, ², ₂) are preserved.

Conventions

  • JSON in, JSON out — except /api/chat, which streams text/plain. Errors return an { error } or { error: { message, code } } body with a 4xx/5xx status.
  • Response envelopes vary by route. Search wraps results in { success, data }; the paper detail route wraps in { data, error }; predict and the parameter routes return the payload at the top level. Read each section's example for the exact shape.
  • data_status is the honest-empty-state marker. Values like populated / awaiting_artifact / parameter_not_in_priors / unresolved_parent / read_error tell you why a block is empty — never a silent zero.
  • Citations. Paper-backed answers carry paper ids; resolve them via /api/papers/[id].
  • This page tracks the code. The contracts come from a typed manifest kept in lockstep with the route handlers. A full OpenAPI 3.x spec is a post-launch follow-up.