{
  "openapi": "3.1.0",
  "info": {
    "title": "Teacher's Bet betting-math API",
    "version": "2026-07-24",
    "summary": "Eight free, unauthenticated endpoints that compute sports-betting mathematics on caller-supplied inputs.",
    "description": "Every endpoint is a pure calculation on the numbers in the request: de-vigging, expected value, Kelly sizing, arbitrage stake splits, free-bet conversion, parlay pricing and Kalshi event-contract fees. Responses return the result plus every intermediate value, so the derivation can be reproduced by the caller. The API serves no odds data, holds no funds, accepts no wagers, and issues no picks, recommendations or signals. Informational and entertainment purposes only. No guarantee of outcomes. Gamble responsibly: 1-800-GAMBLER, or the National Problem Gambling Helpline at 1-800-522-4700. The same math is available as the MIT-licensed Python package `teachersbettextbook`.",
    "termsOfService": "https://teachersbet.com/terms",
    "contact": { "name": "Teacher's Bet", "url": "https://teachersbet.com/contact", "email": "alex@teachersbet.com" },
    "license": { "name": "MIT (the underlying math library)", "identifier": "MIT" }
  },
  "externalDocs": { "description": "Human-readable API reference", "url": "https://teachersbet.com/api-docs" },
  "servers": [{ "url": "https://teachersbet.com", "description": "Production" }],
  "tags": [
    { "name": "fair value", "description": "Removing the margin from a posted price." },
    { "name": "value", "description": "Comparing a price to a fair probability." },
    { "name": "sizing", "description": "Turning an edge into a stake." },
    { "name": "arbitrage", "description": "Cross-venue price comparison." },
    { "name": "promotions", "description": "Valuing free bets and parlays." },
    { "name": "prediction markets", "description": "Exchange fee arithmetic." }
  ],
  "paths": {
    "/api/devig": {
      "post": {
        "operationId": "devig",
        "tags": ["fair value"],
        "summary": "Remove the vig from a two-sided moneyline",
        "description": "Returns the fair (margin-free) probabilities behind a two-outcome market under the proportional, power and Shin methods, plus the overround that was removed and the fair price in decimal and American notation.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DevigRequest" } } }
        },
        "responses": {
          "200": { "description": "Fair probabilities and the removed margin.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/DevigResponse" } } } },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "451": { "$ref": "#/components/responses/RegionBlocked" }
        }
      }
    },
    "/api/ev": {
      "post": {
        "operationId": "expectedValue",
        "tags": ["value"],
        "summary": "Expected value of one price against a reference line",
        "description": "De-vigs a reference line into a fair probability, converts the tested price into net decimal odds, and returns expected value per dollar staked under the chosen method and under all three, so method-dependence is visible.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvRequest" } } }
        },
        "responses": {
          "200": { "description": "Expected value, edge, and the per-method breakdown.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EvResponse" } } } },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "451": { "$ref": "#/components/responses/RegionBlocked" }
        }
      }
    },
    "/api/kelly": {
      "post": {
        "operationId": "kelly",
        "tags": ["sizing"],
        "summary": "Kelly stake fraction and amount for a single bet",
        "description": "Returns the growth-maximizing fraction of bankroll, the fractional-Kelly stake (half by default), and the expected log growth at both. When the inputs carry no edge the stake is zero, not a rounded-up positive number.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/KellyRequest" } } }
        },
        "responses": {
          "200": { "description": "Kelly fractions, the stake, and the growth rates.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/KellyResponse" } } } },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "451": { "$ref": "#/components/responses/RegionBlocked" }
        }
      }
    },
    "/api/kelly/simultaneous": {
      "post": {
        "operationId": "kellySimultaneous",
        "tags": ["sizing"],
        "summary": "Joint Kelly stakes across bets that settle together",
        "description": "Maximizes expected log wealth over every win/loss combination of the slate, instead of stacking per-bet Kelly stakes that together overbet the bankroll. Capped at 14 bets because the solver enumerates 2^n outcomes.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/KellySimultaneousRequest" } } }
        },
        "responses": {
          "200": { "description": "Per-bet joint fractions and stakes, with the naive comparison.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/KellySimultaneousResponse" } } } },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "451": { "$ref": "#/components/responses/RegionBlocked" }
        }
      }
    },
    "/api/arb": {
      "post": {
        "operationId": "arbitrage",
        "tags": ["arbitrage"],
        "summary": "Check whether posted prices cross, and split the stake",
        "description": "Selects the best price on each side across the supplied venues, reports the inverse-odds sum, and returns the stake split that equalizes the payouts. A return above 12 percent is flagged suspect rather than presented as free money: a two-way market that far apart nearly always contains one stale price.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArbRequest" } } }
        },
        "responses": {
          "200": { "description": "Best prices, the inverse-odds sum, the split, and execution-risk warnings.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ArbResponse" } } } },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "451": { "$ref": "#/components/responses/RegionBlocked" }
        }
      }
    },
    "/api/bonusbet": {
      "post": {
        "operationId": "bonusBet",
        "tags": ["promotions"],
        "summary": "Cash value of a free bet, hedged or not",
        "description": "Converts a stake-not-returned promotional bet into cash terms: its fair value as a fraction of face, and — when a hedge price is supplied — the hedge stake that equalizes both outcomes and the profit it locks in.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BonusBetRequest" } } }
        },
        "responses": {
          "200": { "description": "Fair value, and the hedge arithmetic when a hedge price was supplied.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BonusBetResponse" } } } },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "451": { "$ref": "#/components/responses/RegionBlocked" }
        }
      }
    },
    "/api/parlay": {
      "post": {
        "operationId": "parlay",
        "tags": ["promotions"],
        "summary": "Combined odds, payout and break-even chance of a parlay",
        "description": "Multiplies the decimal odds of every leg into the combined price and reports the vig-inclusive break-even probability all legs must jointly clear. Supplying true_prob adds the expected value.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ParlayRequest" } } }
        },
        "responses": {
          "200": { "description": "Per-leg detail, combined odds, payout and break-even probability.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ParlayResponse" } } } },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "451": { "$ref": "#/components/responses/RegionBlocked" }
        }
      }
    },
    "/api/kalshifees": {
      "post": {
        "operationId": "kalshiFees",
        "tags": ["prediction markets"],
        "summary": "Kalshi taker/maker fee, outlay and fee-adjusted break-even",
        "description": "Applies the Kalshi fee schedule with its round-up-to-the-cent rule and returns the fee, the total outlay, the fee-adjusted break-even price, and the cost of trading out versus holding to settlement. Some major events instead carry a flat 0.25 percent maker fee, which this operation does not model.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/KalshiFeesRequest" } } }
        },
        "responses": {
          "200": { "description": "Fee, outlay, break-even and round-trip cost, in dollars.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/KalshiFeesResponse" } } } },
          "422": { "$ref": "#/components/responses/ValidationError" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "451": { "$ref": "#/components/responses/RegionBlocked" }
        }
      }
    }
  },
  "components": {
    "responses": {
      "ValidationError": {
        "description": "A field is missing, the wrong type, or out of range. Validation stops at the first failing field.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" }, "examples": { "badMoneyline": { "value": { "detail": "ml_a must be <= -100 or >= 100" } } } } }
      },
      "RateLimited": {
        "description": "More than 60 requests in a minute from one IP across these eight endpoints.",
        "headers": { "retry-after": { "description": "Seconds to wait before retrying.", "schema": { "type": "string" } } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RegionBlocked": {
        "description": "The request came from a restricted region (currently Washington State, pending review of RCW 9.46.240).",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Moneyline": {
        "type": "number",
        "description": "An American moneyline. Unitless. Must satisfy |ML| >= 100; values inside (-100, 100) are rejected.",
        "examples": [-152, 138]
      },
      "Probability": {
        "type": "number",
        "exclusiveMinimum": 0,
        "exclusiveMaximum": 1,
        "description": "A probability expressed as a fraction, never a percentage. Send 0.65, not 65."
      },
      "DevigMethod": {
        "type": "string",
        "enum": ["proportional", "power", "shin"],
        "description": "How the margin is split between the two sides. Proportional scales both down equally; power fits a common exponent and shades longshots harder; Shin models the margin as protection against informed money."
      },
      "KellyFraction": {
        "type": "number",
        "exclusiveMinimum": 0,
        "maximum": 1,
        "default": 0.5,
        "description": "Kelly multiplier. 0.5 is half Kelly, the default; 0.25 is the more conservative option."
      },
      "Meta": {
        "type": "object",
        "description": "Present on every 200 response.",
        "properties": {
          "computed_at": { "type": "string", "format": "date-time", "description": "ISO-8601 UTC timestamp of the computation." },
          "disclaimer": { "type": "string", "description": "Fixed notice: outputs are calculations on user-supplied inputs, not betting advice." }
        }
      },
      "Error": {
        "type": "object",
        "required": ["detail"],
        "properties": {
          "detail": { "type": "string", "description": "Names the offending field and states the rule it broke." },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "DevigRequest": {
        "type": "object",
        "required": ["ml_a", "ml_b"],
        "additionalProperties": false,
        "properties": {
          "ml_a": { "$ref": "#/components/schemas/Moneyline" },
          "ml_b": { "$ref": "#/components/schemas/Moneyline" },
          "method": { "$ref": "#/components/schemas/DevigMethod", "default": "proportional" }
        },
        "examples": [{ "ml_a": -152, "ml_b": 138 }]
      },
      "DevigResponse": {
        "type": "object",
        "properties": {
          "inputs": { "type": "object", "description": "Echo of the request after defaults were applied." },
          "implied": { "type": "object", "description": "Vig-inclusive probabilities, keyed a and b.", "properties": { "a": { "type": "number" }, "b": { "type": "number" } } },
          "overround": { "type": "number", "description": "q_a + q_b - 1. A fraction; 0.0233 means 2.33 points over par." },
          "fair": { "type": "object", "description": "Fair probabilities under the selected method. Sum to 1.", "properties": { "a": { "type": "number" }, "b": { "type": "number" } } },
          "fair_all_methods": { "type": "object", "description": "Keyed by method name; each value is [p_a, p_b]." },
          "fair_odds": { "type": "object", "description": "The fair probabilities re-expressed as decimal and American prices." },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "EvRequest": {
        "type": "object",
        "required": ["sharp_ml_a", "sharp_ml_b", "soft_ml"],
        "additionalProperties": false,
        "properties": {
          "sharp_ml_a": { "$ref": "#/components/schemas/Moneyline", "description": "Reference line, side A." },
          "sharp_ml_b": { "$ref": "#/components/schemas/Moneyline", "description": "Reference line, side B." },
          "soft_ml": { "$ref": "#/components/schemas/Moneyline", "description": "The price being tested, from a different venue than the reference line." },
          "side": { "type": "string", "enum": ["a", "b"], "default": "a", "description": "Which side soft_ml is on." },
          "method": { "$ref": "#/components/schemas/DevigMethod", "default": "power" },
          "capital": { "type": "number", "exclusiveMinimum": 0, "description": "Bankroll, in money. Omit and stake comes back null." },
          "fraction": { "$ref": "#/components/schemas/KellyFraction" }
        },
        "examples": [{ "sharp_ml_a": -140, "sharp_ml_b": 120, "soft_ml": 145, "side": "b", "method": "power" }]
      },
      "EvResponse": {
        "type": "object",
        "properties": {
          "inputs": { "type": "object" },
          "sharp_implied": { "type": "object", "description": "Vig-inclusive probabilities of the reference line." },
          "sharp_overround": { "type": "number" },
          "p_true": { "type": "number", "description": "De-vigged fair probability of the chosen side." },
          "soft_implied": { "type": "number", "description": "Probability the tested price implies." },
          "edge": { "type": "number", "description": "p_true minus soft_implied, in probability points as a fraction." },
          "b": { "type": "number", "description": "Net decimal odds of the tested price: profit per unit staked." },
          "expected_value_per_dollar": { "type": "number", "description": "Money per unit staked, averaged over many repetitions of this exact price. Not a forecast of one bet." },
          "is_positive_ev": { "type": "boolean", "description": "A comparison of two supplied numbers, not a recommendation." },
          "by_method": { "type": "array", "description": "The same calculation under each de-vig method.", "items": { "type": "object" } },
          "robust_positive_ev": { "type": "boolean", "description": "True only when all three methods clear zero." },
          "worst_case": { "type": "object", "description": "The least favourable of the three method results." },
          "kelly_full": { "type": "number" },
          "kelly_fraction": { "type": "number" },
          "stake": { "type": ["number", "null"], "description": "Null unless capital was supplied." },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "KellyRequest": {
        "type": "object",
        "required": ["ml_a", "ml_b", "capital"],
        "additionalProperties": false,
        "properties": {
          "ml_a": { "$ref": "#/components/schemas/Moneyline", "description": "The price being taken. Net odds b are derived from this side." },
          "ml_b": { "$ref": "#/components/schemas/Moneyline", "description": "The other side, needed to de-vig the market." },
          "capital": { "type": "number", "exclusiveMinimum": 0, "description": "Bankroll, in money." },
          "p_true": { "$ref": "#/components/schemas/Probability", "description": "Your win probability for side A. Overrides the de-vig path." },
          "bias": { "type": "number", "default": 0, "description": "Fractional probability shift added to the de-vigged side A when p_true is absent." },
          "fraction": { "$ref": "#/components/schemas/KellyFraction" },
          "method": { "$ref": "#/components/schemas/DevigMethod", "default": "proportional" }
        },
        "examples": [{ "ml_a": -152, "ml_b": 138, "p_true": 0.65, "capital": 1000, "fraction": 0.5 }]
      },
      "KellyResponse": {
        "type": "object",
        "properties": {
          "inputs": { "type": "object" },
          "implied": { "type": "object" },
          "overround": { "type": "number" },
          "fair": { "type": "object" },
          "p_true": { "type": "number", "description": "The probability actually used." },
          "p_source": { "type": "string", "description": "\"user\", or \"devig[<method>] + bias\" when derived." },
          "edge": { "type": "number" },
          "b": { "type": "number" },
          "expected_value_per_dollar": { "type": "number" },
          "kelly_full": { "type": "number", "description": "Full-Kelly fraction of bankroll. Negative when there is no edge." },
          "kelly_fraction": { "type": "number", "description": "Fractional-Kelly stake fraction, clamped at 0." },
          "stake": { "type": "number", "description": "kelly_fraction times capital, in the same units as capital. Zero when there is no edge." },
          "growth_rate_full": { "type": "number", "description": "Expected log growth per bet at full Kelly." },
          "growth_rate_fractional": { "type": "number", "description": "Expected log growth per bet at the chosen fraction." },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "SimultaneousBet": {
        "type": "object",
        "required": ["ml_a", "ml_b"],
        "additionalProperties": false,
        "properties": {
          "label": { "type": "string", "maxLength": 80, "description": "Your own name for the bet. Echoed back." },
          "ml_a": { "$ref": "#/components/schemas/Moneyline" },
          "ml_b": { "$ref": "#/components/schemas/Moneyline" },
          "p_true": { "$ref": "#/components/schemas/Probability" },
          "bias": { "type": "number", "default": 0 }
        }
      },
      "KellySimultaneousRequest": {
        "type": "object",
        "required": ["bets", "capital"],
        "additionalProperties": false,
        "properties": {
          "bets": { "type": "array", "minItems": 1, "maxItems": 14, "items": { "$ref": "#/components/schemas/SimultaneousBet" } },
          "capital": { "type": "number", "exclusiveMinimum": 0 },
          "fraction": { "$ref": "#/components/schemas/KellyFraction" },
          "method": { "$ref": "#/components/schemas/DevigMethod", "default": "proportional" }
        },
        "examples": [{ "bets": [{ "label": "Game 1", "ml_a": -152, "ml_b": 138, "p_true": 0.65 }, { "label": "Game 2", "ml_a": 120, "ml_b": -140, "p_true": 0.5 }], "capital": 1000, "fraction": 0.5 }]
      },
      "KellySimultaneousResponse": {
        "type": "object",
        "properties": {
          "inputs": { "type": "object" },
          "bets": { "type": "array", "description": "Per bet: b, p_true, p_source, kelly_naive, kelly_joint_full, kelly_joint and stake.", "items": { "type": "object" } },
          "sum_naive": { "type": "number", "description": "Sum of the per-bet Kelly fractions sized independently." },
          "sum_joint_full": { "type": "number", "description": "Sum of the jointly optimized full-Kelly fractions." },
          "sum_joint": { "type": "number", "description": "sum_joint_full scaled by the Kelly multiplier." },
          "total_stake": { "type": "number" },
          "log_growth_full": { "type": "number" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "ArbQuote": {
        "type": "object",
        "required": ["book", "ml_a", "ml_b"],
        "additionalProperties": false,
        "properties": {
          "book": { "type": "string", "maxLength": 64, "description": "Venue name. A label only; it carries no weight in the math." },
          "ml_a": { "$ref": "#/components/schemas/Moneyline" },
          "ml_b": { "$ref": "#/components/schemas/Moneyline" }
        }
      },
      "ArbRequest": {
        "type": "object",
        "required": ["quotes"],
        "additionalProperties": false,
        "properties": {
          "quotes": { "type": "array", "minItems": 1, "maxItems": 200, "items": { "$ref": "#/components/schemas/ArbQuote" } },
          "total_stake": { "type": "number", "exclusiveMinimum": 0, "default": 100, "description": "Total across both legs, in money." }
        },
        "examples": [{ "quotes": [{ "book": "Book A", "ml_a": 110, "ml_b": -105 }, { "book": "Book B", "ml_a": -102, "ml_b": 115 }], "total_stake": 1000 }]
      },
      "ArbResponse": {
        "type": "object",
        "properties": {
          "best": { "type": "object", "description": "Best price found on each side, with the venue that posted it." },
          "inverse_odds_sum": { "type": "number", "description": "1/d_a + 1/d_b. The cost of buying one unit of guaranteed payout." },
          "is_arb": { "type": "boolean", "description": "True when inverse_odds_sum is below 1." },
          "total_stake": { "type": "number" },
          "stake_a": { "type": "number" },
          "stake_b": { "type": "number" },
          "payout": { "type": "number", "description": "Identical on either outcome, assuming both legs fill at the quoted price." },
          "profit": { "type": "number" },
          "return_fraction": { "type": "number" },
          "suspect": { "type": "boolean", "description": "True above a 12 percent return, where one stale price is far likelier than free money. Read this before is_arb." },
          "same_book": { "type": "boolean", "description": "True when both best prices come from one venue: negative margin, nearly always an error." },
          "warnings": { "type": "array", "items": { "type": "string" }, "description": "Execution-risk notes. Always populated." },
          "quotes": { "type": "array", "items": { "type": "object" }, "description": "Echo of the input quotes with decimal odds attached." },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "BonusBetRequest": {
        "type": "object",
        "required": ["bonus", "bonus_ml"],
        "additionalProperties": false,
        "properties": {
          "bonus": { "type": "number", "exclusiveMinimum": 0, "description": "Face value of the free bet, in money." },
          "bonus_ml": { "$ref": "#/components/schemas/Moneyline", "description": "The price the free bet is placed at." },
          "hedge_ml": { "type": ["number", "null"], "description": "Cash price available on the opposite side. Omit for the unhedged valuation only." },
          "keep_stake": { "type": "boolean", "default": false, "description": "True if the token returns the stake as well as the profit." }
        },
        "examples": [{ "bonus": 100, "bonus_ml": 300, "hedge_ml": -360 }]
      },
      "BonusBetResponse": {
        "type": "object",
        "properties": {
          "bonus": { "type": "number" },
          "bonus_decimal": { "type": "number" },
          "keep_stake": { "type": "boolean" },
          "win_profit": { "type": "number", "description": "Profit if the free bet wins, unhedged, in money." },
          "fair_value_rate": { "type": "number", "description": "(d - 1) / d for the bonus price: the fraction of face a stake-not-returned token is worth at a fair hedge." },
          "cash_value": { "type": "number" },
          "hedged": { "type": "boolean" },
          "hedge_decimal": { "type": ["number", "null"] },
          "hedge_stake": { "type": ["number", "null"], "description": "Stake on the other side that equalizes both outcomes, in money." },
          "guaranteed_profit": { "type": ["number", "null"] },
          "conversion_rate": { "type": ["number", "null"], "description": "guaranteed_profit divided by face value. Lower than fair_value_rate because the hedge price carries its own margin." },
          "profit_if_bonus_wins": { "type": ["number", "null"] },
          "profit_if_hedge_wins": { "type": ["number", "null"] },
          "inputs": { "type": "object" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "ParlayRequest": {
        "type": "object",
        "required": ["legs"],
        "additionalProperties": false,
        "properties": {
          "legs": { "type": "array", "minItems": 2, "maxItems": 15, "items": { "$ref": "#/components/schemas/Moneyline" }, "description": "The legs, as bare American moneylines." },
          "stake": { "type": "number", "exclusiveMinimum": 0, "default": 100, "description": "Amount risked, in money." },
          "true_prob": { "$ref": "#/components/schemas/Probability", "description": "Your probability that every leg lands. Omit and the EV fields come back null." }
        },
        "examples": [{ "legs": [-110, -110, -110], "stake": 100 }]
      },
      "ParlayResponse": {
        "type": "object",
        "properties": {
          "legs": { "type": "array", "items": { "type": "object" }, "description": "Per leg: ml, decimal, implied." },
          "n_legs": { "type": "integer" },
          "combined_decimal": { "type": "number" },
          "combined_american": { "type": "number" },
          "implied_prob": { "type": "number", "description": "Vig-inclusive break-even chance that every leg lands." },
          "stake": { "type": "number" },
          "payout": { "type": "number" },
          "profit": { "type": "number" },
          "true_prob": { "type": ["number", "null"] },
          "expected_value_per_dollar": { "type": ["number", "null"] },
          "edge": { "type": ["number", "null"] },
          "is_positive_ev": { "type": ["boolean", "null"] },
          "inputs": { "type": "object" },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "KalshiFeesRequest": {
        "type": "object",
        "required": ["price_cents"],
        "additionalProperties": false,
        "properties": {
          "price_cents": { "type": "number", "minimum": 1, "maximum": 99, "description": "Contract price in CENTS. A 50-cent contract is 50, not 0.50." },
          "contracts": { "type": "integer", "minimum": 1, "maximum": 1000000, "default": 1, "description": "Order size, a whole number." },
          "side": { "type": "string", "enum": ["taker", "maker"], "default": "taker", "description": "Taker crosses the spread; maker rests an order." }
        },
        "examples": [{ "price_cents": 50, "contracts": 100, "side": "taker" }]
      },
      "KalshiFeesResponse": {
        "type": "object",
        "properties": {
          "inputs": { "type": "object" },
          "rate": { "type": "number", "description": "0.07 for takers, 0.0175 for makers." },
          "fee": { "type": "number", "description": "Fee in dollars, rounded up to the next cent on the total." },
          "fee_per_contract": { "type": "number" },
          "cost": { "type": "number", "description": "contracts times price, in dollars." },
          "total_outlay": { "type": "number" },
          "breakeven": { "type": "object", "description": "formula = price + rate * price * (1 - price); effective = rounded outlay divided by contract count.", "properties": { "formula": { "type": "number" }, "effective": { "type": "number" } } },
          "roundtrip_fee": { "type": "number", "description": "Twice the fee: entry plus a pre-settlement exit at an unchanged price." },
          "settlement_fee": { "type": "number", "description": "Zero. Holding to resolution is the cheap exit." },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      }
    }
  }
}
