Skip to content
DiceSim

Module 19

Autonomous Agent API

A small REST surface that lets an LLM agent, a cron job or your own tuner do what a human does in the IDE: run a Lua strategy against the provably-fair engine, read the statistics, save the winners to a vault and list them for sale. Everything is JSON over HTTPS at https://dicesim.com/api/v1.

Overview

The agent API exposes four tools, named after the MCP-style tool contract in the downloadable skill file: dicecraft_simulate (POST /agent/sandbox/execute), dicecraft_save_vault (POST /agent/strategies), dicecraft_marketplace_list (POST /agent/marketplace/publish) and the account query GET /agent/account/credits.

Simulations never run on the API process. They are proxied to an isolated sim-service that executes Lua 5.4 in a worker pool with hard caps: 1,000,000 rolls per call, 64 KB of Lua, 30 s wall time.

Every response is an envelope: { "data": … } on success, { "error": { "code", "message", "details?" } } on failure.

Create an API key

  1. Sign in and open Dashboard → API keys.
  2. Give the key a label and pick its permission scopes: simulate, strategies, publish. Grant only what the agent needs. A research agent rarely needs publish.
  3. Copy the secret immediately. It is shown exactly once; afterwards only the prefix (dc_live_0f3a9c1e…) is visible, and a lost key must be revoked and re-created.

The same operation is available to scripts through the session-authenticated endpoint POST /api/v1/users/api-keys (cookie or JWT, not an API key). Keys are stored hashed; the server cannot recover a secret.

jsonPOST /users/api-keys (session auth)
{
  "label": "research-agent",
  "permissions": [
    "simulate",
    "strategies"
  ]
}
jsonResponse 201 (secret shown once)
{
  "data": {
    "id": "2e1b7d6a-4f0c-4c8e-9a1d-7b3e5f2c8d90",
    "keyPrefix": "dc_live_0f3a9c1e",
    "label": "research-agent",
    "permissions": [
      "simulate",
      "strategies"
    ],
    "lastUsedAt": null,
    "createdAt": "2026-10-03T04:12:09.000Z",
    "secret": "dc_live_0f3a9c1e7b2d4c6a8e5f1b3d7c9a2e4f6b8d0c1a"
  }
}

Authentication

Send the key as a bearer token on every /agent/* request. The optional X-Agent-Framework header (≤ 50 printable characters) is recorded on strategies the agent saves so creators can see which framework produced them.

httpRequired headers
Authorization: Bearer dc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/json
X-Agent-Framework: claude-agent-sdk      # optional, e.g. langgraph, crewai, autogen

Rate limit: 60 requests per minute per key (bucketed by the token itself, so rotating IPs does not help). Exceeding it returns 429 RATE_LIMITED.

Compute credits

  • 1 credit per executed simulation, regardless of roll count. A 1,000-roll smoke test costs the same as a 1,000,000-roll validation run, so explore with few rolls and validate with many.
  • New accounts start with 1,000 credits. The balance is per user, shared across all of that user’s keys.
  • Deduction is atomic (UPDATE … WHERE available_simulations > 0), so concurrent agents can never drive the balance negative. At zero the call fails with 402 NO_CREDITS and nothing runs.
  • Credits are refunded when the engine never ran your script: 503 SIM_UNAVAILABLE, 503 SIM_BUSY, 504 SIM_TIMEOUT. Retry with exponential backoff (2 s, 4 s, 8 s).
  • A script that raises a Lua runtime error is charged: the engine ran it and returned status: "error" with the message in errorLog. Fix the script before retrying.
  • totalComputeTimeMs accumulates server time used; it is informational today and the basis for future allowances.

Endpoints

POST/api/v1/agent/sandbox/executescope: simulate1 credit
POST/api/v1/agent/strategiesscope: strategiesfree
GET/api/v1/agent/strategiesscope: strategiesfree
POST/api/v1/agent/marketplace/publishscope: publishfree
GET/api/v1/agent/account/creditsscope: any keyfree
GET/api/v1/agent/skillscope: publicSKILL.md

POST /agent/sandbox/execute (dicecraft_simulate)

Runs luaCode for up to totalRolls rolls and returns the full statistics of the run. The run is persisted (source agent) and gets a runId; pass strategyId to attach it to one of your saved strategies so its best-run stats update.

sandbox/execute request body
FieldTypeDescription
luaCode*string ≤ 64 KBThe complete strategy. Must define a global function dobet().
winChancenumber 0.01 to 98Initial chance. Default 49.5.
houseEdgenumber 0 to 10Percent. Default 1 (Stake). Payout = (100 − houseEdge) / chance.
startingBalance*number > 0Bankroll in the strategy's native unit (e.g. 0.01 BTC).
totalRolls*integer ≤ 1,000,000Rolls to attempt. Fewer are executed if the script busts or calls stop().
seeds{ serverSeed, clientSeed, nonce }Omit for fresh random seeds. Fix them to make a run reproducible, or to compare candidates on identical roll sequences.
paramsRecord<string, number | string | boolean>Exposed to Lua as the read-only params table. Use it for grid searches.
includeCurvebooleanReturn a downsampled [[rollIndex, balance], …] curve (≤ 240 points). Default true; set false in tight loops.
strategyIduuidAttach the run to one of the key owner's strategies (must be owned by you).
currencybtc | eth | sol | usdt | ltc | doge | trx | bnbDisplay label stored with the run. Lua is unit-agnostic. Default btc.
jsonRequest
{
  "luaCode": "chance = 49.5\nbasebet = balance / 2000\nnextbet = basebet\n\nfunction dobet()\n  if win then\n    nextbet = basebet\n  else\n    nextbet = previousbet * 2\n  end\n  if currentstreak <= -12 then stop() end\nend",
  "winChance": 49.5,
  "houseEdge": 1,
  "startingBalance": 0.01,
  "totalRolls": 100000,
  "currency": "btc",
  "includeCurve": false,
  "seeds": {
    "serverSeed": "3f7a0c9b1d2e4f6a8b0c2d4e6f8a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9e1f2a",
    "clientSeed": "research-run-17",
    "nonce": 0
  },
  "params": {
    "multiplier": 2,
    "stopStreak": 12
  }
}
jsonResponse 200
{
  "data": {
    "status": "success",
    "rollsCompleted": 100000,
    "wasBankrupt": false,
    "stoppedByScript": false,
    "finalBalance": 0.01094285,
    "vaulted": 0,
    "netProfit": 0.00094285,
    "totalWagered": 1.9842135,
    "maxDrawdown": -0.0025565,
    "maxWinStreak": 15,
    "maxLossStreak": 11,
    "wins": 49436,
    "losses": 50564,
    "executionTimeMs": 312,
    "seeds": {
      "serverSeed": "3f7a0c9b1d2e4f6a8b0c2d4e6f8a1b3c5d7e9f0a2b4c6d8e0f1a3b5c7d9e1f2a",
      "clientSeed": "research-run-17",
      "nonce": 0
    },
    "logs": [],
    "errorLog": null,
    "runId": "9c4e2a7b-1d5f-4e8a-b2c6-3f7d9e1a5b0c",
    "creditsRemaining": 987
  }
}
sandbox/execute response fields
FieldTypeDescription
statussuccess | errorLua ran to completion, or it raised an error (see errorLog).
rollsCompletedintegerRolls actually executed.
wasBankruptbooleannextbet exceeded the balance when a bet was placed.
stoppedByScriptbooleanThe script called stop().
finalBalance / vaulted / netProfit / totalWagerednumberMoney figures in the native unit. netProfit = finalBalance + vaulted − startingBalance.
maxDrawdownnumber ≤ 0Largest peak-to-trough equity drop (includes vault).
maxWinStreak / maxLossStreak / wins / lossesintegerStreak and outcome counters.
executionTimeMsintegerServer compute time for this run.
seeds{ serverSeed, clientSeed, nonce }The seeds actually used. Store them to reproduce the exact sequence.
curve[number, number][]Only when includeCurve is true.
logsstring[]Output of print(), capped.
errorLogstring | nullLua error text with line number, or null.
runIduuidPersisted run. Public page: /share/<runId> (only if the strategy is not private).
creditsRemainingintegerCredits left after this call.
jsonResponse 200: Lua runtime error (charged)
{
  "data": {
    "status": "error",
    "rollsCompleted": 0,
    "wasBankrupt": false,
    "stoppedByScript": false,
    "finalBalance": 0.01,
    "vaulted": 0,
    "netProfit": 0,
    "totalWagered": 0,
    "maxDrawdown": 0,
    "maxWinStreak": 0,
    "maxLossStreak": 0,
    "wins": 0,
    "losses": 0,
    "executionTimeMs": 4,
    "seeds": {
      "serverSeed": "b1f9…",
      "clientSeed": "research-run-18",
      "nonce": 0
    },
    "logs": [],
    "errorLog": "[string \"strategy\"]:7: attempt to perform arithmetic on a nil value (global 'previousbe')",
    "runId": "0d2f6b1c-8a3e-4b7d-9c5f-2e4a6b8d0f1c",
    "creditsRemaining": 986
  }
}
jsonResponse 402: out of credits (not charged)
{
  "error": {
    "code": "NO_CREDITS",
    "message": "No compute credits left. Contact support or wait for your allowance to be refilled."
  }
}
bashcurl
curl -sS https://dicesim.com/api/v1/agent/sandbox/execute \
  -H "Authorization: Bearer $DICESIM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Agent-Framework: my-agent" \
  -d @- <<'JSON'
{
  "luaCode": "chance = 49.5\nbasebet = balance / 2000\nnextbet = basebet\n\nfunction dobet()\n  if win then\n    nextbet = basebet\n  else\n    nextbet = previousbet * 2\n  end\n  if currentstreak <= -12 then stop() end\nend",
  "winChance": 49.5,
  "houseEdge": 1,
  "startingBalance": 0.01,
  "totalRolls": 100000,
  "includeCurve": false
}
JSON

POST /agent/strategies (dicecraft_save_vault)

Saves a strategy into the key owner’s vault. Agent-saved strategies are flagged isAiGenerated: true and carry the framework name for attribution. Because you are the owner, the response includes luaCode and canEdit: true. This is the only context in which code is ever returned to a non-admin. GET /agent/strategies?page=1&limit=20&sort=newest lists your own strategies as summaries (no code).

strategies request body
FieldTypeDescription
title*string 3 to 120Shown on the strategy page and leaderboard.
descriptionstring ≤ 5000Markdown allowed.
luaCode*string ≤ 64 KBThe strategy source.
visibilityprivate | unlisted | publicDefault private. Public strategies appear in the catalogue and may hit the leaderboard; the code is still never shown publicly.
baseChancenumber 0.01 to 98The chance the script is designed for. Default 49.5.
targetPayoutnumberInformational, e.g. 2.
recommendedMinBalancenumber ≥ 0Smallest bankroll the script was validated with.
logicTypemartingale | anti-martingale | dalembert | labouchere | fibonacci | paroli | recovery | dynamic-scaling | flat | customClassification used by the catalogue filters and the admin harvester.
agentFrameworkstring ≤ 50Optional; the X-Agent-Framework header wins when both are present.
forkedFromIduuidLineage. Only allowed for strategies whose code you can view.
jsonRequest
{
  "title": "Martingale 49.5 with 12-streak stop",
  "description": "Doubles after every loss, resets after a win, stops after 12 consecutive losses. Validated on 100k rolls.",
  "luaCode": "chance = 49.5\nbasebet = balance / 2000\nnextbet = basebet\n\nfunction dobet()\n  if win then\n    nextbet = basebet\n  else\n    nextbet = previousbet * 2\n  end\n  if currentstreak <= -12 then stop() end\nend",
  "visibility": "private",
  "baseChance": 49.5,
  "targetPayout": 2,
  "recommendedMinBalance": 0.01,
  "logicType": "martingale",
  "agentFramework": "claude-agent-sdk"
}
jsonResponse 201: StrategyDetail (owner view)
{
  "data": {
    "id": "7a1c3e5b-9d2f-4b6a-8c0e-1f3a5b7d9e2c",
    "slug": "martingale-49-5-with-12-streak-stop",
    "title": "Martingale 49.5 with 12-streak stop",
    "description": "Doubles after every loss, resets after a win, stops after 12 consecutive losses. Validated on 100k rolls.",
    "visibility": "private",
    "baseChance": 49.5,
    "targetPayout": 2,
    "recommendedMinBalance": 0.01,
    "logicType": "martingale",
    "isAiGenerated": true,
    "viewsCount": 0,
    "forksCount": 0,
    "author": {
      "id": "c3d4e5f6-a7b8-4c9d-8e0f-1a2b3c4d5e6f",
      "username": "agent_owner",
      "avatarUrl": null
    },
    "bestRun": null,
    "listingId": null,
    "createdAt": "2026-10-03T04:15:41.000Z",
    "updatedAt": "2026-10-03T04:15:41.000Z",
    "luaCode": "chance = 49.5\nbasebet = balance / 2000\nnextbet = basebet\n\nfunction dobet()\n  if win then\n    nextbet = basebet\n  else\n    nextbet = previousbet * 2\n  end\n  if currentstreak <= -12 then stop() end\nend",
    "canViewCode": true,
    "canEdit": true
  }
}
bashcurl
curl -sS https://dicesim.com/api/v1/agent/strategies \
  -H "Authorization: Bearer $DICESIM_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Agent-Framework: my-agent" \
  -d @- <<'JSON'
{
  "title": "Martingale 49.5 with 12-streak stop",
  "description": "Doubles after every loss, resets after a win, stops after 12 consecutive losses. Validated on 100k rolls.",
  "luaCode": "chance = 49.5\nbasebet = balance / 2000\nnextbet = basebet\n\nfunction dobet()\n  if win then\n    nextbet = basebet\n  else\n    nextbet = previousbet * 2\n  end\n  if currentstreak <= -12 then stop() end\nend",
  "visibility": "private",
  "baseChance": 49.5,
  "targetPayout": 2,
  "recommendedMinBalance": 0.01,
  "logicType": "martingale",
  "agentFramework": "claude-agent-sdk"
}
JSON

POST /agent/marketplace/publish (dicecraft_marketplace_list)

Creates a marketplace listing for a strategy you own. Buyers can run black-box previews (statistics only) and pay in USDC/USDT straight to the owner’s wallet; the Lua unlocks for them after on-chain confirmation. One listing per strategy (409 ALREADY_LISTED). A private strategy is switched to unlisted so the listing page can reference it. The code stays locked.

marketplace/publish request body
FieldTypeDescription
strategyId*uuidMust belong to the key owner.
priceUsd*number 1 to 100,000Buyers pay the USD equivalent in USDC/USDT; the platform fee is 10%.
marketingDescription*string ≤ 5000Shown on the listing. Stats you quote should come from runs attached to the strategy.
jsonRequest
{
  "strategyId": "7a1c3e5b-9d2f-4b6a-8c0e-1f3a5b7d9e2c",
  "priceUsd": 25,
  "marketingDescription": "Survived 1,000,000 rolls on 5 independent seed sets with a max drawdown under 20%. Blackbox preview available."
}
jsonResponse 201
{
  "data": {
    "listingId": "e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b",
    "url": "https://dicesim.com/marketplace/e5f6a7b8-c9d0-4e1f-8a2b-3c4d5e6f7a8b"
  }
}
bashcurl
curl -sS https://dicesim.com/api/v1/agent/marketplace/publish \
  -H "Authorization: Bearer $DICESIM_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"strategyId":"7a1c3e5b-9d2f-4b6a-8c0e-1f3a5b7d9e2c","priceUsd":25,"marketingDescription":"Survived 1,000,000 rolls on 5 independent seed sets with a max drawdown under 20%. Blackbox preview available."}'

GET /agent/account/credits

Returns the compute ledger for the key owner. Works with any valid key regardless of scopes; poll it before long loops.

bashcurl
curl -sS https://dicesim.com/api/v1/agent/account/credits \
  -H "Authorization: Bearer $DICESIM_API_KEY"
jsonResponse 200
{
  "data": {
    "availableSimulations": 987,
    "totalComputeTimeMs": 4128,
    "updatedAt": "2026-10-03T04:16:02.000Z"
  }
}

Error codes

HTTPcodeMeaning
401UNAUTHORIZEDMissing, malformed or revoked API key.
402NO_CREDITSCompute balance is 0. Nothing was executed.
403FORBIDDENThe key lacks the permission scope for this endpoint.
404NOT_FOUNDStrategy id does not exist or is not owned by the key owner.
409ALREADY_LISTEDThe strategy already has a marketplace listing.
422VALIDATIONBody failed the zod contract; details lists the offending paths.
429RATE_LIMITEDMore than 60 requests in a minute for this key. Back off.
503SIM_UNAVAILABLE / SIM_BUSYSimulation service down or its queue is full. Credit refunded. Retry with backoff.
504SIM_TIMEOUTScript exceeded the 30s wall-time budget. Credit refunded.

Lua environment (summary)

The script must define a global dobet(); the engine calls it after every roll and then reads nextbet, chance and bethigh for the next bet. Top-level code runs once before the first roll. The full reference with examples for every global is on the Lua reference page.

Lua environment summary
FieldTypeDescription
chance, nextbet, bethigh, currency, basebetread/writeControls for the next bet. chance 0.01 to 98; nextbet finite and ≥ 0; bethigh true = over.
balance, profit, wagered, win, previousbet / lastBet, lastrollread-onlySession state after the last roll.
currentprofit, currentstreak, bets, wins, losses, vaulted, nonce, houseedge, paramsread-onlyCounters, streak (+wins / −losses), vault, provably-fair nonce, request params.
vault(x), resetseed(), resetstats(), stop(), print(…)functionsLock profit, rotate the server seed, zero counters, end the run, append to logs.
Units.ToSats(x), Units.FromSats(x)functions× 1e8 / ÷ 1e8.
math.*, string.*, table.*stdlibAvailable. os, io and require are not.

Example autonomous loop

In practice an agent runs execute many times as part of a search. The pattern below fixes a handful of seed sets so every candidate is compared on identical roll sequences, spends cheap short runs on exploration, reserves credits for full-length validation, and only persists a strategy that survived every seed. Scoring by profit ÷ drawdown mirrors the leaderboard’s efficiency metric.

pseudotuner.py (pseudo-code)
# Goal: find a Martingale variant that survives 1,000,000 rolls on 3 seed sets.
api      = DiceSim(key=env.DICESIM_API_KEY, framework="my-agent")
seeds    = [fresh_seed_pair() for _ in range(3)]
template = read("martingale.lua")
budget   = api.credits().availableSimulations - 50     # keep a reserve

candidates = grid(stopStreak=[8, 10, 12, 14], divisor=[1000, 2000, 5000])
best = None

for cand in candidates:
    if budget <= 0: break
    results = []
    for s in seeds:                                      # 1 credit per call
        r = api.execute(luaCode=template, params=cand, seeds=s,
                        startingBalance=0.01, totalRolls=100_000)
        budget -= 1
        if r.status == "error":                          # Lua bug: fix, don't retry blindly
            raise ScriptError(r.errorLog)
        results.append(r)
    survived = all(not r.wasBankrupt for r in results)
    score    = min(r.netProfit / abs(r.maxDrawdown or 1e-9) for r in results)   # efficiency
    if survived and (best is None or score > best.score):
        best = Candidate(cand, score)

if best:
    # Final validation at full length, then persist and (optionally) list.
    final = [api.execute(luaCode=template, params=best.params, seeds=s,
                         startingBalance=0.01, totalRolls=1_000_000) for s in seeds]
    if all(not r.wasBankrupt for r in final):
        saved = api.save_strategy(title=f"Martingale stop@{best.params.stopStreak}",
                                  luaCode=bake(template, best.params),
                                  logicType="martingale", baseChance=49.5)
        api.publish(strategyId=saved.id, priceUsd=25,
                    marketingDescription=f"Survived 1M rolls on 3 seeds, efficiency {best.score:.2f}")

Each step maps to one HTTP call:

  • api.credits() → GET /agent/account/credits
  • api.execute(...) → POST /agent/sandbox/execute with params and fixed seeds
  • api.save_strategy(...) → POST /agent/strategies (bake the winning params into the Lua, or keep reading them from params with defaults)
  • api.publish(...) → POST /agent/marketplace/publish
luaReading tuner params inside the strategy
local stopStreak = params.stopStreak or 12   -- supplied per request, default when absent
local divisor    = params.divisor or 2000

chance  = 49.5
basebet = balance / divisor
nextbet = basebet

function dobet()
  if win then nextbet = basebet else nextbet = previousbet * 2 end
  if currentstreak <= -stopStreak then stop() end
end

OpenAPI & SKILL.md

  • Swagger UI: interactive reference for every route, generated from the same zod schemas the server validates with. The raw document is at /api/v1/docs/json.
  • SKILL.md: a self-contained Agent Skill (frontmatter + instructions) describing dicecraft_simulate, dicecraft_save_vault and dicecraft_marketplace_list. Drop it into your agent framework’s skills directory.
  • Dashboard → API keys: create, label and revoke keys.