openapi: 3.1.0
info:
  title: swornly — deterministic pay-per-call tools for agents (x402 + L402)
  description: |
    swornly is a substrate of small, deterministic tools that AI agents pay for
    per call, each returning an HMAC-signed receipt. The first tool renders
    Markdown to a styled PDF; the rest are verification oracles (below). Each
    call is gated by one of two payment rails:

      * `POST /convert`      — x402 (USDC on Base, Coinbase facilitator).
      * `POST /convert/l402` — L402 (Lightning sats, macaroon + BOLT-11).

    Flow for x402:
      1. POST /convert with `{ "markdown": "..." }`.
      2. Receive 402 with the `accepts` array of payment requirements.
      3. Sign EIP-3009 `transferWithAuthorization` and resubmit with the
         `X-PAYMENT` header set to the base64-encoded payment payload.
      4. Response is `application/pdf` plus `X-PAYMENT-RESPONSE`.

    Flow for L402:
      1. POST /convert/l402 with `{ "markdown": "..." }`.
      2. Receive 402 with `WWW-Authenticate: L402 macaroon="...", invoice="..."`
         and a JSON body containing `{macaroon, invoice, amount_sats,
         payment_hash, expires_at}`.
      3. Pay the BOLT-11 invoice on Lightning. Obtain the payment preimage.
      4. Resubmit with `Authorization: L402 <macaroon_b64>:<preimage_hex>`
         and receive the PDF.

    Deterministic verification oracles (same two rails; JSON in, JSON out, each
    response carries an HMAC-signed `receipt`):

      * `POST /dry-run/command`        — classify a shell/git command as
        destructive + its blast radius BEFORE the agent runs it.
      * `POST /diff/mcp-schema`        — diff two MCP `tools/list` payloads into
        breaking vs non-breaking changes.
      * `POST /snapshot/tool-contract` — stable fingerprint of a `tools/list`
        for contract pinning / rug-pull detection.
      * `POST /receipts/verify`        — free: re-verify any receipt.

    Each oracle has an `/l402` sibling (e.g. `POST /dry-run/command/l402`) that
    takes the Lightning rail instead of x402.
  version: "1.2.0"
servers:
  - url: http://localhost:8080
paths:
  /health:
    get:
      operationId: health
      security: []  # not an x402 endpoint — skip x402 discovery probing
      summary: Service health and configuration
      responses:
        "200":
          description: ok
          content:
            application/json:
              schema:
                type: object
  /convert:
    post:
      operationId: convertMarkdownToPdf
      summary: Convert Markdown to PDF (x402-gated)
      parameters:
        - in: header
          name: X-PAYMENT
          required: false
          schema:
            type: string
          description: Base64-encoded x402 payment payload.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [markdown]
              properties:
                markdown:
                  type: string
                  description: Raw Markdown source to render.
      responses:
        "200":
          description: PDF file
          headers:
            X-PAYMENT-RESPONSE:
              schema: { type: string }
              description: Base64-encoded settlement receipt from the facilitator.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "402":
          description: Payment required (x402)
          content:
            application/json:
              schema:
                type: object
                properties:
                  x402Version: { type: integer }
                  accepts:
                    type: array
                    items: { type: object }
                  error: { type: string }
        "400":
          description: Bad request (missing/invalid markdown)
  /convert/l402:
    post:
      operationId: convertMarkdownToPdfL402
      security: []  # not an x402 endpoint — skip x402 discovery probing
      summary: Convert Markdown to PDF (L402-gated, Lightning)
      parameters:
        - in: header
          name: Authorization
          required: false
          schema:
            type: string
          description: L402 credentials — `L402 <macaroon_b64>:<preimage_hex>`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [markdown]
              properties:
                markdown:
                  type: string
                  description: Raw Markdown source to render.
      responses:
        "200":
          description: PDF file
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        "402":
          description: Payment required (L402)
          headers:
            WWW-Authenticate:
              schema: { type: string }
              description: |
                L402 challenge in the form
                `L402 macaroon="<base64>", invoice="<bolt11>"`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  macaroon: { type: string }
                  invoice: { type: string }
                  amount_sats: { type: integer }
                  payment_hash: { type: string }
                  expires_at: { type: integer }
        "400":
          description: Bad request (missing/invalid markdown or malformed Authorization)
  /dry-run/command:
    post:
      operationId: dryRunCommand
      summary: Classify a shell/git command's destructiveness + blast radius (x402-gated)
      description: |
        POST `{ "command": "rm -rf ../build" | ["rm","-rf","../build"], "shell"?: "bash" }`.
        Returns `{ "result": <analysis>, "receipt": <signed> }`. Deterministic:
        same command -> same verdict. Errs toward flagging destructive.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [command]
              properties:
                command:
                  oneOf:
                    - type: string
                    - type: array
                      items: { type: string }
                  description: Raw command line, or argv array.
                shell:
                  type: string
                  default: bash
      responses:
        "200":
          description: Analysis + signed receipt
          headers:
            X-Receipt-Id: { schema: { type: string } }
            X-PAYMENT-RESPONSE:
              schema: { type: string }
              description: Base64 settlement receipt from the facilitator.
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
                    properties:
                      is_destructive: { type: boolean }
                      risk: { type: string, enum: [none, low, medium, high, critical] }
                      subcommands: { type: array, items: { type: object } }
                      summary: { type: string }
                  receipt: { type: object }
        "402":
          description: Payment required (x402)
          content:
            application/json:
              schema:
                type: object
                properties:
                  x402Version: { type: integer }
                  accepts: { type: array, items: { type: object } }
                  error: { type: string }
        "400":
          description: Bad request (missing/invalid command)
  /dry-run/command/l402:
    post:
      operationId: dryRunCommandL402
      security: []  # not an x402 endpoint — skip x402 discovery probing
      summary: Dry-run a command (L402/Lightning rail)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [command]
              properties:
                command:
                  oneOf:
                    - type: string
                    - type: array
                      items: { type: string }
                shell: { type: string, default: bash }
      responses:
        "200":
          description: Analysis + signed receipt
          headers:
            X-Receipt-Id: { schema: { type: string } }
            X-L402-Macaroon-Id: { schema: { type: string } }
          content:
            application/json:
              schema: { type: object }
        "402":
          description: Payment required (L402)
          headers:
            WWW-Authenticate:
              schema: { type: string }
              description: '`L402 macaroon="<base64>", invoice="<bolt11>"`.'
          content:
            application/json:
              schema: { type: object }
        "400":
          description: Bad request
  /diff/mcp-schema:
    post:
      operationId: diffMcpSchema
      summary: Diff two MCP tools/list payloads into breaking vs non-breaking changes (x402-gated)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [before, after]
              properties:
                before: { type: object, description: An MCP tools/list payload (or a bare list of tools). }
                after: { type: object, description: The later tools/list to compare against. }
      responses:
        "200":
          description: Diff + signed receipt
          headers:
            X-Receipt-Id: { schema: { type: string } }
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
                    properties:
                      has_breaking: { type: boolean }
                      breaking: { type: array, items: { type: object } }
                      non_breaking: { type: array, items: { type: object } }
                      summary: { type: string }
                  receipt: { type: object }
        "402":
          description: Payment required (x402)
        "400":
          description: Bad request (missing before/after)
  /diff/mcp-schema/l402:
    post:
      operationId: diffMcpSchemaL402
      security: []  # not an x402 endpoint — skip x402 discovery probing
      summary: Diff MCP schemas (L402/Lightning rail)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [before, after]
              properties:
                before: { type: object }
                after: { type: object }
      responses:
        "200": { description: Diff + signed receipt }
        "402": { description: Payment required (L402) }
        "400": { description: Bad request }
  /snapshot/tool-contract:
    post:
      operationId: snapshotToolContract
      summary: Stable fingerprint of an MCP tools/list for contract pinning (x402-gated)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tools_list: { type: object, description: An MCP tools/list payload (or a bare list of tools). }
      responses:
        "200":
          description: Fingerprint + signed receipt
          headers:
            X-Receipt-Id: { schema: { type: string } }
          content:
            application/json:
              schema:
                type: object
                properties:
                  result:
                    type: object
                    properties:
                      fingerprint: { type: string }
                      tool_count: { type: integer }
                      tools: { type: object }
                      algorithm: { type: string }
                  receipt: { type: object }
        "402":
          description: Payment required (x402)
        "400":
          description: Bad request
  /snapshot/tool-contract/l402:
    post:
      operationId: snapshotToolContractL402
      security: []  # not an x402 endpoint — skip x402 discovery probing
      summary: Fingerprint a tools/list (L402/Lightning rail)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                tools_list: { type: object }
      responses:
        "200": { description: Fingerprint + signed receipt }
        "402": { description: Payment required (L402) }
        "400": { description: Bad request }
  /receipts/verify:
    post:
      operationId: verifyReceipt
      security: []  # not an x402 endpoint — skip x402 discovery probing
      summary: Re-verify a receipt against its result (free, no payment)
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [result, receipt]
              properties:
                result: { description: The `result` object returned by an oracle. }
                receipt: { type: object, description: The `receipt` object returned alongside it. }
      responses:
        "200":
          description: Verification outcome
          content:
            application/json:
              schema:
                type: object
                properties:
                  valid: { type: boolean }
                  reason: { type: string }
        "400":
          description: Bad request (missing result/receipt)
