{"components":{"responses":{},"schemas":{"CascadeEntry":{"properties":{"kills":{"example":2,"minimum":0,"type":"integer"},"probability":{"example":0.331,"format":"double","type":"number"},"probability_exact":{"example":"331/1000","type":"string"}},"required":["kills","probability","probability_exact"],"title":"CascadeEntry","type":"object"},"CascadesRequest":{"description":"Cascade depth + queue HP are encoded *in the expression* — see the `cascade N` (Count) and `cascade [HP1, HP2, …]` (Explicit) forms documented at /concepts/great-weapon-master-on-kill.","properties":{"expression":{"description":"Expression containing a `cascade` clause.","example":"(1d12+5) cascade 4","type":"string"},"hp":{"description":"Per-minion HP. Required for Count-form cascades.","example":11,"minimum":1,"nullable":true,"type":"integer"}},"required":["expression"],"title":"CascadesRequest","type":"object"},"CascadesResponse":{"description":"If the expression has no cascade clause, `result` is `null` and the response is still 200 — empty/no-cascade is a valid answer.","properties":{"meta":{"$ref":"#/components/schemas/EnvelopeMeta"},"result":{"nullable":true,"oneOf":[{"$ref":"#/components/schemas/CascadesResult"}]}},"required":["meta","result"],"title":"CascadesResponse","type":"object"},"CascadesResult":{"description":"Cascade kill-count distribution. `pmf[i]` is P(kills = i); `cumulative[i]` is P(kills ≥ i+1). `count_form` is true when the expression used the Count form (`cascade N`) so the queue was materialized using `per_minion_hp`.","nullable":true,"properties":{"count_form":{"example":true,"type":"boolean"},"cumulative":{"items":{"$ref":"#/components/schemas/CascadeEntry"},"type":"array"},"expected_kills":{"example":1.62,"format":"double","type":"number"},"expected_kills_exact":{"example":"81/50","type":"string"},"per_minion_hp":{"example":11,"type":"integer"},"pmf":{"items":{"$ref":"#/components/schemas/CascadeEntry"},"type":"array"},"queue_length":{"example":4,"type":"integer"}},"required":["queue_length","pmf","cumulative","expected_kills","expected_kills_exact","count_form","per_minion_hp"],"title":"CascadesResult","type":"object"},"CompareRequest":{"properties":{"a":{"example":"2d6+5","type":"string"},"b":{"example":"1d12+5","type":"string"},"hp":{"example":11,"minimum":1,"type":"integer"}},"required":["a","b","hp"],"title":"CompareRequest","type":"object"},"CompareResponse":{"properties":{"meta":{"$ref":"#/components/schemas/EnvelopeMeta"},"result":{"$ref":"#/components/schemas/CompareResult"}},"required":["meta","result"],"title":"CompareResponse","type":"object"},"CompareResult":{"description":"Side-by-side distributions of `a` and `b` at the given HP, plus the signed delta a.kill_probability − b.kill_probability (positive when `a` is the better killer at this HP). `kill_probability_delta_exact` is the reduced-rational form (\"p/q\" or \"p\"); `null` only if either side's HP-dependent kill probability is itself null (which Compare prevents by requiring `hp` in the request).","properties":{"a":{"$ref":"#/components/schemas/Distribution"},"b":{"$ref":"#/components/schemas/Distribution"},"kill_probability_delta":{"example":0.083,"format":"double","type":"number"},"kill_probability_delta_exact":{"example":"1/12","nullable":true,"type":"string"}},"required":["a","b","kill_probability_delta","kill_probability_delta_exact"],"title":"CompareResult","type":"object"},"Distribution":{"description":"Full damage-distribution analysis of an expression. `kill_probability` and the variance/stddev/skewness fields are present whenever the engine can compute them for the given shape; `null` otherwise.","properties":{"expression":{"example":"2d6+5","type":"string"},"kill_probability":{"description":"P(damage ≥ hp). Present when `hp` is supplied.","example":0.722,"format":"double","nullable":true,"type":"number"},"kill_probability_exact":{"example":"13/18","nullable":true,"type":"string"},"max":{"example":17,"type":"integer"},"mean":{"example":12.0,"format":"double","type":"number"},"mean_exact":{"example":"12","type":"string"},"min":{"example":7,"type":"integer"},"outcomes":{"items":{"$ref":"#/components/schemas/Outcome"},"type":"array"},"skewness":{"example":0.0,"format":"double","nullable":true,"type":"number"},"stddev":{"example":2.415,"format":"double","nullable":true,"type":"number"},"variance":{"example":5.833,"format":"double","nullable":true,"type":"number"},"variance_exact":{"example":"35/6","nullable":true,"type":"string"}},"required":["expression","min","max","mean","mean_exact","outcomes"],"title":"Distribution","type":"object"},"EnvelopeMeta":{"description":"Per-response metadata always present on 2xx envelopes. `engine_version` follows `X.Y.Z+sha.<short>` and pins the exact build that produced this response.","properties":{"engine_version":{"example":"0.1.0+sha.91b3979","type":"string"},"request_id":{"description":"Mirrors the `x-request-id` response header for log correlation.","example":"GUv8h4iY...XK","type":"string"},"tier":{"$ref":"#/components/schemas/Tier"}},"required":["tier","engine_version","request_id"],"title":"EnvelopeMeta","type":"object"},"Error":{"description":"Standard error envelope for 4xx / 5xx responses.","properties":{"error":{"description":"Stable machine-readable code (e.g. \"invalid_expression\").","example":"invalid_expression","type":"string"},"message":{"example":"expected `d`, got `q` at position 2","type":"string"},"request_id":{"nullable":true,"type":"string"},"see":{"example":"https://diceplots.com/platforms/api","format":"uri","type":"string"},"tier":{"$ref":"#/components/schemas/Tier"}},"required":["error","message","tier","see"],"title":"Error","type":"object"},"HelperArg":{"properties":{"default":{"example":"","type":"string"},"doc":{"example":"Attack bonus added to the d20.","type":"string"},"kind":{"description":"Type tag: \"int\" / \"bool\" / \"expression\" / \"string\".","example":"int","type":"string"},"name":{"example":"to_hit","type":"string"},"required":{"example":true,"type":"boolean"}},"required":["name","kind","required","default","doc"],"title":"HelperArg","type":"object"},"HelperResponse":{"properties":{"meta":{"$ref":"#/components/schemas/EnvelopeMeta"},"result":{"$ref":"#/components/schemas/HelperSchema"}},"required":["meta","result"],"title":"HelperResponse","type":"object"},"HelperSchema":{"description":"Full schema for one MCP-callable engine helper.","properties":{"args":{"items":{"$ref":"#/components/schemas/HelperArg"},"type":"array"},"doc":{"type":"string"},"name":{"example":"strike","type":"string"},"returns":{"enum":["distribution","probability_bps","scalar_bps","scalar_count"],"type":"string"}},"required":["name","doc","args","returns"],"title":"HelperSchema","type":"object"},"HelperSummary":{"description":"Light-weight helper entry for the list endpoint.","properties":{"doc":{"type":"string"},"name":{"type":"string"},"returns":{"type":"string"}},"required":["name","returns","doc"],"title":"HelperSummary","type":"object"},"HelpersListResponse":{"properties":{"meta":{"$ref":"#/components/schemas/EnvelopeMeta"},"result":{"$ref":"#/components/schemas/HelpersListResult"}},"required":["meta","result"],"title":"HelpersListResponse","type":"object"},"HelpersListResult":{"properties":{"count":{"example":31,"type":"integer"},"helpers":{"items":{"$ref":"#/components/schemas/HelperSummary"},"type":"array"}},"required":["helpers","count"],"title":"HelpersListResult","type":"object"},"Outcome":{"description":"One row of a discrete distribution: value + probability + exact rational.","properties":{"probability":{"example":0.0277,"format":"double","type":"number"},"probability_exact":{"description":"Reduced rational, encoded as \"p/q\" (or \"p\" when q=1).","example":"1/36","type":"string"},"value":{"example":7,"type":"integer"}},"required":["value","probability","probability_exact"],"title":"Outcome","type":"object"},"RateLimitError":{"description":"429 body when the per-tier rate limit is exhausted.","properties":{"error":{"enum":["rate_limited"],"type":"string"},"limit_per_window":{"example":30,"type":"integer"},"see":{"format":"uri","type":"string"},"tier":{"$ref":"#/components/schemas/Tier"},"window_ms":{"example":60000,"type":"integer"}},"required":["error","tier","limit_per_window","window_ms","see"],"title":"RateLimitError","type":"object"},"Resistance":{"description":"Per-damage-type resistance/vulnerability/immunity. `type` is a damage-type tag (e.g. \"fire\", \"slashing\"); `level` is one of \"resistant\" / \"vulnerable\" / \"immune\".","properties":{"level":{"enum":["resistant","vulnerable","immune"],"type":"string"},"type":{"example":"fire","type":"string"}},"required":["type","level"],"title":"Resistance","type":"object"},"StrikeRequest":{"properties":{"expression":{"description":"Damage expression (same grammar as /strike/:slug).","example":"2d6+5","type":"string"},"hp":{"description":"Target HP for kill_probability. Omit to skip the kill calc.","example":11,"minimum":1,"nullable":true,"type":"integer"},"resistances":{"description":"Optional per-damage-type resistances applied before the convolution.","example":[{"level":"resistant","type":"fire"}],"items":{"$ref":"#/components/schemas/Resistance"},"type":"array"}},"required":["expression"],"title":"StrikeRequest","type":"object"},"StrikeResponse":{"properties":{"meta":{"$ref":"#/components/schemas/EnvelopeMeta"},"result":{"$ref":"#/components/schemas/Distribution"}},"required":["meta","result"],"title":"StrikeResponse","type":"object"},"StrikesToKillEntry":{"properties":{"cumulative_kill_probability":{"example":0.842,"format":"double","type":"number"},"cumulative_kill_probability_exact":{"example":"421/500","type":"string"},"strike_count":{"example":5,"minimum":1,"type":"integer"}},"required":["strike_count","cumulative_kill_probability","cumulative_kill_probability_exact"],"title":"StrikesToKillEntry","type":"object"},"StrikesToKillRequest":{"properties":{"expression":{"example":"2d6+5","type":"string"},"hp":{"example":50,"minimum":1,"type":"integer"},"max_strikes":{"description":"Max strikes to walk before declaring non-convergence. Default 50.","example":50,"maximum":1000,"minimum":1,"nullable":true,"type":"integer"},"resistances":{"items":{"$ref":"#/components/schemas/Resistance"},"nullable":true,"type":"array"}},"required":["expression","hp"],"title":"StrikesToKillRequest","type":"object"},"StrikesToKillResponse":{"properties":{"meta":{"$ref":"#/components/schemas/EnvelopeMeta"},"result":{"$ref":"#/components/schemas/StrikesToKillResult"}},"required":["meta","result"],"title":"StrikesToKillResponse","type":"object"},"StrikesToKillResult":{"description":"Expected strikes to deplete `hp` plus the cumulative kill curve. `source` is \"exact\" for full rational arithmetic or \"f64_fallback\" when the BigRational path bailed on its budget — the fallback is still numerically sound, just not bit-exact.","properties":{"converged":{"example":true,"type":"boolean"},"cumulative":{"items":{"$ref":"#/components/schemas/StrikesToKillEntry"},"type":"array"},"expected_strikes":{"example":4.27,"format":"double","type":"number"},"expected_strikes_exact":{"example":"427/100","type":"string"},"source":{"enum":["exact","f64_fallback"],"type":"string"}},"required":["cumulative","expected_strikes","expected_strikes_exact","converged","source"],"title":"StrikesToKillResult","type":"object"},"Tier":{"description":"Resolved per-request tier (set by ApiKeyAuth plug).","enum":["free_unreg","free_reg","paid"],"title":"Tier","type":"string"}}},"info":{"contact":{"email":"tom@defensiblelogic.com","name":"Diceplots","url":"https://diceplots.com/platforms/api"},"description":"Public HTTP / JSON surface to the Diceplots Rust engine — the same core that\npowers every number on diceplots.com. Three tiers (free unauthenticated, free\nregistered, paid) gated by API key with per-tier rate limits enforced at the\nedge. See <https://diceplots.com/platforms/api> for tier pricing and key\nmanagement.\n\n**Exactness is the product.** Every probability is returned as both a `f64`\napproximation and a reduced-rational `*_exact` string (\"p/q\" or \"p\"). Don't\nround-trip through floats if you need bit-exact results — parse the exact\nrationals directly.\n","title":"Diceplots API","version":"0.1.0+sha.f8c2a84"},"openapi":"3.0.0","paths":{"/api/v1/cascades":{"post":{"callbacks":{},"description":"GWM-on-kill, PAM glaive haft, and similar cascade mechanics. Cascade depth + queue HP are encoded in the expression itself (`cascade N` for Count form, `cascade [HP1, HP2, …]` for Explicit form). Expressions with no cascade clause return 200 with `result: null`.","operationId":"DiceplotsWeb.Api.V1Controller.cascades","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CascadesRequest"}}},"description":"Cascades parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CascadesResponse"}}},"description":"Cascade result (or null)"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid expression or body"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}},"description":"Rate limited"}},"summary":"Cascade kill-count distribution","tags":[]}},"/api/v1/compare":{"post":{"callbacks":{},"description":"Side-by-side distributions of `a` and `b` at `hp`, plus the signed `kill_probability_delta` (positive when `a` is the better killer at this HP). Crossover-vs-HP curves can be computed by sweeping `hp` client-side.","operationId":"DiceplotsWeb.Api.V1Controller.compare","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareRequest"}}},"description":"Compare parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CompareResponse"}}},"description":"Compare result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid expression or body"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}},"description":"Rate limited"}},"summary":"Compare two expressions at a given HP","tags":[]}},"/api/v1/helpers":{"get":{"callbacks":{},"description":"Returns the lightweight {name, returns, doc} shape for every registered helper. For the full arg schema of one helper, GET /api/v1/helpers/:name.","operationId":"DiceplotsWeb.Api.V1Controller.helpers_index","parameters":[],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HelpersListResponse"}}},"description":"Helper list"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}},"description":"Rate limited"}},"summary":"List MCP-callable engine helpers","tags":[]}},"/api/v1/helpers/{name}":{"get":{"callbacks":{},"operationId":"DiceplotsWeb.Api.V1Controller.helpers_show","parameters":[{"description":"","example":"strike","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HelperResponse"}}},"description":"Helper schema"},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Helper not registered"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}},"description":"Rate limited"}},"summary":"Describe one engine helper","tags":[]}},"/api/v1/strike":{"post":{"callbacks":{},"description":"Returns the full damage distribution (outcomes + summary stats) for `expression`. When `hp` is supplied, also returns `kill_probability` = P(damage ≥ hp). Same shape and grammar as `/strike/:slug`, JSON-shaped for programmatic consumers.","operationId":"DiceplotsWeb.Api.V1Controller.strike","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrikeRequest"}}},"description":"Strike parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrikeResponse"}}},"description":"Distribution result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid expression or body"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}},"description":"Rate limited"}},"summary":"Analyze a damage expression","tags":[]}},"/api/v1/strikes-to-kill":{"post":{"callbacks":{},"description":"Walks the strike chain against `hp` up to `max_strikes` and returns the expected number of strikes plus the cumulative kill probability after each strike. `source` distinguishes exact-rational results from the f64 fallback path.","operationId":"DiceplotsWeb.Api.V1Controller.strikes_to_kill","parameters":[],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrikesToKillRequest"}}},"description":"Strikes-to-kill parameters","required":false},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StrikesToKillResponse"}}},"description":"Strikes-to-kill result"},"400":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Invalid expression or body"},"429":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitError"}}},"description":"Rate limited"}},"summary":"Expected strikes to kill + cumulative kill curve","tags":[]}}},"security":[],"servers":[{"description":"Production","url":"https://diceplots.com","variables":{}},{"description":"Local dev","url":"http://localhost:4010","variables":{}}],"tags":[]}