{
  "openapi": "3.1.0",
  "info": {
    "title": "megachad cua",
    "version": "1.0.0",
    "summary": "Voice or text in, structured decisions out.",
    "description": "Two endpoints. POST /v1/decide: text in, decision out (POST /v1/decide/batch: up to 50 at once). WS /v1/listen: stream audio, get decisions as people talk (WebSocket, not describable in OpenAPI; see https://megachadcua.com/docs#listen). Billing: 1 credit = $0.0001. A decision is 1 credit per ~4,000 input tokens (min 1). Voice is 10 credits per second.",
    "contact": {
      "email": "hello@megachadcua.com",
      "url": "https://megachadcua.com/docs"
    }
  },
  "servers": [
    {
      "url": "https://api.megachadcua.com"
    }
  ],
  "security": [
    {
      "bearer": []
    }
  ],
  "paths": {
    "/v1/decide": {
      "post": {
        "operationId": "decide",
        "summary": "Make decisions from text",
        "description": "Answers every field in `schema` from `text`. Retries for ~2 s when busy, then 503 at_capacity with Retry-After. Failed calls are not billed. Retry 429, 409 idempotency_in_progress and 5xx with backoff, honoring Retry-After; never 400/401/402/403/422. Send an Idempotency-Key to make retries safe: for 24 h the same key and body get the first response back, not billed again.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "1 to 255 printable ASCII characters, unique per request (a UUID), reused on its retries. Same account, key and body within 24 h: the stored response (2xx or 400) comes back with idempotent-replayed: true and the first x-request-id, not billed. Still running: 409. Different body: 422. 402, 429 and 5xx aren't stored, so a retry runs again.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DecideRequest"
              },
              "example": {
                "schema": {
                  "action": {
                    "question": "What should the agent do?",
                    "options": [
                      "Click Sign in",
                      "Scroll down",
                      "Nothing yet"
                    ]
                  },
                  "confirm": {
                    "question": "Did the user confirm?",
                    "type": "yes_no"
                  },
                  "urgency": {
                    "question": "How urgent is it?",
                    "scale": [
                      "low",
                      "medium",
                      "high"
                    ]
                  }
                },
                "text": "yes, sign me in, quick"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Decisions",
            "headers": {
              "server-timing": {
                "schema": {
                  "type": "string"
                },
                "description": "auth, slot, model, gpu and total durations in ms"
              },
              "idempotent-replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                },
                "description": "Present on a stored response sent again for a repeated Idempotency-Key."
              },
              "x-request-id": {
                "schema": {
                  "type": "string"
                },
                "description": "Request id to quote in support emails. A replay carries the first request's."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DecideResponse"
                }
              }
            }
          },
          "400": {
            "description": "bad_request, bad_schema or bad_idempotency_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "payment_required, payment_failed or spend_limit",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "email_unverified",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_in_progress: a call with this Idempotency-Key is still running. Retry after Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "413": {
            "description": "too_large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_mismatch: this Idempotency-Key was used with a different body in the last 24 h",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "concurrency_limit or slow_down",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "model error (model_closed, timeout, ...)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "at_capacity or model_unavailable. Not billed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/v1/decide/batch": {
      "post": {
        "operationId": "decideBatch",
        "summary": "Make up to 50 decisions in one request",
        "description": "Each item is one /v1/decide call: same answer, same price, same request log row (under its session_id). Results come back in order. A failed item is {ok: false, error} and is not billed; the batch still answers 200. The whole batch is refused only for a bad key, the account (402, 403) or a malformed body (400, 413). Items run up to 4 at a time (2 on free credits). Out of credits or over the spend limit midway: the remaining items get payment_required or spend_limit and aren't started. An item refused with at_capacity or concurrency_limit is tried again with backoff before it fails. Deadline: the whole batch answers within 90 s. No item starts in the last 10 s and each item's model call is cut to the time left; items that didn't start or ran out of time get batch_timeout, not billed. Use a client timeout past 90 s (the SDKs wait 120 s). Idempotency-Key: the whole response is stored and replayed for 24 h, so a retried batch is never billed twice. While the batch runs, the key is locked for 120 s.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "As on /v1/decide. The whole batch response (failed items included) is stored and replayed with idempotent-replayed: true.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 255
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              },
              "example": {
                "schema": {
                  "topic": {
                    "question": "What is this ticket about?",
                    "options": [
                      "Billing",
                      "Bug",
                      "Other"
                    ]
                  }
                },
                "items": [
                  {
                    "id": "T-1",
                    "text": "I was charged twice this month"
                  },
                  {
                    "id": "T-2",
                    "text": "the app crashes when I log in"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One result per item, in order",
            "headers": {
              "idempotent-replayed": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                },
                "description": "Present on a stored response sent again for a repeated Idempotency-Key."
              },
              "x-request-id": {
                "schema": {
                  "type": "string"
                },
                "description": "The batch's id (bat_...). Each item's own id is its session_id; searching Logs for the batch id shows its items."
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "400": {
            "description": "bad_request (no items, over 50 items, not JSON), bad_schema (the shared schema) or bad_idempotency_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "invalid_key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "402": {
            "description": "payment_required, payment_failed or spend_limit, before any item ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "email_unverified",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "idempotency_in_progress",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "413": {
            "description": "too_large: body over 4 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "model_offline. Not billed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "retry-after": {
                "schema": {
                  "type": "integer"
                }
              }
            }
          }
        }
      }
    },
    "/v1/status": {
      "get": {
        "operationId": "status",
        "summary": "Model, voice and capacity status",
        "description": "No key. Cached for 10 s. `model` is the text model. `voice` is speech recognition, checked on its own every 5 minutes and by live sessions: it can be down while `model` is up. Then /v1/listen sessions still open and take text frames, and send {\"type\":\"warning\",\"code\":\"voice_unavailable\"} right after ready.",
        "security": [],
        "responses": {
          "200": {
            "description": "Status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Status"
                }
              }
            }
          }
        }
      }
    },
    "/v1/status/history": {
      "get": {
        "operationId": "statusHistory",
        "summary": "Uptime and latency history (90 days)",
        "security": [],
        "responses": {
          "200": {
            "description": "History",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "API key from https://megachadcua.com/app (mcc_live_...). Also accepted as x-api-key."
      }
    },
    "schemas": {
      "Field": {
        "type": "object",
        "description": "One decision. Give exactly one of options, scale, or type: yes_no.",
        "properties": {
          "question": {
            "type": "string",
            "description": "What the model answers. Defaults to the field name."
          },
          "options": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Pick one of these."
          },
          "scale": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "description": "Ordered levels, low to high."
          },
          "type": {
            "type": "string",
            "enum": [
              "yes_no"
            ]
          },
          "unknown": {
            "type": "boolean",
            "description": "Sockets only: false makes yes_no answer right away instead of null."
          }
        }
      },
      "Line": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "speaker": {
            "type": "string",
            "enum": [
              "user",
              "other"
            ]
          },
          "text": {
            "type": "string"
          }
        }
      },
      "DecideRequest": {
        "type": "object",
        "required": [
          "schema",
          "text"
        ],
        "properties": {
          "schema": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Field"
            },
            "description": "Field name to field. Max 64 KB as JSON."
          },
          "text": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Line"
                }
              }
            ],
            "description": "What was said. Max 200,000 characters."
          },
          "speaker": {
            "type": "string",
            "enum": [
              "user",
              "other"
            ],
            "default": "user"
          },
          "timeout_ms": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 60000,
            "default": 15000
          }
        }
      },
      "Answer": {
        "type": "object",
        "required": [
          "value",
          "confidence"
        ],
        "properties": {
          "value": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "boolean"
              }
            ]
          },
          "confidence": {
            "type": "number",
            "minimum": 0,
            "maximum": 1
          },
          "probabilities": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "Options fields: probability per option."
          },
          "level": {
            "type": "integer",
            "description": "Scale fields: 0-based index of value."
          }
        }
      },
      "DecideResponse": {
        "type": "object",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Answer"
            }
          },
          "transcript": {
            "type": "string"
          },
          "latency_ms": {
            "type": "integer",
            "description": "Our side, request in to answer out: the model's time plus the network hop inside our infrastructure (~90 ms from the US east coast). Your own round trip comes on top."
          },
          "model_ms": {
            "type": "number",
            "description": "The model's decision time (~17 ms)."
          },
          "usage": {
            "type": "object",
            "properties": {
              "credits": {
                "type": "integer"
              }
            }
          }
        }
      },
      "BatchItem": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Line"
                }
              }
            ]
          },
          "id": {
            "oneOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "number"
              }
            ],
            "description": "Yours, echoed back."
          },
          "schema": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Field"
            },
            "description": "This item's own schema. Wins over the shared one."
          },
          "speaker": {
            "type": "string",
            "enum": [
              "user",
              "other"
            ]
          },
          "timeout_ms": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 60000
          }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "description": "A shared schema, or one per item. Body up to 4 MB.",
        "properties": {
          "schema": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Field"
            },
            "description": "For every item without its own."
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/BatchItem"
            }
          },
          "speaker": {
            "type": "string",
            "enum": [
              "user",
              "other"
            ],
            "default": "user"
          },
          "timeout_ms": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 60000,
            "default": 15000,
            "description": "Per item, and never past the batch's 90 s deadline."
          }
        }
      },
      "BatchResult": {
        "type": "object",
        "required": [
          "id",
          "index",
          "ok"
        ],
        "properties": {
          "id": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ]
          },
          "index": {
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          },
          "session_id": {
            "type": "string",
            "description": "ok items: the item's own request id, as in its log row."
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Answer"
            }
          },
          "latency_ms": {
            "type": "integer",
            "description": "This item, our side, as on /v1/decide."
          },
          "usage": {
            "type": "object",
            "properties": {
              "credits": {
                "type": "integer"
              }
            }
          },
          "error": {
            "type": "object",
            "description": "Failed items (not billed): the code /v1/decide would give, or payment_required / spend_limit (not started), batch_timeout (the 90 s deadline: not started, or ran out of time), server_error.",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "BatchResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchResult"
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "credits": {
                "type": "integer",
                "description": "Sum over the answered items."
              }
            }
          },
          "latency_ms": {
            "type": "integer",
            "description": "The whole batch, our side."
          }
        }
      },
      "Status": {
        "type": "object",
        "properties": {
          "model": {
            "type": "string",
            "enum": [
              "up",
              "down",
              "unconfigured"
            ],
            "description": "The text model (POST /v1/decide)."
          },
          "voice": {
            "type": "string",
            "enum": [
              "up",
              "down",
              "unknown"
            ],
            "description": "Speech recognition (audio on WS /v1/listen). down: the model is down, or it is up but can't hear; send text frames or use /v1/decide. unknown: not checked yet."
          },
          "slots": {
            "type": [
              "integer",
              "null"
            ]
          },
          "in_use": {
            "type": "integer"
          },
          "free": {
            "type": [
              "integer",
              "null"
            ]
          },
          "demo": {
            "type": "boolean"
          },
          "today": {
            "type": "object",
            "properties": {
              "decisions": {
                "type": "integer"
              },
              "fastest_ms": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    }
  }
}
