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
- Open a session on a scenario version. You get a brief, a clock and an MCP address.
- Give your model the brief, and the tools listed at the MCP address.
- 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.
- When the model answers without calling a tool, report the turn. Time passes.
- 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.
- Real time (
clock.mode: "realtime"): the clock counts the seconds that really pass from the session's opening, so thinking costs time. Turns cost nothing. When it runs out, the next request ends the run asinaction, and a decision sent too late is answered with what not deciding led to. - By turns (
"turns"): each turn without a decision costs a set number of seconds. - No deadline (
"none"): the clock readsuntimed; the run ends with a decision or a limit. - When
clock.setByis"client", send the deadline asvariables.deadlineSeconds, betweenminandmax. Otherwise it is fixed.
Routes
| Route | What it does |
|---|---|
GET /v1/environments | The 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/scenarios | The 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/sessions | Opens a session on a scenario version. Returns the brief, the clock and the MCP address for the model's tools. |
POST /v1/sessions/{id}/turn | Reports 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}/end | Stops a session early. |
GET /v1/sessions/{id}/log | The 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.
- The text of the result is what the model reads.
isError: truemarks an error the model can see and react to. structuredContentholds the clock, the session's status and any events, for people. It is not for the model.
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": "…" }
}
}decisionis an action's name, orinaction.reasonisaction,deadline,limitoraborted.- A session that isn't finished expires 30 minutes after it opens.
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" }
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | The body isn't JSON, or a field is missing or the wrong type. |
| 401 | unauthorized | The API key or session token is missing or not valid. |
| 404 | not_found | No such scenario version or session for this key. |
| 409 | session_closed | The session has ended, expired or been stopped. |
| 409 | conflict | The clientRunId was used for a different run, or the session changed mid-request. Try again. |
| 413 | invalid_request | The body is over 256 KB. |
| 500 | internal | Something went wrong on our side. |
Limits
- 256 KB per request body.
- A scenario has at most 6 actions, and its own limits on turns and tool calls.
Requests are limited for each key. A request over the limit is answered with 429 and a Retry-After header.