{
  "openapi": "3.1.0",
  "info": {
    "title": "Ohmatic API",
    "version": "0.1",
    "description": "Deterministic circuit verifier (ERC) as an HTTP + MCP API. You send a circuit, Ohmatic returns diagnostics + pin-level repair feedback, or refuses. It verifies; it does not generate."
  },
  "paths": {
    "/v1/webhooks/polar": {
      "post": {
        "summary": "Polar Webhook",
        "operationId": "polar_webhook_v1_webhooks_polar_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/signup": {
      "post": {
        "summary": "Signup",
        "description": "ADMIN-gated. The site verifies the email/identity, then calls this server-to-server to mint a\nFRESH VERIFIED key (externalId = normalized email) on the verified free tier\n(OHMATIC_FREE_GENERATIONS per month). Records the confirmed marketing opt-in (the GDPR basis for\nthe 'trial finished' email). Idempotent \u2014 one key per identity. A 0-credit key is valid at cost=0\nfor free generations and 402s once the monthly free tier is spent; a Polar purchase then tops it\nup (binds via externalId=email). Email verification itself belongs to the site.",
        "operationId": "signup_v1_signup_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/anon-key": {
      "post": {
        "summary": "Anon Key",
        "description": "PUBLIC. Mint a free ANONYMOUS key \u2014 OHMATIC_FREE_ANON free generation(s), LIFETIME ('one and\ndone'). The zero-friction 'try it' hook; once spent, the 402 gate nudges email signup (which grants\nOHMATIC_FREE_GENERATIONS free generations a month). Anti-abuse = the swappable _anon_allowed() seam\n(light per-IP daily cap by default).",
        "operationId": "anon_key_v1_anon_key_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/billing/checkout": {
      "post": {
        "summary": "Billing Checkout",
        "description": "Authed by the caller's API key. Returns a Polar hosted checkout URL to BUY A CREDIT PACK ($5 min); the pack's meter_credit benefit lands the units on the prepaid balance. Body: {pack} (a product_id from /v1/billing/catalog; defaults to the $5 pack). Anon keys are sent to sign up first.",
        "operationId": "billing_checkout_v1_billing_checkout_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/billing/status": {
      "get": {
        "summary": "Billing Status",
        "description": "Authed. Drives the frontend: {tier, free_generations_remaining, balance_usd, balance_micros, needs_topup} — the prepaid balance in USD.",
        "operationId": "billing_status_v1_billing_status_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/billing/subscribe": {
      "post": {
        "summary": "Billing Subscribe",
        "description": "Authed (verified). Returns a Polar hosted checkout URL to START A MONTHLY PLAN; the plan's meter_credit benefit tops up the prepaid balance each cycle, plus a one-time first-month welcome bonus. Body: {plan} (a product_id from /v1/billing/catalog). Anon keys are sent to sign up first.",
        "operationId": "billing_subscribe_v1_billing_subscribe_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/billing/catalog": {
      "get": {
        "summary": "Billing Catalog",
        "description": "PUBLIC. The buyable products: {currency, min_topup_usd, unit_price_usd, packs:[{product_id, credits_usd}], plans:[{product_id, welcome_usd}]}. Lets a client render Buy/Subscribe without hard-coding product ids.",
        "operationId": "billing_catalog_v1_billing_catalog_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/referral": {
      "post": {
        "summary": "Referral",
        "description": "Authed. Returns {code, link, referred_count, bonus_earned_usd}. The caller earns 10% of what each user they refer spends in that user's first 14 days, paid as bonus credits.",
        "operationId": "referral_v1_referral_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/internal/drain-meter": {
      "post": {
        "summary": "Drain Meter",
        "description": "ADMIN-gated. Re-ingest usage events that failed live ingestion (the durable spool). Idempotent.",
        "operationId": "drain_meter_v1_internal_drain_meter_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/internal/notify-trial-ended": {
      "post": {
        "summary": "Notify Trial Ended",
        "description": "STUB (admin-gated) \u2014 the trial-ended pricing email. NOT YET IMPLEMENTED. See the banner above.",
        "operationId": "notify_trial_ended_v1_internal_notify_trial_ended_post",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/onboarding": {
      "get": {
        "summary": "Onboarding",
        "operationId": "onboarding_v1_onboarding_get",
        "parameters": [
          {
            "name": "checkout_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "",
              "title": "Checkout Id"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "summary": "Health",
        "operationId": "health_v1_health_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/v1/verify": {
      "post": {
        "summary": "Verify",
        "description": "Verify a circuit. Auth: `Authorization: Bearer <key>`. Group a circuit's verify+repair calls under one `generation_id`. If `passed` is false, apply `feedback.repairs` and re-POST under the SAME `generation_id`.",
        "operationId": "verify_v1_verify_post",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/VerifyRequest"},
              "example": {
                "circuit": {
                  "metadata": {"title": "LED indicator", "description": "5V LED with current-limiting resistor", "version": "0.1", "tags": ["led"]},
                  "components": [
                    {"id": "VCC1", "type": "power_vcc", "value": "5V", "part": "VCC", "x": 0, "y": 0, "pins": {"1": "VCC"}},
                    {"id": "GND1", "type": "power_gnd", "value": "0V", "part": "GND", "x": 0, "y": 40, "pins": {"1": "GND"}},
                    {"id": "R1", "type": "resistor", "value": "330", "part": "0603", "x": 10, "y": 10, "pins": {"1": "VCC", "2": "N1"}},
                    {"id": "D1", "type": "led", "value": "red", "part": "0603", "x": 10, "y": 30, "pins": {"A": "N1", "K": "GND"}}
                  ],
                  "nets": [
                    {"name": "VCC", "pins": ["VCC1.1", "R1.1"]},
                    {"name": "N1", "pins": ["R1.2", "D1.A"]},
                    {"name": "GND", "pins": ["D1.K", "GND1.1"]}
                  ]
                },
                "generation_id": "led-001",
                "max_rounds": 4
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/VerifyResponse"}
              }
            }
          }
        }
      }
    },
    "/v1/repair-feedback": {
      "post": {
        "summary": "Repair Feedback",
        "description": "Same request shape as /v1/verify; returns just the repair guidance for a failing circuit (no full diagnostics list). Auth: `Authorization: Bearer <key>`.",
        "operationId": "repair_feedback_v1_repair_feedback_post",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/VerifyRequest"}
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {"$ref": "#/components/schemas/VerifyResponse"}
              }
            }
          }
        }
      }
    },
    "/v1/credits": {
      "get": {
        "summary": "Credits",
        "operationId": "credits_v1_credits_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Circuit": {
        "type": "object",
        "title": "OhmaticCircuitV01",
        "description": "A circuit schematic (v0.1). Full JSON Schema: https://ohmatic.dev/schemas/circuit_v01.json . NOTE: pins on a component are a MAP (pin -> node label), not a list; every component needs id/type/value/part/x/y/pins; a circuit with power must include power_vcc and power_gnd components; each net lists >=2 'ComponentId.pin' refs.",
        "required": ["metadata", "components", "nets"],
        "properties": {
          "metadata": {
            "type": "object",
            "required": ["title", "description", "version", "tags"],
            "properties": {
              "title": {"type": "string"},
              "description": {"type": "string"},
              "version": {"type": "string", "const": "0.1"},
              "tags": {"type": "array", "items": {"type": "string"}, "minItems": 1}
            }
          },
          "components": {
            "type": "array", "minItems": 1,
            "items": {
              "type": "object",
              "required": ["id", "type", "value", "part", "x", "y", "pins"],
              "properties": {
                "id": {"type": "string", "description": "Unique, starts uppercase (R1, U1, VCC1)."},
                "type": {"type": "string", "description": "Registry type: resistor, capacitor, led, diode, transistor_npn, ic_timer, ic_opamp, power_vcc, power_gnd, ... (see the JSON Schema enum)."},
                "value": {"type": "string", "description": "e.g. \"330\", \"100nF\", \"555\"."},
                "part": {"type": "string", "description": "Footprint/package, e.g. \"0603\", \"DIP-8\"."},
                "x": {"type": "number"},
                "y": {"type": "number"},
                "pins": {"type": "object", "minProperties": 1, "additionalProperties": {"type": "string"}, "description": "MAP of pin name -> local node label, e.g. {\"1\":\"VCC\",\"2\":\"N1\"}."}
              }
            }
          },
          "nets": {
            "type": "array", "minItems": 1,
            "items": {
              "type": "object",
              "required": ["name", "pins"],
              "properties": {
                "name": {"type": "string"},
                "pins": {"type": "array", "minItems": 2, "items": {"type": "string"}, "description": "\"ComponentId.pin\" refs, at least 2, e.g. [\"R1.1\",\"VCC1.1\"]."}
              }
            }
          }
        }
      },
      "VerifyRequest": {
        "type": "object",
        "required": ["circuit"],
        "properties": {
          "circuit": {"$ref": "#/components/schemas/Circuit"},
          "generation_id": {"type": "string", "description": "Group a circuit's verify+repair calls under one stable id (bills as ONE circuit). Omit -> a new id per call."},
          "max_rounds": {"type": "integer", "description": "Per-circuit round cap (spend guard). Free tier is hard-capped at 4; paid accepts 0-999."}
        }
      },
      "VerifyResponse": {
        "type": "object",
        "properties": {
          "passed": {"type": "boolean"},
          "diagnostic_count": {"type": "integer"},
          "diagnostics": {"type": "array", "items": {"type": "object"}},
          "feedback": {"type": "object", "description": "When passed=false: repairs[] with pin-level fixes to apply, then re-POST under the same generation_id."},
          "billed_usd": {"type": "number"},
          "round": {"type": "integer"},
          "free_generations_remaining": {"type": "integer"}
        }
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          },
          "input": {
            "title": "Input"
          },
          "ctx": {
            "type": "object",
            "title": "Context"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      }
    }
  },
  "servers": [
    {
      "url": "https://ohmatic.dev",
      "description": "Ohmatic API (production)"
    }
  ]
}