{
  "openapi": "3.0.3",
  "info": {
    "title": "MySQLBot Services API",
    "version": "1.0.0",
    "description": "Programmatic API for MySQLBot's verified-answer & document services: verified/cited research briefs, competitor analyses, and structured data extraction from documents. Authenticate with an API key in the X-Api-Key header. Order lifecycle: create_order -> poll status -> fetch deliverable (returned inline). Human-readable guide: https://mysqlbot.com/pages/public/serviceApiGuide.php",
    "contact": { "name": "MySQLBot", "url": "https://mysqlbot.com/pages/public/?page=contact" }
  },
  "servers": [ { "url": "https://mysqlbot.com" } ],
  "security": [ { "ApiKeyAuth": [] } ],
  "paths": {
    "/pages/public/serviceApi.php": {
      "post": {
        "operationId": "postAction",
        "summary": "create_order (needs X-Api-Key) or register (no auth)",
        "description": "POST actions. create_order (X-Api-Key) creates + queues an order; a prepaid key debits its balance (402 insufficient_balance) and idempotency_key makes retries safe. register (NO auth) mints a new prepaid key with a zero balance, returning api_key + top_up_url inline. The remaining actions are ACCOUNT/TEAM MANAGEMENT for a key attached to a team account, gated by the key's member permissions (mirrors the web control panel): invite_member/remove_member (can_manage_members), set_permissions/rename_account (owner only), create_key/revoke_key (can_manage_keys), set_display_name/set_password (self). See the human guide (serviceApiGuide.php) §8 for each action's body fields. Auth is OPTIONAL at the operation level only because register needs none; every other action requires X-Api-Key.",
        "security": [ { "ApiKeyAuth": [] }, {} ],
        "parameters": [
          { "name": "action", "in": "query", "required": true, "schema": { "type": "string", "enum": ["create_order", "register", "invite_member", "remove_member", "set_permissions", "create_key", "revoke_key", "rename_account", "set_display_name", "set_password"] } }
        ],
        "requestBody": {
          "required": false,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateOrderRequest" } } }
        },
        "responses": {
          "200": { "description": "create_order → an order (or idempotent duplicate); register → a new prepaid key.", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/CreateOrderResponse" }, { "$ref": "#/components/schemas/RegisterResponse" } ] } } } },
          "400": { "description": "invalid_service_type | empty_request | extraction_requires_source_docs | invalid_email (register)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "401": { "description": "invalid_api_key (create_order)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "402": { "description": "insufficient_balance — prepaid key balance too low; data has balance, required, currency, top_up_url", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "no_permission | not_an_account_key | key_member_not_resolved (management actions)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "409": { "description": "already_a_member_somewhere (invite_member)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "405": { "description": "use_POST_for_create_order | use_POST_for_register", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "413": { "description": "source_docs_too_long (data.max = 300000)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "429": { "description": "rate_limited | daily_order_cap_reached | provision_rate_limited (register)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "500": { "description": "create_failed | provision_failed | server_error — transient; retry with backoff", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "503": { "description": "service_price_unconfigured | service temporarily unavailable", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } }
        }
      },
      "get": {
        "operationId": "getAction",
        "summary": "Order status / deliverable / prepaid balance",
        "description": "GET actions (all require X-Api-Key). status → order state (poll until done); deliverable → finished output inline; balance → the key's prepaid balance + top_up_url; a key only sees orders it created. Account/team reads for an account-attached key (see guide §8): account → who am I + permissions; list_members (can_manage_members) → team roster; list_keys (can_manage_keys) → the account's keys; list_orders → account orders (a member without can_see_all_orders sees only their own).",
        "parameters": [
          { "name": "action", "in": "query", "required": true, "schema": { "type": "string", "enum": ["status", "deliverable", "balance", "account", "list_members", "list_keys", "list_orders"] } },
          { "name": "ref", "in": "query", "required": false, "description": "The order_ref (required for status/deliverable; omit for the others).", "schema": { "type": "string" } },
          { "name": "limit", "in": "query", "required": false, "description": "list_orders: max rows (<=200, default 50).", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "status → order state; deliverable → finished output; balance → prepaid balance.", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/StatusResponse" }, { "$ref": "#/components/schemas/DeliverableResponse" }, { "$ref": "#/components/schemas/BalanceResponse" } ] } } } },
          "400": { "description": "missing_ref | unknown_action", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "401": { "description": "invalid_api_key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "403": { "description": "no_permission | not_an_account_key (account/team reads)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "404": { "description": "order_not_found (wrong ref, or not this key's order)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "409": { "description": "not_ready (data.status) | order_refunded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "429": { "description": "rate_limited", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "500": { "description": "server_error — transient; retry with backoff", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-Api-Key", "description": "An sk_-prefixed API key. Self-provision one instantly via POST ?action=register (no auth, prepaid), create one in the client portal (Team -> Create key), or request one from the operator." }
    },
    "schemas": {
      "Envelope": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "error": { "type": "string", "description": "Machine-readable error code (empty on success)." },
          "data": { "type": "object", "additionalProperties": true }
        },
        "required": ["ok", "error", "data"]
      },
      "CreateOrderRequest": {
        "type": "object",
        "required": ["service_type", "request"],
        "properties": {
          "service_type": { "type": "string", "enum": ["research_brief", "competitor_brief", "extraction"], "description": "The service to run." },
          "request": { "type": "string", "description": "The research question / competitor set / extraction instructions." },
          "source_docs": { "type": "string", "description": "Document TEXT to extract from. Required for extraction. Max 300000 chars." },
          "source_docs_base64": { "type": "string", "description": "Base64 of a text file, decoded server-side. Use instead of source_docs." },
          "title": { "type": "string", "description": "Optional short label." },
          "priority": { "type": "string", "enum": ["low", "standard", "high"], "default": "standard" },
          "deliverable_format": { "type": "string", "enum": ["markdown", "csv", "json", "text"], "description": "markdown (default for briefs) | csv (default for extraction) | json (extraction only -> a JSON array) | text." },
          "idempotency_key": { "type": "string", "description": "Optional. Makes retries safe: the same key returns the same order instead of re-billing." }
        }
      },
      "CreateOrderResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean", "example": true },
          "error": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "order_ref": { "type": "string", "example": "SO-XXXX" },
              "status": { "type": "string", "example": "pending" },
              "service_type": { "type": "string" },
              "amount": { "type": "number" },
              "currency": { "type": "string", "example": "GBP" },
              "billing": { "type": "string" },
              "duplicate": { "type": "boolean", "description": "Present and true if an idempotent duplicate was returned." },
              "note": { "type": "string" }
            }
          }
        }
      },
      "StatusResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "error": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "order_ref": { "type": "string" },
              "status": { "type": "string", "enum": ["pending", "processing", "done", "failed", "cancelled"] },
              "service_type": { "type": "string" },
              "amount": { "type": "number" },
              "currency": { "type": "string" },
              "payment_status": { "type": "string" },
              "tokens": { "type": "integer" },
              "verify_outcome": { "type": "string", "nullable": true },
              "error": { "type": "string" },
              "created_at": { "type": "string" },
              "completed_at": { "type": "string" },
              "deliverable_ready": { "type": "boolean" }
            }
          }
        }
      },
      "DeliverableResponse": {
        "type": "object",
        "properties": {
          "ok": { "type": "boolean" },
          "error": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "order_ref": { "type": "string" },
              "status": { "type": "string", "example": "done" },
              "service_type": { "type": "string" },
              "deliverable_format": { "type": "string" },
              "deliverable": { "type": "string", "description": "The full finished output (markdown / CSV text / JSON string per deliverable_format)." }
            }
          }
        }
      },
      "RegisterResponse": {
        "type": "object",
        "description": "Response to POST ?action=register (no auth): a freshly provisioned prepaid key.",
        "properties": {
          "ok": { "type": "boolean", "example": true },
          "error": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "api_key": { "type": "string", "description": "The sk_ key — shown ONCE, store it now.", "example": "sk_..." },
              "key_prefix": { "type": "string" },
              "billing": { "type": "string", "example": "prepaid" },
              "balance": { "type": "number", "example": 0 },
              "currency": { "type": "string", "example": "GBP" },
              "top_up_url": { "type": "string", "description": "Open this (or hand it to the human paying) to fund the key via PayPal." },
              "guide_url": { "type": "string" },
              "note": { "type": "string" }
            }
          }
        }
      },
      "BalanceResponse": {
        "type": "object",
        "description": "Response to GET ?action=balance: the authenticated key's prepaid balance.",
        "properties": {
          "ok": { "type": "boolean" },
          "error": { "type": "string" },
          "data": {
            "type": "object",
            "properties": {
              "billing": { "type": "string", "enum": ["prepaid", "account"] },
              "balance": { "type": "number" },
              "currency": { "type": "string" },
              "top_up_url": { "type": "string", "description": "Present (non-empty) for a prepaid key; empty for an account-billed key." }
            }
          }
        }
      }
    }
  }
}
