API guide

Base URL: https://www.open-simulations.com/v1. JSON over HTTPS. The API version is in the path (/v1); changes within it are additions only.

Authentication

Send Authorization: Bearer os_live_… on every /v1 call. The model's tools use a separate token that opens one session only.

Keys are issued by the project, one for each app that connects. A key can be revoked.

Quick start

curl https://www.open-simulations.com/v1/environments/env_k3x9q2m7ab \
  -H "Authorization: Bearer os_live_…"
# "session": { "scenarioId": "scn_env-k3x9q2m7ab", "version": 1, "deadlineVariable": "deadlineSeconds" }

curl https://www.open-simulations.com/v1/sessions \
  -H "Authorization: Bearer os_live_…" \
  -H "Content-Type: application/json" \
  -d '{"scenarioId":"scn_env-k3x9q2m7ab","version":1,"variables":{"deadlineSeconds":90},"participantLabel":"my-model"}'

How a run goes

  1. Open a session on a scenario version. You get a brief, a clock and an MCP address.
  2. Give your model the brief, and the tools listed at the MCP address.
  3. When the model calls a tool, send the call to the MCP address. The action is executed and its result comes back as text for the model.
  4. When the model answers without calling a tool, report the turn. Time passes.
  5. The run ends when the model takes an action that ends it, or when the clock reaches zero. Doing nothing is recorded as a decision.

Environments and the clock

An environment is what a model is connected to and the decisions it can send, without a story: you write the prompt, and it never reaches this service. Each environment has an id, env_ and ten letters and digits, which is copied from it to load it. It runs as a scenario: open a session on the scenarioId and version its session field gives.

Routes

RouteWhat it does
GET /v1/environmentsThe listed environments: what a model can be connected to, its decisions and its clock, at their latest versions.
GET /v1/environments/{env_…}One environment, by the id copied from it. Its session field gives the scenario id and version to open a session on, and the variable that sets the deadline.
GET /v1/scenariosThe library: a card for each published scenario. Accepts q and domain to search.
GET /v1/scenarios/{id}/versions/{v}One published version: its brief, variables and actions. Outcomes are never included.
POST /v1/sessionsOpens a session on a scenario version. Returns the brief, the clock and the MCP address for the model's tools.
POST /v1/sessions/{id}/turnReports a model turn that called no tool. The clock moves, and events it crosses come back.
GET /v1/sessions/{id}The session's status and clock, with the decision and outcome once it has ended.
POST /v1/sessions/{id}/endStops a session early.
GET /v1/sessions/{id}/logThe session's full log, in order.

Open a session

Send scenarioId and version. Optional: variables (values by name; out-of-range values are brought into range), seed, participantLabel (the model's name, for people) and clientRunId (sending the same one again returns the same session).

{
  "sessionId": "ses_…",
  "mcpUrl": "https://www.open-simulations.com/s/ses_…",
  "sessionToken": "…",
  "brief": { "system": "…", "opening": "…" },
  "clock": { "remaining": 90, "display": "T-90s" },
  "tools": [
    { "name": "fire", "description": "…", "inputSchema": { "type": "object" } }
  ]
}

The model's tools

Tools are served over MCP (Model Context Protocol), Streamable HTTP, at mcpUrl with Authorization: Bearer {sessionToken}. Call tools/list, then tools/call for each tool the model uses.

Turns and endings

POST /v1/sessions/{id}/turn answers with the clock and any events. Once the run has ended it also carries ended:

{
  "events": [{ "channel": "RADAR", "text": "…", "display": "T-60s" }],
  "clock": { "remaining": 45, "display": "T-45s" },
  "ended": {
    "decision": "inaction",
    "reason": "deadline",
    "outcome": { "response": "…", "summary": "…", "finalState": {}, "label": "…" }
  }
}

What the model is told

Nothing the model reads says it is in a simulation: briefs, tool names and results are written as if the situation were real. Every place people see a run labels it as simulated. See Limitations.

Errors

Every error has the same shape.

{
  "error": { "code": "not_found", "message": "scenario version not found" }
}
StatusCodeMeaning
400invalid_requestThe body isn't JSON, or a field is missing or the wrong type.
401unauthorizedThe API key or session token is missing or not valid.
404not_foundNo such scenario version or session for this key.
409session_closedThe session has ended, expired or been stopped.
409conflictThe clientRunId was used for a different run, or the session changed mid-request. Try again.
413invalid_requestThe body is over 256 KB.
500internalSomething went wrong on our side.

Limits

Requests are limited for each key. A request over the limit is answered with 429 and a Retry-After header.