# DecompilerAI public API - OpenAPI 3.1
# Served at https://decompiler.ai/openapi.yaml; human docs at https://decompiler.ai/api
openapi: 3.1.0
info:
  title: DecompilerAI API
  version: "1.0.0"
  description: |
    Headless REST API for DecompilerAI: upload a binary, list its functions,
    decompile one function or the whole program, and fetch the reconstructed
    C (or a .zip project).

    Authentication uses API keys (prefix `dcai_`), created on your profile
    page (https://decompiler.ai/profile, section "API keys"). Send the key as
    an `X-API-Key` header or as `Authorization: Bearer dcai_...`. API keys are
    separate from browser sessions: a session token is never accepted here and
    an API key never works as a login. The analysis API (tag `analysis`, the
    routes the inspector and the agent tools use) takes the key as a bearer token.

    Billing is metered in the same credits as the web UI: a decompile request
    charges exactly what the same action costs in the browser (prices are in
    `GET /api/v1/me`). Insufficient credits answer `402`. A key may carry an
    optional monthly credit cap; when it is exhausted, billable calls answer
    `402` until the next calendar month (the cap is reserved atomically before
    a billable request runs and given back when nothing ran). Every key is
    rate limited per minute (default 60 requests/min, settable per key via the
    `rate_per_min` field of `POST /api/keys`, up to the server ceiling of 600);
    exceeding it answers `429` with a `Retry-After` header.

    Engine x format: `decai` decompiles ELF/x86-64 only; `ghidra` and `gpt`
    decompile ELF and PE; Mach-O uploads can be browsed but not decompiled
    yet. A refused combination answers `415` (whole program) or `400` (one
    function) with the reason, before any credit or cap is touched.
  contact:
    url: https://decompiler.ai/contact
servers:
  - url: https://decompiler.ai
security:
  - ApiKeyHeader: []
  - ApiKeyBearer: []
tags:
  - name: binaries
    description: Upload and inspect binaries you own
  - name: decompile
    description: Start decompilations and fetch their C output
  - name: account
    description: Key, credits and prices
  - name: analysis
    description: |
      The analysis API behind the inspector and the agent tools (`/api/analysis/{file}/...`):
      the same dcai_ key as a bearer token (`Authorization: Bearer dcai_...`). A read-only key
      may only GET (any other method answers 403). Listed here: the project, snapshot,
      correction, marks, flow-repair, debug-info, header-import and Android routes.
paths:
  /api/v1/binaries:
    post:
      tags: [binaries]
      operationId: uploadBinary
      summary: Upload a binary (multipart form, field name `file`)
      description: |
        Accepts the containers the analysis backend understands (ELF and
        PE/EXE, Mach-O as it lands; max 25 MB globally; your plan may allow
        less and limits how many files you can keep at once).
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Stored. `id` and `name` both work as `{id}` in later calls.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/UploadResult"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "409":
          description: Plan file-count limit reached
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "413":
          description: File larger than the global or plan limit
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "415":
          description: Not a supported binary container
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
    get:
      tags: [binaries]
      operationId: listBinaries
      summary: List your binaries (with latest job and finished runs)
      responses:
        "200":
          description: Up to 200 newest uploads
          content:
            application/json:
              schema:
                type: object
                properties:
                  binaries:
                    type: array
                    items: { $ref: "#/components/schemas/BinarySummary" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/v1/binaries/{id}:
    get:
      tags: [binaries]
      operationId: getBinary
      summary: Summary and status of one binary
      parameters:
        - $ref: "#/components/parameters/BinaryId"
      responses:
        "200":
          description: Summary, latest whole-program job, finished runs
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BinarySummary" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/v1/binaries/{id}/functions:
    get:
      tags: [binaries]
      operationId: listFunctions
      summary: Function list of a binary
      parameters:
        - $ref: "#/components/parameters/BinaryId"
        - { name: q, in: query, schema: { type: string }, description: Name filter }
        - { name: offset, in: query, schema: { type: integer, minimum: 0 } }
        - { name: limit, in: query, schema: { type: integer, minimum: 0 } }
      responses:
        "200":
          description: "Functions with addresses and sizes (analysis backend shape). Served through the same result cache and analysis queue as the web inspector (`X-Analysis-Cache: hit|miss`)."
          content:
            application/json:
              schema:
                type: object
                properties:
                  total: { type: integer }
                  functions:
                    type: array
                    items:
                      type: object
                      properties:
                        name: { type: string }
                        addr: { type: integer }
                        size: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
  /api/v1/binaries/{id}/forge:
    get:
      tags: [binaries]
      operationId: forgeMatches
      summary: Library functions recognized by FORGE (signature identification)
      description: |
        FORGE matches the binary's functions against a database of library functions
        compiled by us (generation, not curation). A match names the function (lib,
        version, tier 0 exact / 1 normalized / 2 structural, confidence = how many
        independent builds agree). Firm matches (confidence >= 0.75) are skipped by
        whole-program runs and not charged unless `include_forge` is set; tentative
        ones are named with a question mark in the web UI and decompiled normally.
        Same JSON as the web inspector; `{"available": false}` when no FORGE database
        is loaded on the server.
      parameters:
        - $ref: "#/components/parameters/BinaryId"
      responses:
        "200":
          description: FORGE matches (cached per binary and database)
          content:
            application/json:
              schema:
                type: object
                properties:
                  available: { type: boolean }
                  functions_total: { type: integer }
                  matched: { type: integer }
                  by_tier: { type: object, additionalProperties: { type: integer } }
                  db: { type: object, properties: { built_at: { type: [string, "null"] }, libs: { type: array, items: { type: object } } } }
                  matches:
                    type: array
                    items:
                      type: object
                      properties:
                        addr: { type: integer }
                        size: { type: integer }
                        name: { type: string }
                        lib: { type: string }
                        version: { type: string }
                        tier: { type: integer, description: 0 exact, 1 normalized, 2 structural }
                        confidence: { type: number, description: ">= 0.75 is firm (skipped by whole-program runs)" }
                        support: { type: integer, description: independent builds that agree }
                        builds_total: { type: integer }
                        func_id: { type: integer }
                        alternatives: { type: array, items: { type: object } }
                        reason: { type: string }
                  note: { type: [string, "null"] }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
  /api/v1/binaries/{id}/decompile:
    post:
      tags: [decompile]
      operationId: decompileBinary
      summary: Decompile one function (synchronous) or the whole program (job)
      description: |
        With `addr`: decompiles that one function and returns the C in the
        response (billed like the web UI's per-function run; results are
        cached per function, engine and level, and cached answers are free).
        Per-function levels are l0, l0-l1, l0-l4, l0-l1-l4 (L2/L3 need the
        whole program and are clamped to L1).

        Without `addr`: starts a whole-program run and answers `202` with a
        `job_id` to poll at `/api/v1/jobs/{id}`. When it finishes, fetch the C
        via `/api/v1/binaries/{id}/decompiled`. A run the engine cannot do for
        this binary format (e.g. `decai` on a PE, any engine on a Mach-O)
        answers `415`; a full server queue answers `503` with `Retry-After`.
        Neither starts a job nor touches credits or the key's monthly cap.
        Library functions recognized by FORGE (see `/forge`) are skipped by a
        whole-program run - named from the database with their real source text
        in the output, not decompiled, not charged - unless `include_forge` is
        true; `estimated_credits` and `forge_skipped` in the 202 reflect that.
      parameters:
        - $ref: "#/components/parameters/BinaryId"
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                engine:
                  type: string
                  enum: [ghidra, gpt, decai]
                  default: ghidra
                level:
                  type: string
                  description: Stage token, e.g. l0, l0-l4, l0-l1-l2-l3-l4
                  default: l0
                addr:
                  type: [string, integer]
                  description: Function address (hex string like "0x1140" or integer). Omit for a whole-program run.
                size: { type: integer, description: Function size in bytes (optional hint) }
                name: { type: string, description: "Function name (optional, for nicer output)" }
                force: { type: boolean, description: Re-run even when a cached result exists (re-billed) }
                include_forge: { type: boolean, default: false, description: "Whole program only: decompile the FORGE-recognized library functions too (charged as before) instead of skipping them" }
      responses:
        "200":
          description: Per-function result
          content:
            application/json:
              schema: { $ref: "#/components/schemas/FunctionDecompilation" }
        "202":
          description: Whole-program run started (job_id is set once the job row exists)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProgramRunStarted" }
        "400":
          description: Malformed input, or (with `addr`) an engine the binary format does not support (e.g. `decai` on a PE)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PaymentRequired" }
        "403":
          description: Level needs a Pro or Team plan (verify stages L1-L3)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "404": { $ref: "#/components/responses/NotFound" }
        "415":
          description: Whole-program run refused for this engine x binary format (decai needs ELF/x86-64; Mach-O is not decompilable yet)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "429": { $ref: "#/components/responses/TooManyRequests" }
        "503":
          description: Whole-program queue is full; retry after `Retry-After` seconds (`retry_after_s` in the body)
          headers:
            Retry-After: { schema: { type: integer }, description: Seconds to wait }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /api/v1/jobs/{id}:
    get:
      tags: [decompile]
      operationId: getJob
      summary: Whole-program job status
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: integer }
      responses:
        "200":
          description: Job with per-stage progress
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Job" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/v1/binaries/{id}/decompiled:
    get:
      tags: [decompile]
      operationId: getDecompiled
      summary: The C output of one finished run (JSON, or a single .c file)
      parameters:
        - $ref: "#/components/parameters/BinaryId"
        - $ref: "#/components/parameters/Engine"
        - $ref: "#/components/parameters/Level"
        - name: format
          in: query
          schema: { type: string, enum: [json, c], default: json }
      responses:
        "200":
          description: The run's program C and per-function code
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DecompiledProgram" }
            text/plain:
              schema: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404":
          description: Binary unknown, or no finished run for this engine/level
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /api/v1/binaries/{id}/decompiled.zip:
    get:
      tags: [decompile]
      operationId: getDecompiledZip
      summary: The run as a .zip project (program.c, functions/, header, Makefile)
      parameters:
        - $ref: "#/components/parameters/BinaryId"
        - $ref: "#/components/parameters/Engine"
        - $ref: "#/components/parameters/Level"
      responses:
        "200":
          description: ZIP archive
          content:
            application/zip:
              schema: { type: string, format: binary }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/v1/me:
    get:
      tags: [account]
      operationId: getMe
      summary: Account, key, credit balance and price list
      responses:
        "200":
          description: Who you are and what actions cost
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Me" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/annotations:
    post:
      tags: [analysis]
      operationId: setAnnotation
      summary: Store (or delete) one user edit at an address
      description: |
        Every user edit is one annotation row keyed by (binary, addr, kind); an empty
        `value` deletes it. Besides names, comments, prototypes and variables:

        - code vs data (I01): `data` {type, count, str?} defines data (`str` ascii |
          utf16 marks a string of `count` units), `code` {} forces a disassembly start,
          kind `undefine` (not stored) removes the data item or code mark that
          contains `addr`, like the inspector's U.
        - flow repairs (I02/I05): `func` {name?, size? | end?, chunks?: [{addr, size | end}]}
          defines a function or sets its end and extra chunks; `spdelta` {delta} an
          instruction's stack-pointer change; `noreturn` {noreturn: bool} at a function
          entry; `jumptable` {targets: [...]} or a table to read {table, count,
          entry_size, base?, shift?, signed?} at an indirect jump.
        - decompiler corrections (I04): `callsite` {proto} at a call instruction,
          `unionfield` {union, field}, `splitvar` {var, name} at a function.
        - C++ (I13): `vcall` {targets: [...]} at a (virtual) call instruction: the
          functions it reaches (xrefs and call graph edges, Ghidra's C unchanged);
          `vtable_class` {name} at a vtable's address point.

        A read-only key answers 403.
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [addr, kind]
              properties:
                addr: { type: [integer, string], description: "Address, decimal or 0x hex" }
                kind:
                  type: string
                  enum: [name, comment, var, vartype, forge_reject, proto, data, func, bookmark, rcomment, callsite, unionfield, splitvar, code, spdelta, noreturn, jumptable, vcall, vtable_class, undefine]
                value: { description: "The kind's value (string or object); empty deletes" }
      responses:
        "200":
          description: Stored (`deleted` when the value was empty; `removed` = the rows an undefine dropped)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Ok" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
  /api/analysis/{file}/project:
    get:
      tags: [analysis]
      operationId: getProject
      summary: The whole user state of a binary as one versioned document (I07)
      description: Every annotation, the type header, loader settings and the patch set. Any `download` value answers it as an attachment.
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: download, in: query, schema: { type: string }, description: Any value adds Content-Disposition attachment }
      responses:
        "200":
          description: The project document
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProjectDocument" }
        "401": { $ref: "#/components/responses/Unauthorized" }
    put:
      tags: [analysis]
      operationId: restoreProject
      summary: Restore a project document (or a snapshot) exactly
      description: The binary's user state becomes the document's; the answer is the per-kind diff that was applied. POST is accepted as well.
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ProjectRestore" }
      responses:
        "200":
          description: Restored; the per-kind diff and notes
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProjectDiff" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
  /api/analysis/{file}/project-diff:
    post:
      tags: [analysis]
      operationId: diffProject
      summary: What restoring a document (or a snapshot) would change, without changing anything
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/ProjectRestore" }
      responses:
        "200":
          description: The per-kind diff; `sha_match` is false when the document belongs to another binary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProjectDiff" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/snapshots:
    get:
      tags: [analysis]
      operationId: listSnapshots
      summary: Named project snapshots of a binary (I07)
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      responses:
        "200":
          description: Newest first, with the per-binary maximum
          content:
            application/json:
              schema:
                type: object
                properties:
                  snapshots: { type: array, items: { $ref: "#/components/schemas/Snapshot" } }
                  max: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [analysis]
      operationId: saveSnapshot
      summary: Save the current project state under a name
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name: { type: string, maxLength: 128, description: "Defaults to Snapshot <date>" }
      responses:
        "200":
          description: Saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  snapshot: { $ref: "#/components/schemas/Snapshot" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
        "409": { $ref: "#/components/responses/Conflict" }
        "413": { $ref: "#/components/responses/TooLarge" }
  /api/analysis/{file}/snapshots/{snapshot}:
    get:
      tags: [analysis]
      operationId: getSnapshot
      summary: The stored document of one snapshot
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: snapshot, in: path, required: true, schema: { type: integer } }
      responses:
        "200":
          description: The project document
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ProjectDocument" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    delete:
      tags: [analysis]
      operationId: deleteSnapshot
      summary: Delete one snapshot
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: snapshot, in: path, required: true, schema: { type: integer } }
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Ok" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/analysis/{file}/transfer:
    get:
      tags: [analysis]
      operationId: previewTransfer
      summary: Preview carrying an older upload's analysis into this binary (I14); changes nothing
      description: "The two versions are paired by the function diff (kind, confidence, basis exact or heuristic, reason). Each item is one annotation of the source (name, prototype, local names into identical functions, function or instruction comment, bookmark) with the address it lands on here, the value this binary has there now (status conflict), how it was mapped and whether it goes by default (new and confidence of at least 0.75). Ambiguous functions (several candidates) carry nothing until `choose` names the candidate. `header` lists the type declarations this binary lacks; `skipped` counts what is not carried and why."
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: source, in: query, required: true, schema: { type: string }, description: "Stored name of the older upload (one of the caller's)" }
        - { name: choose, in: query, required: false, schema: { type: string }, description: "JSON object {old function address: new address}: the user's pick among an ambiguous function's candidates" }
      responses:
        "200":
          description: The preview
          content:
            application/json:
              schema:
                type: object
                properties:
                  source: { type: object }
                  target: { type: object }
                  scope: { type: object, description: "Functions compared on each side and the engine" }
                  items: { type: array, items: { type: object } }
                  header: { type: object, nullable: true }
                  skipped: { type: object }
                  counts: { type: object }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
    post:
      tags: [analysis]
      operationId: applyTransfer
      summary: Apply the transfer as one batch with a transfer id (undo it with transfer/{id}/undo)
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [source]
              properties:
                source: { type: string }
                choose: { type: object, description: "Picks among ambiguous candidates, as for the preview" }
                include: { type: array, items: { type: string }, description: "Item ids of the preview (and header); absent = the default items" }
      responses:
        "200":
          description: Written; the rows with their value before and after, per-kind counts
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  transfer: { type: object, properties: { id: { type: integer } } }
                  applied: { type: object }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
  /api/analysis/{file}/transfers:
    get:
      tags: [analysis]
      operationId: listTransfers
      summary: The transfers into this binary, newest first
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      responses:
        "200":
          description: "{transfers: [{id, source, created_at, undone_at, summary}]}"
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/transfer/{id}/undo:
    post:
      tags: [analysis]
      operationId: undoTransfer
      summary: Revert one transfer; rows changed since it are kept and listed
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: id, in: path, required: true, schema: { type: integer } }
      responses:
        "200":
          description: "{restored, kept: [{kind, addr, why}]}"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { description: Undone already }
  /api/analysis/{file}/debug/start:
    post:
      tags: [analysis]
      operationId: debugStart
      summary: Start a live debug session (the emulator stays paused between commands)
      description: "One session per file, the same the user sees in the inspector's Emulate tab (it follows every step, labelled with who did it). mode function / cursor run one function or from an address in the jailed emulator (free, per-user cap); mode program runs the whole program from its entry under a real loader (costs `emulate.session.program` credits per START, plan caps; send max_price = the price you accept: a higher price answers 428 with the price, nothing is charged). `config` starts a saved run configuration; the body's other fields override its fields."
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                config: { type: string, description: Name of a saved run configuration }
                mode: { type: string, enum: [function, cursor, program], default: function }
                addr: { type: string, description: "Function / start address (function, cursor)" }
                args: { type: array, items: { type: string }, description: Integer arguments (the ABI's registers / stack) }
                abi: { type: string, enum: [sysv, win64] }
                os: { type: string, enum: [none, linux, windows], description: The OS layer of a function session }
                max_insns: { type: integer, description: Instructions one run may take (function sessions) }
                start_at: { type: string, enum: [main, entry], description: Where a whole program stops first }
                argv: { type: array, items: { type: string } }
                env: { type: object, additionalProperties: { type: string } }
                stdin: { type: string }
                stdin_b64: { type: string }
                stdin_eof: { type: boolean }
                files: { type: array, items: { type: object, properties: { name: { type: string }, b64: { type: string } } }, description: "The program's working directory (8 MB together)" }
                set_regs: { type: array, items: { type: object } }
                writes: { type: array, items: { type: object } }
                skip_calls: { type: array, items: { type: object } }
                breakpoints: { type: array, items: { $ref: "#/components/schemas/DebugBreakpoint" } }
                watches: { type: array, items: { type: string } }
                hooks: { type: array, items: { type: object, properties: { api: { type: string }, addr: { type: string }, action: { type: string, enum: [log, break, return] }, value: { type: string } } } }
                max_price: { type: integer, description: The credits per start the caller accepts (whole program) }
                replace: { type: boolean, description: End the open session of the same kind first }
      responses:
        "200":
          description: "Started and paused. `X-Patch-Rev` names the patch set it runs."
          content:
            application/json:
              schema:
                type: object
                properties:
                  sid: { type: string }
                  state: { $ref: "#/components/schemas/DebugState" }
                  charged: { type: integer, description: Credits taken (whole program) }
                  bps: { type: array, items: { $ref: "#/components/schemas/DebugBreakpoint" } }
                  setup_errors: { type: array, items: { type: string }, description: The breakpoints / watches / hooks that were refused, with the reason }
                  config: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PaymentRequired" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
        "404": { $ref: "#/components/responses/NotFound" }
        "409": { $ref: "#/components/responses/Conflict" }
        "428":
          description: A whole-program start costs more than max_price (the body names the price)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /api/analysis/{file}/debug/session:
    get:
      tags: [analysis]
      operationId: debugSession
      summary: The open debug session of the file, what can be started here, the saved run configurations
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      responses:
        "200":
          description: The session (null when none) with its last state, console and breakpoints
          content:
            application/json:
              schema:
                type: object
                properties:
                  session:
                    type: [object, "null"]
                    properties:
                      sid: { type: string }
                      mode: { type: string }
                      running: { type: boolean }
                      state: { $ref: "#/components/schemas/DebugState" }
                      console: { type: array, items: { type: object } }
                      bps: { type: array, items: { $ref: "#/components/schemas/DebugBreakpoint" } }
                  engines:
                    type: object
                    description: "{function, program, program_info: {os, arch, price, available, caps} or {why}}"
                  run_configs: { type: array, items: { type: object, properties: { name: { type: string }, mode: { type: string } } } }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/debug/{sid}/{cmd}:
    post:
      tags: [analysis]
      operationId: debugCommand
      summary: One command of a debug session (sid `current` = the caller's session on this file)
      description: "Commands continue, step_into, step_over, step_out, run_to {addr}, pause, restart, stop, state, set_reg {name, value}, set_flag {name, value}, set_pc {addr}, write_mem {addr, hex}, read_mem {addr, n}, memory_map, bp_set {bp_id?, kind, addr?, end?, access?, api?, condition?, hit_count?, log?, enabled?}, bp_del {bp_id}, bp_list, watch_set {expr}, watch_del {watch_id}, eval {expr}, stdin_feed {text | b64, eof?}, file_list, file_get {name}, file_put {name, b64}, log {since, limit} (the API / syscall log), time travel step_back, run_back, seek {pos}, timeline, snapshot, snapshot_list, snapshot_restore {snap_id}, compare {snap_id}, origin {reg | addr, size}, coverage, coverage_clear, hook_set {api | addr, action, value?}. A run answers {running: true} at once and its stop arrives on the socket; with ?wait=<seconds> it answers only once it stopped again (paused when the time is up). A read-only key may send state, read_mem, memory_map, eval, log, file_list, file_get and bp_list."
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: sid, in: path, required: true, schema: { type: string }, description: "The session id, or current" }
        - { name: cmd, in: path, required: true, schema: { type: string } }
        - { name: wait, in: query, schema: { type: integer, minimum: 0, maximum: 120 }, description: Seconds a run may take before it is paused and the state returned }
        - { name: tail, in: query, schema: { type: integer, enum: [0, 1] }, description: Include console_tail (the program's last output) }
      requestBody:
        content:
          application/json:
            schema: { type: object, description: "The command's arguments" }
      responses:
        "200":
          description: "The worker's answer: {state} / {running} / the command's own fields; with wait {state, waited_ms, timed_out?}"
          content:
            application/json:
              schema:
                type: object
                properties:
                  state: { $ref: "#/components/schemas/DebugState" }
                  running: { type: boolean }
                  waited_ms: { type: integer }
                  timed_out: { type: boolean }
                  console_tail: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
        "404": { $ref: "#/components/responses/NotFound" }
        "410":
          description: The session has ended
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
  /api/analysis/{file}/run-configs:
    get:
      tags: [analysis]
      operationId: listRunConfigs
      summary: The saved run configurations of the debugger for this binary
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      responses:
        "200":
          description: By name, with the per-binary maximum
          content:
            application/json:
              schema:
                type: object
                properties:
                  configs: { type: array, items: { $ref: "#/components/schemas/RunConfig" } }
                  max: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/run-configs/{name}:
    put:
      tags: [analysis]
      operationId: saveRunConfig
      summary: Save (create or replace) a run configuration
      description: "Checked like a start of this binary (addresses, arguments, registers); nothing runs. create true refuses an existing name (409). Part of the project document (items) and its snapshots."
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: name, in: path, required: true, schema: { type: string, maxLength: 64 } }
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required: [config]
              properties:
                config: { $ref: "#/components/schemas/RunConfig" }
                create: { type: boolean }
      responses:
        "200":
          description: Saved
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  created: { type: boolean }
                  config: { $ref: "#/components/schemas/RunConfig" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
        "409": { $ref: "#/components/responses/Conflict" }
    delete:
      tags: [analysis]
      operationId: deleteRunConfig
      summary: Delete a run configuration
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: name, in: path, required: true, schema: { type: string } }
      responses:
        "200":
          description: Deleted
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Ok" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
        "404": { $ref: "#/components/responses/NotFound" }
  /api/analysis/{file}/corrections:
    get:
      tags: [analysis]
      operationId: correctionSites
      summary: The decompiler-correction sites of a function (I04)
      description: Call sites, union accesses and splittable variables of the function's last Ghidra decompilation, with the corrections already set (`set`). Without a decompilation a `hint` says to decompile first (free).
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: addr, in: query, required: true, schema: { type: string }, description: Function address }
      responses:
        "200":
          description: Sites and current corrections
          content:
            application/json:
              schema:
                type: object
                properties:
                  addr: { type: integer }
                  decompiled: { type: boolean }
                  calls: { type: array, items: { type: object } }
                  unions: { type: array, items: { type: object } }
                  splits: { type: array, items: { type: object } }
                  set: { type: object, description: "The corrections set, per kind by address" }
                  hint: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/member-xrefs:
    get:
      tags: [analysis]
      operationId: memberXrefs
      summary: Every decompiled line that uses a struct / union member (I04)
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: type, in: query, required: true, schema: { type: string }, description: Struct or union name }
        - { name: field, in: query, schema: { type: string }, description: One member (every member when omitted) }
        - { name: access, in: query, schema: { type: string }, description: "I11: comma list of read / write / address / unknown (every kind when omitted)" }
      responses:
        "200":
          description: Uses over the functions decompiled with Ghidra so far (`exact` = rows with Ghidra's own member tokens, `text` = older rows searched as text), each with its access kind (I11)
          content:
            application/json:
              schema:
                type: object
                properties:
                  type: { type: string }
                  field: { type: [string, "null"] }
                  access: { type: [array, "null"], items: { type: string } }
                  file: { type: string }
                  engine: { type: string }
                  rev: { type: string, description: The patch revision of the bytes read }
                  scanned: { type: integer }
                  exact: { type: integer }
                  text: { type: integer }
                  functions_total: { type: [integer, "null"] }
                  not_decompiled: { type: [integer, "null"] }
                  counts: { type: object, description: "uses per kind {read, write, address, unknown} (before the access filter)" }
                  stale: { type: array, description: "functions decompiled before access kinds existed: kind unknown until decompiled again", items: { type: object } }
                  uses:
                    type: array
                    items:
                      type: object
                      properties:
                        addr: { type: integer, description: The using function }
                        fn: { type: string }
                        line: { type: integer }
                        field: { type: string }
                        kind: { type: string, enum: [read, write, address, unknown] }
                        insn: { type: [integer, "null"], description: The instruction of the access when Ghidra gives one }
                        source: { type: string, enum: [ghidra, text] }
                        text: { type: string }
                        stale: { type: boolean }
                        rev_note: { type: string }
                  truncated: { type: integer }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/paths:
    get:
      tags: [analysis]
      operationId: callPaths
      summary: Call-graph paths between two functions (I11 connection finder, IDA Pathfinder)
      description: "Simple paths from `from` to `to` over the static call graph, shortest first. A call-graph connection, not a data-flow or reachability proof: unresolved indirect calls on the way are listed in `unresolved`, never followed."
  /api/analysis/{file}/flow/{kind}:
    get:
      tags: [analysis]
      operationId: flowQuery
      summary: Static value queries over Ghidra's P-code - call arguments, value origin, uses, bounds check (I12)
      description: "Read-only, nothing runs the binary. kind call-args lists every call site of `callee` with argument `arg`; origin follows one value back (def-use chain) to constants, parameters, call returns or memory; uses follows it forward; check says whether a compare on the value decides every path to the call. Name the value with site + arg (an argument), site alone (the call's return value), fn + param (a parameter) or addr (+ var). A value is exact (constant, or a set of constants) only when every path ends in constants, else unknown with its origins. Answers are tagged with the patch revision they were made from (rev) and use the user's types."
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: from, in: query, required: true, schema: { type: string }, description: Start function address (hex or decimal) }
        - { name: to, in: query, required: true, schema: { type: string }, description: Target function address }
        - { name: depth, in: query, schema: { type: integer, minimum: 1, maximum: 12, default: 6 }, description: Max calls per path }
        - { name: max, in: query, schema: { type: integer, minimum: 1, maximum: 200, default: 20 }, description: Max paths }
        - { name: lib, in: query, schema: { type: string, enum: ["0", "1"], default: "1" }, description: "1 = imports and FORGE library functions are never hops" }
        - { name: refs, in: query, schema: { type: string, enum: ["0", "1"], default: "0" }, description: "1 = an address-taken function counts as reached (callbacks)" }
      responses:
        "200":
          description: The paths, what was scanned and what could not be followed
          content:
            application/json:
              schema:
                type: object
                properties:
                  note: { type: string }
                  engine: { type: string, description: "xref index (ELF x86) or linear sweep" }
                  shortest: { type: [integer, "null"] }
                  truncated: { type: boolean }
                  truncated_reason: { type: string }
                  scanned: { type: object, description: "{functions, edges, excluded_library, excluded_imports}" }
                  paths:
                    type: array
                    items:
                      type: object
                      properties:
                        length: { type: integer }
                        hops: { type: array, items: { type: object, description: "{addr, name, edge: call | tail | ref, indirect?, library?, external?}" } }
                  unresolved: { type: array, items: { type: object, description: "{addr, name, n}: a function within reach making n unresolved indirect calls" } }
                  indirect_tracked: { type: boolean }
                  library_known: { type: integer, description: FORGE library functions known for this file }
                  file: { type: string }
                  rev: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        - { name: kind, in: path, required: true, schema: { type: string, enum: [call-args, origin, uses, check] } }
        - { name: callee, in: query, schema: { type: string }, description: "call-args: a function name (an import such as memcpy, a symbol, your rename, sub_<hex>) or address" }
        - { name: arg, in: query, schema: { type: integer, minimum: 1, maximum: 64 }, description: "Argument number, 1 = the first" }
        - { name: site, in: query, schema: { type: string }, description: A call instruction address }
        - { name: fn, in: query, schema: { type: string }, description: A function (address or name); with param }
        - { name: param, in: query, schema: { type: integer, minimum: 1, maximum: 64 } }
        - { name: addr, in: query, schema: { type: string }, description: An instruction address (with end, a range such as a pseudocode line) }
        - { name: end, in: query, schema: { type: string } }
        - { name: var, in: query, schema: { type: string }, description: "A variable as Ghidra's C names it (local_10, param_2, uVar1)" }
        - { name: depth, in: query, schema: { type: integer, minimum: 1, maximum: 32 } }
        - { name: interproc, in: query, schema: { type: string, enum: ["0", "1"] }, description: "Follow a parameter one step into the direct callers" }
        - { name: max, in: query, schema: { type: integer, minimum: 1, maximum: 200 }, description: "call-args: at most this many call sites" }
      responses:
        "200":
          description: "{ok, kind, file, engine: ghidra, rev, patched, cached, query, result, type_errors?}; result.sites (call-args), result.summary + result.chain (origin), result.uses (uses), result.verdict + result.guards (check), result.scope says what was covered"
          content:
            application/json:
              schema: { type: object }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "422": { description: "Ghidra could not answer this query (no call there, the function did not decompile, ...)" }
        "502": { description: "The Ghidra run failed (platform failure)" }
  /api/analysis/{file}/types/import:
    post:
      tags: [analysis]
      operationId: importHeader
      summary: Append a C header file to the binary's type header (I09)
      description: Includes are resolved against the shipped type libraries (or skipped with a note), the file is checked with its own line numbers, preprocessed and reduced to declarations. Its function declarations reach Ghidra by name.
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [text]
              properties:
                text: { type: string, description: "The .h file's text" }
                name: { type: string, description: "File name for messages (default header.h)" }
      responses:
        "200":
          description: Appended
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok: { type: boolean }
                  header: { type: string, description: The whole type header now }
                  added: { type: string }
                  notes: { type: array, items: { type: string } }
        "400":
          description: The header does not compile (`errors` with line, col, message, where - the file's own errors; `conflicts` - its lines that redefine a type of the enabled type libraries, each with `library` {line, message}; `libraries` - those libraries) or is too large
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Error" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
  /api/analysis/{file}/debug:
    get:
      tags: [analysis]
      operationId: getDebugFile
      summary: The separate DWARF debug file of an ELF / Mach-O (I09)
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      responses:
        "200":
          description: Whether one applies to this format and is present
          content:
            application/json:
              schema:
                type: object
                properties:
                  applies: { type: boolean }
                  format: { type: string }
                  present: { type: boolean }
                  size: { type: integer }
                  max_mb: { type: integer }
        "401": { $ref: "#/components/responses/Unauthorized" }
    post:
      tags: [analysis]
      operationId: uploadDebugFile
      summary: Attach a debug file (objcopy --only-keep-debug output, a .dSYM's DWARF file)
      description: Matched by build-id, else the .gnu_debuglink CRC (ELF), or LC_UUID (Mach-O); a file of another build answers 409. The analysis and Ghidra re-read the binary with it.
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [debug]
              properties:
                debug: { type: string, format: binary }
      responses:
        "200":
          description: Attached
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Ok" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
        "409": { $ref: "#/components/responses/Conflict" }
        "413": { $ref: "#/components/responses/TooLarge" }
    delete:
      tags: [analysis]
      operationId: deleteDebugFile
      summary: Remove the debug file
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      responses:
        "200":
          description: Removed (`removed` is false when there was none)
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Ok" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "403": { $ref: "#/components/responses/ReadOnlyKey" }
  /api/analysis/{file}/classes:
    get:
      tags: [analysis]
      operationId: cxxClasses
      summary: The C++ classes recovered from the RTTI (I13)
      description: "Itanium (gcc / clang / mingw) and MSVC (Complete Object Locators), stripped binaries too. Per class - bases with offset / virtual / public, derived classes, every vtable (primary + one per base subobject) with its slots (index, offset, target, pure, status new / override / inherited, the implementations a call through the slot can reach) and the layout of the user's struct of that name."
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: limit, in: query, schema: { type: integer }, description: At most this many classes (5000) }
      responses:
        "200": { $ref: "#/components/responses/AnalysisResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/vcalls:
    get:
      tags: [analysis]
      operationId: cxxVirtualCalls
      summary: Virtual call sites and the implementations each can reach (I13)
      description: "Per site - slot, object (this / parameter / global / unknown), static type, status exact (known vtable, or one implementation for the static type in this binary) / heuristic (a candidate list) / unresolved / user (targets the user set), the candidates. Static block dataflow per function; scope in `scanned`."
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: addr, in: query, schema: { type: string }, description: The sites in this function }
        - { name: target, in: query, schema: { type: string }, description: Only the sites that may call this function }
        - { name: limit, in: query, schema: { type: integer }, description: At most this many sites }
      responses:
        "200": { $ref: "#/components/responses/AnalysisResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/debug-types:
    get:
      tags: [analysis]
      operationId: debugTypes
      summary: The named types of the binary's DWARF (embedded or the attached debug file)
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
      responses:
        "200": { $ref: "#/components/responses/AnalysisResult" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/resources:
    get:
      tags: [analysis]
      operationId: androidResources
      summary: An Android package's resources.arsc (A01)
      description: R.type.name = value per configuration. Answered for the APK and for a member opened out of it.
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: q, in: query, schema: { type: string }, description: Name / value filter }
        - { name: type, in: query, schema: { type: string }, description: "Resource type (string, id, layout, ...)" }
        - { name: id, in: query, schema: { type: string }, description: "One resource id (0x7f...)" }
        - { name: ids, in: query, schema: { type: string }, description: "Comma-separated ids: just those entries" }
        - { name: offset, in: query, schema: { type: integer, minimum: 0 } }
        - { name: limit, in: query, schema: { type: integer, minimum: 0 } }
      responses:
        "200": { $ref: "#/components/responses/AnalysisResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/resxrefs:
    get:
      tags: [analysis]
      operationId: androidResourceXrefs
      summary: Where a resource is used (code sites in every dex, binary XML), or one XML file's ids to the code behind them (A01)
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: id, in: query, schema: { type: string }, description: "Resource id (0x7f...) or R.type.name" }
        - { name: path, in: query, schema: { type: string }, description: "A binary XML member (res/layout/x.xml)" }
        - { name: limit, in: query, schema: { type: integer, minimum: 0 } }
      responses:
        "200": { $ref: "#/components/responses/AnalysisResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/pkgsearch:
    get:
      tags: [analysis]
      operationId: androidPackageSearch
      summary: Methods and classes by name across every dex file of the package (multi-dex, A01)
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: q, in: query, required: true, schema: { type: string } }
        - { name: limit, in: query, schema: { type: integer, minimum: 0 } }
      responses:
        "200": { $ref: "#/components/responses/AnalysisResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
  /api/analysis/{file}/jni:
    get:
      tags: [analysis]
      operationId: androidJniMap
      summary: The JNI map - native methods to the functions implementing them in each ABI's lib*.so (A01)
      description: Java_* exports and RegisterNatives tables; unresolved methods say why; System.loadLibrary sites name their library.
      security:
        - ApiKeyBearer: []
      parameters:
        - $ref: "#/components/parameters/StoredFile"
        - { name: q, in: query, schema: { type: string }, description: Method / class filter }
      responses:
        "200": { $ref: "#/components/responses/AnalysisResult" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
components:
  securitySchemes:
    ApiKeyHeader:
      type: apiKey
      in: header
      name: X-API-Key
      description: An API key from your profile page, e.g. dcai_...
    ApiKeyBearer:
      type: http
      scheme: bearer
      bearerFormat: dcai_ API key
      description: The same key as a bearer token (must start with dcai_)
  parameters:
    BinaryId:
      name: id
      in: path
      required: true
      schema: { type: string }
      description: Numeric upload id (from POST /api/v1/binaries) or the stored file name
    Engine:
      name: engine
      in: query
      required: false
      schema: { type: string, enum: [ghidra, gpt, decai], default: ghidra }
      description: L0 engine of the run (defaults to ghidra; an unknown engine answers 400)
    Level:
      name: level
      in: query
      required: false
      schema: { type: string, default: l0 }
      description: Stage token of the run, e.g. l0, l0-l4, l0-l1-l2-l3-l4 (defaults to l0; a malformed token answers 400)
    StoredFile:
      name: file
      in: path
      required: true
      schema: { type: string }
      description: The stored file name (`name` from POST /api/v1/binaries or GET /api/v1/binaries)
  responses:
    BadRequest:
      description: Malformed input
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Unauthorized:
      description: Missing, invalid or revoked API key
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    PaymentRequired:
      description: Insufficient credits, or the key's monthly credit cap is reached
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    NotFound:
      description: Not found (or not yours)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    TooManyRequests:
      description: Per-key rate limit exceeded (see Retry-After)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    ReadOnlyKey:
      description: A read-only key may only read
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Conflict:
      description: Refused in the current state (the reason is in `error`)
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    TooLarge:
      description: Larger than the limit
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    AnalysisResult:
      description: "The analysis backend's answer (served through the analysis cache, `X-Analysis-Cache: hit|miss`)"
      content:
        application/json:
          schema: { type: object }
  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error: { type: string }
    Ok:
      type: object
      properties:
        ok: { type: boolean }
        deleted: { type: boolean }
        removed: { type: [integer, boolean] }
    ProjectDocument:
      type: object
      description: The whole user state of one binary (format decompilerai-project, version 1)
      properties:
        format: { type: string, enum: [decompilerai-project] }
        version: { type: integer }
        file: { type: string }
        sha256: { type: string, description: The binary the document belongs to }
        saved_at: { type: string, format: date-time }
        annotations:
          type: array
          items:
            type: object
            properties:
              kind: { type: string }
              addr: { type: integer }
              value: { type: string }
        header: { type: string, description: The C type header }
        loadspec: { type: [object, "null"], description: A raw image's loader settings }
        patches: { type: array, items: { type: object } }
        items:
          type: array
          description: "Named project items, e.g. kind runconfig = the debugger's run configurations (a document without items keeps the current ones)"
          items:
            type: object
            properties:
              kind: { type: string }
              name: { type: string }
              value: { type: object }
        pdb: { type: [object, "null"] }
    ProjectRestore:
      type: object
      description: A document to restore, or a stored snapshot by id
      properties:
        doc: { $ref: "#/components/schemas/ProjectDocument" }
        snapshot_id: { type: integer }
        force: { type: boolean, description: Restore a document of another binary (sha256 differs) anyway }
    ProjectDiff:
      type: object
      properties:
        ok: { type: boolean }
        changes:
          type: object
          description: "Per kind (and header, loadspec, patches): {added, changed, removed}"
        total: { type: integer }
        notes: { type: array, items: { type: string } }
        sha_match: { type: boolean }
    Snapshot:
      type: object
      properties:
        id: { type: integer }
        name: { type: string }
        created_at: { type: string }
        summary: { type: object, description: Row count per kind }
    DebugState:
      type: object
      description: "Where a debug session stopped. Every machine word is an exact 0x text (never a JSON number)."
      properties:
        status: { type: string, enum: [paused, running, exited, waiting_input, error] }
        reason: { type: string, description: "start, main, entry, breakpoint, step, watchpoint, api, pause, exception, exit, limit, input, travel, ..." }
        bp_id: { type: integer }
        pc: { type: string }
        pc_label: { type: string }
        insn_count: { type: integer }
        regs: { type: object, additionalProperties: { type: string }, description: Every register }
        changed: { type: array, items: { type: string } }
        flags: { type: object }
        stack: { type: array, items: { type: object } }
        callstack: { type: array, items: { type: object } }
        watches: { type: array, items: { type: object } }
        exit_code: { type: integer }
        detail: { type: string }
    DebugBreakpoint:
      type: object
      properties:
        id: { type: integer }
        kind: { type: string, enum: [exec, mem, api] }
        addr: { type: string }
        end: { type: string, description: A memory breakpoint's end (exclusive) }
        access: { type: string, enum: [r, w, rw, x] }
        api: { type: string }
        condition: { type: string }
        hit_count: { type: integer }
        log: { type: string, description: A log message template; logs, never stops }
        ret: { type: string, description: A return-value hook skips the call returning this }
        enabled: { type: boolean }
        hits: { type: integer }
    RunConfig:
      type: object
      description: "A saved start of the debugger: the start request's fields (mode, addr, args, abi, os, max_insns, start_at, argv, env, stdin, stdin_b64, stdin_eof, files up to 2 MB, set_regs, writes, skip_calls) and what the session gets (breakpoints, watches, hooks)."
      properties:
        name: { type: string }
        mode: { type: string, enum: [function, cursor, program] }
        addr: { type: string }
        args: { type: array, items: { type: string } }
        argv: { type: array, items: { type: string } }
        env: { type: object }
        files: { type: array, items: { type: object } }
        breakpoints: { type: array, items: { $ref: "#/components/schemas/DebugBreakpoint" } }
        watches: { type: array, items: { type: string } }
        hooks: { type: array, items: { type: object } }
    UploadResult:
      type: object
      properties:
        id: { type: integer }
        name: { type: string, description: "Stored name, also usable as {id}" }
        sha256: { type: string }
        size_bytes: { type: integer }
        format: { type: string, enum: [elf, pe, macho, unknown] }
    BinarySummary:
      type: object
      properties:
        id: { type: [integer, "null"] }
        name: { type: string }
        original_name: { type: [string, "null"] }
        size_bytes: { type: integer }
        sha256: { type: [string, "null"] }
        format: { type: string }
        uploaded_at: { type: [string, "null"], format: date-time }
        job:
          oneOf:
            - $ref: "#/components/schemas/Job"
            - type: "null"
        runs:
          type: array
          items: { $ref: "#/components/schemas/FinishedRun" }
    FinishedRun:
      type: object
      properties:
        engine: { type: string }
        level: { type: string }
        run_id: { type: [string, "null"] }
        finished_at: { type: [string, "null"] }
        functions: { type: [integer, "null"] }
    Job:
      type: object
      properties:
        id: { type: integer }
        run_id: { type: [string, "null"] }
        engine: { type: string }
        level: { type: string }
        status: { type: string, enum: [QUEUED, RUNNING, DONE, FAILED, CANCELLED] }
        stages:
          type: object
          description: Per-stage status map (analysis, l0_decai, l1_syntax, l2_compile, l3_symdiff, l4_readability)
        error: { type: [string, "null"] }
        functions: { type: [integer, "null"] }
        file: { type: [string, "null"] }
        started_at: { type: [string, "null"] }
        finished_at: { type: [string, "null"] }
        queue_position: { type: [integer, "null"] }
    FunctionDecompilation:
      type: object
      properties:
        ok: { type: boolean }
        scope: { type: string, enum: [function] }
        engine: { type: string }
        level: { type: string }
        addr: { type: integer }
        cached: { type: boolean, description: True when served from cache (free) }
        status: { type: string }
        code: { type: string, description: The decompiled C }
        compiles: { type: [boolean, integer, "null"] }
    ProgramRunStarted:
      type: object
      properties:
        ok: { type: boolean }
        scope: { type: string, enum: [program] }
        engine: { type: string }
        level: { type: string }
        job_id: { type: [integer, "null"] }
        run_id: { type: [string, "null"] }
        status: { type: string }
        poll: { type: string, description: URL to poll for progress }
        estimated_credits: { type: integer }
        functions: { type: [integer, "null"] }
        forge_skipped: { type: integer, description: FORGE-recognized functions left out of the run (0 with include_forge) }
        include_forge: { type: boolean }
    DecompiledProgram:
      type: object
      properties:
        file: { type: string }
        sha256: { type: string }
        engine: { type: string }
        level: { type: string }
        generated_at: { type: string, format: date-time }
        program_c: { type: [string, "null"] }
        functions:
          type: array
          items:
            type: object
            properties:
              addr: { type: integer }
              name: { type: string }
              code: { type: string }
              source: { type: [string, "null"] }
        counts:
          type: object
          properties:
            functions_total: { type: [integer, "null"] }
            decompiled: { type: integer }
        patch_rev: { type: [string, "null"], description: "The patch revision the run was made from ('0' = the unpatched binary, '?' = patched, recorded before revisions were stored)." }
        rev_note: { type: string, description: "Empty while the binary is at that revision; otherwise why this C is not the current state (e.g. made from an earlier patch state - run again). The .c / .zip headers carry the same." }
    Me:
      type: object
      properties:
        user:
          type: object
          properties:
            id: { type: integer }
            username: { type: string }
        key:
          type: object
          properties:
            id: { type: integer }
            monthly_credit_cap: { type: [integer, "null"] }
            cap_remaining: { type: [integer, "null"] }
        credits:
          type: object
          description: Credit balance view (available, plan_key, period_end, ...)
        prices:
          type: object
          description: Map action -> {credits, unit, description}
