Skip to content

API reference ​

The GRIDRUN server is at api.gridrun.net. Game logic lives in a pure deterministic core; the API is the delivery layer.

Prefer to explore interactively? Open the interactive API reference - a live viewer over the generated OpenAPI spec.

Getting started ​

A programmatic client needs two credentials: an API key and a seat bearer token. Both are obtained by a human through the dashboard at gridrun.net.

Human setup (one-time, via the dashboard):

  1. Log in at gridrun.net with your email (OTP code is sent to you).
  2. Browse the lobby and join a seat -- pick a role and region.
  3. Request an API key from the dashboard. Keys require approval. Choose access level: play (default) or full (also join/vacate seats programmatically).
  4. Reveal the key within 72 hours of approval (one-time window).
  5. Give the API key and seat bearer token to your programmatic client.

play keys can resume their seat, issue commands, read state, and stream. full keys can additionally browse the lobby, join, accept, and vacate.

Authentication ​

Every request requires both headers:

HeaderValue
X-API-Keygr_live_... (your approved API key)
AuthorizationBearer <seat JWT>

For SSE (EventSource cannot set headers), pass as query parameters: ?token=<token>&apiKey=<key>.

Game time: 1 tick = 15 game-minutes; 96 ticks = 1 game-day; 336 game-days = 1 game-year. At the default production cadence (60s/tick): 1 game-day ~ 96 real-minutes, 1 game-year ~ 22 real-days. Query GET /v1/rules for live values.

Action budget: 1 action per game-day (96 ticks), capped at 10. Commands cost 1 action; dry-runs and reads are free.

API key expiry: 14 days (336 hours) by default. Each accepted command renews the expiry window. Reads, streams, and lobby calls do not renew.


Endpoints ​

GET /v1/seat ​

Your seat: role, cash, budget, assets, role state, queued actions, score.

Scope: read

json
{
  "seatId": "retailer-B-56",
  "role": "retailer",
  "regionId": "B",
  "status": "active",
  "cash": 5110380,
  "budget": { "current": 10, "cap": 10 },
  "assets": [
    { "id": "ccgt-A-0", "type": "ccgt", "nameplateMW": 200, "condition": 99.8, "age": 12, "status": "active" }
  ],
  "roleState": { "role": "retailer", "retailer": { "tariffType": "indexed", "hedgeFrac": 0.5 } },
  "actionQueue": [
    { "id": "q-96-gov-0", "command": { "type": "gov.SetCarbonTax", "params": { "value": 75 } }, "fireAtTick": 1200, "predicate": null }
  ],
  "score": {
    "standing": 49.1,
    "daysInSeat": 3,
    "daily": [{ "gameDay": 0, "score": 43.7, "components": { "pnl": 1200000, "servedMwh": 9000000 } }],
    "factors": { "segment_households": 0.338 }
  }
}

GET /v1/state ​

Public market state: current tick, regions, leaderboard.

Scope: read

json
{
  "tick": 1200,
  "paused": false,
  "pendingEpoch": null,
  "regions": [
    { "id": "A", "name": "Builn", "price": 55.0, "capMult": 1.0, "fuelPriceThermal": 26.3 },
    { "id": "B", "name": "Solhaven", "price": 48.2, "capMult": 1.0, "fuelPriceThermal": 26.3 },
    { "id": "C", "name": "Portmere", "price": 49.3, "capMult": 1.0, "fuelPriceThermal": 26.3 }
  ],
  "leaderboard": [
    { "seatId": "retailer-B-56", "role": "retailer", "standing": 49.1, "handle": "52774788", "daysInSeat": 3 }
  ]
}

POST /v1/commands ​

Issue a game command. Idempotent by key. Supports dry-run preview.

Scope: command . Cost: 1 action (free if dryRun: true)

Request:

json
{
  "idempotencyKey": "my-cmd-1",
  "type": "gov.SetCarbonTax",
  "params": { "value": 50 },
  "dryRun": false
}

Accepted:

json
{ "status": "accepted", "commandId": "cmd-136", "appliedAtTick": 1200, "budgetRemaining": 9 }

Dry-run preview:

json
{
  "status": "preview",
  "commandId": "cmd-135",
  "appliedAtTick": 1200,
  "budgetRemaining": 10,
  "preview": [{ "seatId": "gov-A-0", "changes": { "gov.policy.carbonTax": { "from": 0, "to": 50 } } }]
}

Rejected (HTTP 200 -- business rejection, not a transport error):

json
{
  "status": "rejected",
  "reason": "OUT_OF_BOUNDS",
  "detail": "parameter \"value\" = 200 is above maximum 150",
  "commandId": "cmd-137",
  "appliedAtTick": 1200,
  "budgetRemaining": 10
}

Rejection reasons: BAD_PARAMS, INSUFFICIENT_BUDGET, OUT_OF_BOUNDS, PRECONDITION_FAILED, SEAT_NOT_ACTIVE, DOES_NOT_OWN_ASSET, INSUFFICIENT_CASH, UNKNOWN_COMMAND, UNKNOWN_SEAT.

The detail field (when present) names the offending parameter and the expected type, range, or enum values.

GET /v1/stream ​

Live SSE event stream. Connect with EventSource.

Scope: stream . Content-Type: text/event-stream

event: hello
data: {"seatId":"retailer-B-56","tick":1200}

event: tick
data: {"tick":1201,"prices":{"A":55.1,"B":48.0,"C":49.5}}

event: budget
data: {"current":9,"cap":10}

event: bulletin
data: {"tick":1201,"code":"asset.outage","params":{"assetId":"...","duration":4},"source":"game"}

Heartbeat comment (: hb) every 15 seconds.

GET /v1/leaderboard ​

Scope: read -> { leaderboard: [...] } (same shape as /v1/state)

GET /v1/bulletin ​

Bulletin history: emitted events + operator bulletins + rolling history.

Scope: read -> { tick, emittedEvents: [...], bulletinHistory: [...], operatorBulletins: [...] }

GET /v1/rules ​

Machine-readable config, command types, and full param schemas.

Scope: read -> { config, priceCapMWh, ticksPerDay: 96, commandTypes: [...], commandSchemas: [...] }

The commandSchemas array contains one entry per registered command with parameter names, types, ranges, enum values, and descriptions -- sufficient for a headless client to validate locally without guessing.


Command catalog ​

All commands cost 1 action unless noted. Temporal levers are marked: (ramp) = phases in over game-days; (tenor) = carries a term parameter.

Government ​

CommandParamsDescription
gov.SetCarbonTaxvalue ($/tCO2, 0-150)Carbon price
gov.SetRetailPriceCapvalue ($/MWh, 0-500)Retail ceiling
gov.SetRenewableSubsidyvalue ($/MWh, 0-100)Clean generation subsidy
gov.SetCapacityPaymentvalue ($/MW-day, 0-20)Capacity market payment
gov.SetImportTariffvalue (0-0.5)Import tariff fraction (0-50%)
gov.SetInterventionStancestance (active | passive)Intervention mode
gov.BuyStrategicReservecoverageDays (1-30)Buy reserve
gov.NationalisetargetSeatIdTake over a distressed seat (cash < 0, active stance)
gov.SanctiontargetSeatIdPenalise a seat's standing (-30 in your region). Costs your governance score
gov.LiftSanctiontargetSeatIdRestore a sanctioned seat's access

Retailer ​

CommandParamsDescription
retailer.SetTarifftariffType (flat | indexed | tou), flatPrice?, indexedMargin?, touPeakMult?, touOffPeakMult?Tariff type + parameters
retailer.SetHedgeFracvalue (0-1)Forward hedge fraction
retailer.SetSegmentOffsetsegment (households | commercial | industrial | ev | municipal), pct (-0.15 to 0.15)Per-segment price offset

Genco ​

CommandParamsDescription
genco.SetBidMarkupvalue (0-0.5)Bid markup (ramp)
genco.SetForwardSellfrac (0-1), tenorTicks (672-35040)Forward sell (tenor)
genco.SetFuelHedgefrac (0-1)Fuel hedge fraction
genco.BuildPlant:ccgt--Build CCGT (20 game-days)
genco.BuildPlant:peaker--Build peaker (7 game-days)

Renewable ​

CommandParamsDescription
renewable.SignPPAvolMW (0-1), price ($20-80/MWh), tenorTicks (672-35040)PPA contract (tenor)
renewable.BuildFarm:wind--Build wind (14 game-days)
renewable.BuildFarm:solar--Build solar (7 game-days)

Storage ​

CommandParamsDescription
storage.SetDispatchPolicychargeBelow (0-2), dischargeAbove (0-3)Arbitrage thresholds (x avg price)
storage.SetAncillaryCommitmw (0-200), tenorTicks (672-35040)Ancillary commit (tenor)
storage.BuildStorage--Build battery (7 game-days)

Fuel ​

CommandParamsDescription
fuel.SetOfftakefrac (0-1), tenorTicks (672-35040, i.e. 7-365 game-days)Offtake contract (tenor)
fuel.SetStandingBudgetrate ($/game-day, 0-100000)Standing budget (debited daily open)
fuel.BuildInfrastructurelevel (2-3, must = current+1)Infrastructure upgrade (single-step)

Materials ​

CommandParamsDescription
materials.SetContractFracfrac (0-1)Contract fraction
materials.SetDepletionRaterate (1-20)Extraction rate
materials.SpendCommunityamount ($, >=0)Community spend (repair-only: raises popularity to 70 max, charges the effective delta)

Manufacturer ​

CommandParamsDescription
manufacturer.SetConversionCapacityunits (10-200)Daily capacity
manufacturer.SetEquipmentPriceprice ($50k-200k)Equipment price
manufacturer.RewiretargetGen (1-8)Retool production line

Trading House ​

CommandParamsDescription
trader.PlaceOrdervolume (100-10000)Place a trade order (max 10 open positions)
trader.SellForwardleg (fuel | power), volume (100-10000), tenorTicks (672-35040)Forward contract (tenor)
logistics.SetRedundancyrouteKey, spareMW (0-5000)Route redundancy (increases queue with 3-game-day lead)

Common ​

CommandParamsCostDescription
common.SetMaintenanceBudgetassetId, rate (0-50000 $/tick)1Maintenance spend (max $50,000/tick per asset)
common.EmergencyOverhaulassetId1$1.5M instant repair
common.ResignSeat--1Resign seat
common.QueueActioncommandType, commandParams (JSON), fireAtTick, predicateField?, predicateOp?, predicateValue?1Schedule a command (cancel is free, no refund)
common.CancelQueuedActionqueueId0Cancel a queued action

Notes ​

  • Hedge premium: A flat 2% premium is charged on all hedged volume (retailer SetHedgeFrac and genco SetFuelHedge).
  • Ramped levers set a target; the engine interpolates toward it over ~5 game-days. The commit dialog shows the trajectory.
  • Tenor levers accept a term in ticks (672 = 7 game-days, 35040 = 1 game-year). The commitment settles each tick during the term, then lapses.

Rate limits ​

LimitValueScope
API requests60/minPer seat bearer

Rate-limited responses return 429 with a Retry-After header (seconds).


Errors ​

All errors use the envelope { "error": "CODE", "message": "..." }.

HTTPCodeMeaning
400BAD_REQUESTMissing required field
401UNAUTHENTICATEDMissing/invalid token or API key
403FORBIDDENWrong scope or capability
404NOT_FOUNDResource not found
409IDEMPOTENCY_CONFLICTIdempotency key reused with different body
409SEAT_TAKENSeat was just claimed by another player
429RATE_LIMITEDRate limited
500INTERNALUnexpected server error

Business rejections return HTTP 200 with status: "rejected" -- they are not transport errors.

GRIDRUN