{
  "openapi": "3.1.0",
  "info": {
    "title": "Enclave API",
    "version": "1.0.0",
    "summary": "Verifiable, attested on-demand confidential GPU compute for WebAssembly apps.",
    "description": "Deploy a confidential WebAssembly app into a hardware-isolated enclave with an H200 in NVIDIA confidential-computing mode exposed to it. Each app is a `wasi:http` component that runs sandboxed inside the enclave: no SSH, no host filesystem, no host environment, no ambient authority; just the request/response socket the runtime hands it. Apps are published to the on-chain app catalog (EnclaveAppCatalog on Base) addressed by IPFS CID; the enclave fetches the CID and verifies every byte hashes to it before launch, and the verified CID is folded into the attestation. Authenticate with an Ethereum signature (SIWE), pay USDC or ETH per deployment (your wallet is your account; no held balance), and verify the enclave yourself before you send data. Every endpoint is CORS-enabled so it can be driven directly from browser JavaScript."
  },
  "servers": [
    {
      "url": "https://api.enclave.host/v1",
      "description": "Production API gateway: proxies to the live enclave (attested TLS terminates in the enclave; the gateway sees control-plane traffic). Talk to an enclave directly by using its base, e.g. https://svalbard.enclave.containers.tinfoil.dev/v1"
    }
  ],
  "security": [
    {
      "siwe": []
    }
  ],
  "tags": [
    {
      "name": "Auth",
      "description": "Prove control of an address and obtain a bearer token."
    },
    {
      "name": "Account",
      "description": "Your address, the legacy payment forwarder, and a summary of your deployments. Current deployments fund on the EnclaveDeployments contract; no balances are held by this service."
    },
    {
      "name": "Pricing",
      "description": "Live per-second rates and GPU availability."
    },
    {
      "name": "Deployments",
      "description": "Create, fund, inspect, and tear down confidential Wasm apps."
    },
    {
      "name": "Attestation",
      "description": "Verify what is actually running."
    },
    {
      "name": "System",
      "description": "Health and build metadata."
    }
  ],
  "paths": {
    "/auth/nonce": {
      "get": {
        "tags": [
          "Auth"
        ],
        "operationId": "getNonce",
        "summary": "Get a SIWE nonce",
        "security": [],
        "description": "Returns a single-use nonce (and a ready-to-sign SIWE message) for an address. Sign the message and exchange it at /auth/login.",
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "example": "0x4E5A...b9c1"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A nonce and canonical SIWE message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthChallenge"
                }
              }
            }
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "tags": [
          "Auth"
        ],
        "operationId": "login",
        "summary": "Exchange a signature for a token",
        "security": [],
        "description": "Verifies the signed SIWE message and issues a short-lived bearer token bound to the address. The token only authorizes API orchestration; there are no custodied funds it could ever move.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A bearer token bound to the address.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Session"
                }
              }
            }
          },
          "401": {
            "description": "Bad or expired signature.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getAccount",
        "summary": "Account summary",
        "description": "Your address, the legacy USDC payment forwarder (pre-on-chain records only; current deployments fund the EnclaveDeployments contract), and counts of your running and awaiting-payment deployments plus total funded time remaining. No balances are held by Enclave.",
        "responses": {
          "200": {
            "description": "Account summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "401": {
            "description": "Missing or expired token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/pricing": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "operationId": "getPricing",
        "summary": "Rates and billing model",
        "security": [],
        "description": "The two per-second rates (whole GPU card, whole CPU node) and how the two shares a deployment buys map to a price: ratePerSecond = gpuShare x cardRate + cpuShare x nodeRate, plus the version's per-second publisher fee when the app charges one (EnclaveAppCatalog.versionFee, snapshotted into the deployment's rate at create and forwarded to the publisher's wallet out of each funding). The card/node specs let you derive an app's minimum shares from its catalog specs.",
        "responses": {
          "200": {
            "description": "Pricing model.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pricing"
                }
              }
            }
          }
        }
      }
    },
    "/availability": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "operationId": "getAvailability",
        "summary": "Live capacity (both pools)",
        "security": [],
        "servers": [
          {
            "url": "https://api.enclave.host",
            "description": "Root origin (this path is not under /v1)"
          }
        ],
        "description": "Live free capacity as TWO shares: `gpuShareFree` (largest slice one card can still take; 0 on CPU-only enclaves) and `cpuShareFree` (the node's leftover vCPU+RAM - on a GPU enclave it is rentable by CPU-only apps). Read from the live allocators; falls back to local accounting if the backend is briefly unreachable. NOTE: this endpoint is served at the root origin, not under /v1.",
        "responses": {
          "200": {
            "description": "Current capacity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Availability"
                }
              }
            }
          }
        }
      }
    },
    "/gpu": {
      "get": {
        "tags": [
          "Pricing"
        ],
        "operationId": "getGpu",
        "summary": "Live MPS / per-tenant SM grants",
        "security": [],
        "description": "External proof that MPS caps are live: each running tenant's granted SM count (sanitized, no tenant ids). A 25% tenant reports roughly a quarter of the card's SMs. On a CPU-only enclave this endpoint returns 404 (no GPU attached).",
        "responses": {
          "200": {
            "description": "MPS status and per-tenant SM grants.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Gpu"
                }
              }
            }
          },
          "404": {
            "description": "CPU-only enclave: no GPU attached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Worker manager unreachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/deployments": {
      "post": {
        "tags": [
          "Deployments"
        ],
        "operationId": "createDeployment",
        "summary": "Create a deployment (awaiting payment)",
        "description": "RETIRED on Wasm enclaves - returns 410 `deploy_on_chain` with the on-chain coordinates. Deployments are created on the EnclaveDeployments contract (Base), not through this endpoint: the ledger holds the spec and the funded balance, so a deployment survives enclave updates and crashes (runners serve it under expiring leases; a dead runner's lease lapses and any enclave re-claims the work). Flow: (1) create(appRef, gpuMilli, cpuMilli, appPort, ports, isPublic, configCid, feeRecipient, feePerSec6) from YOUR wallet - appRef is catalog://<appId>/<versionIndex>, the on-chain record of the version to run (runners refuse CID refs: a CID names bytes, not a version); the version's record supplies the config, volumes and ports the owner approved; configCid carries either \"\" or the deployment-options envelope {\"waf\":{...}} (per-IP rate limit + request filter, enforced by the runner's in-enclave proxy - never handed to the app; anything else is refused) and ports/appPort ride along untrusted. msg.sender owns the record; the id is the Created event's first topic; shares are 1/1000ths and the app's catalog specs set the minimums runners will accept; feeRecipient/feePerSec6 copy the referenced version's publisher fee (the app's publisher wallet; 0x0/0 for a free app) - snapshotted like the rate and folded into it, and runners refuse to claim a deployment that under-declares the version's fee (fail closed, like catalog approval). (2) Fund it: fundWithAuthorization (EIP-3009 USDC ReceiveWithAuthorization signed to the contract, nonce prefixed with the id's first 16 bytes) or fundEth (credited at the contract's Chainlink ETH/USD read); funds forward in the same tx (to Enclave, and on a fee-bearing deployment the publisher's pro-rata cut straight to the fee recipient's wallet), the credit lands in on-chain balance6. (3) Optionally POST /claim-hint {id} so the fleet claims immediately instead of on the ~60s sweep. Runners enforce the same catalog-approval gate (403-equivalent: unapproved CIDs are never claimed) and the same share minimums. GET /pricing carries the contract address, USDC EIP-712 domain, and an ETH/USD quote.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDeployment"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created, awaiting payment. Fund it to start the app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deployment"
                }
              }
            }
          },
          "401": {
            "description": "Missing or expired token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "App not approved (`not_approved`): the referenced catalog app version has not been approved by the catalog owner (or was rejected, yanked, or delisted). Only the EnclaveAppCatalog deployer can approve a version, by signing a setApproval transaction; until then its record cannot be deployed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "No capacity: no single card has that gpuShare free, or the node lacks the cpuShare.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid spec (e.g. a share out of range, below the app's minimum shares, gpuShare < cpuShare, or an exact-resource field - apps declare those in the catalog; deployments buy shares).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Approval could not be verified (`approval_unavailable` / `catalog_unreachable`): the enclave has no catalog configured or the chain RPC is unreachable. The gate fails closed; retry later.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Deployments"
        ],
        "operationId": "listDeployments",
        "summary": "List your deployments",
        "description": "With a session token: your deployments, enriched by the enclaves' live view. Without one, pass ?owner=0x… - the gateway answers from the EnclaveDeployments ledger (public on-chain records; rows carry ledger:true), so a connected wallet needs no sign-in just to list.",
        "parameters": [
          { "name": "owner", "in": "query", "required": false,
            "schema": { "type": "string", "pattern": "^0x[0-9a-fA-F]{40}$" },
            "description": "Wallet to scope the PUBLIC ledger listing by when no session token is sent." }
        ],
        "responses": {
          "200": {
            "description": "Your deployments.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeploymentList"
                }
              }
            }
          }
        }
      }
    },
    "/claim-hint": {
      "post": {
        "operationId": "claimHint",
        "summary": "Nudge the fleet to claim a funded on-chain deployment right now",
        "description": "Unauthenticated fast-path for EnclaveDeployments work items: after create() + funding, POST the bytes32 id here and every live enclave immediately evaluates the deployment (capacity, catalog approval, share minimums) and races to claim it - the contract referees, the loser's tx reverts. Without a hint the sweep claims funded work within about a minute. The response is advisory: accepted=true means an enclave is attempting the claim in its background; watch EnclaveDeployments.get(id).runner/leaseUntil for the authoritative result.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["id"], "properties": {
            "id": { "type": "string", "description": "The bytes32 deployment id from the Created event (0x + 64 hex).", "example": "0x8b7a…" } } } } }
        },
        "responses": {
          "200": { "description": "Evaluation result from the first enclave that accepted (or the last refusal reason).",
            "content": { "application/json": { "schema": { "type": "object", "properties": {
              "accepted": { "type": "boolean" }, "status": { "type": "string" },
              "reason": { "type": "string", "description": "Why no enclave took it (unfunded, leased elsewhere, below app minimums, no capacity, unapproved CID, ...)." } } } } } },
          "422": { "description": "Malformed id." }
        }
      }
    },
    "/deployments/{id}": {
      "get": {
        "tags": [
          "Deployments"
        ],
        "operationId": "getDeployment",
        "summary": "Get a deployment",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deployment"
                }
              }
            }
          },
          "404": {
            "description": "No such deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Deployments"
        ],
        "operationId": "terminateDeployment",
        "summary": "Stop a deployment",
        "description": "Stops the worker process, tears down the tenant, and releases both shares (GPU and CPU); the deployment finishes as `terminated` once teardown completes (the receipt already carries it). An `awaiting_payment` reservation is removed outright instead (`canceled`). For an on-chain deployment this only ends the CURRENT run: the lease is released and its unused tail is refunded to the deployment's on-chain balance, which stays on the record - an active, funded deployment gets re-claimed by the fleet, and a permanent stop is your setActive(false) transaction. Unspent balance survives for a later restart but is never withdrawable; payments are final. (Legacy pre-on-chain records had no balance: their unused funded time was forfeit on early stop.)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Teardown acknowledged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TerminationReceipt"
                }
              }
            }
          },
          "404": {
            "description": "No such deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/deployments/{id}/topup": {
      "post": {
        "tags": [
          "Deployments"
        ],
        "operationId": "topupDeployment",
        "summary": "Top-up instructions",
        "description": "Returns the funding instructions to extend a deployment. For on-chain deployments (the current path) that means the EnclaveDeployments contract: fundWithAuthorization (EIP-3009 USDC, nonce prefixed with the id's first 16 bytes) or fundEth - NOT the legacy forwarder, which cannot credit an on-chain balance. Each payment adds amount(6dp)/rate seconds. Legacy pre-on-chain records still receive EnclavePay payWithAuthorization instructions. Send it before expiry (a short grace period covers in-flight top-ups).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Payment instructions for the top-up.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TopupResponse"
                }
              }
            }
          },
          "404": {
            "description": "No such deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Deployment is not in a top-up-able state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/deployments/{id}/restart": {
      "post": {
        "tags": [
          "Deployments"
        ],
        "operationId": "restartDeployment",
        "summary": "Restart in place (owner only)",
        "description": "Stop the app instance and relaunch it on the same version, lease and balance (app state is ephemeral by design). The remedy for a wedged instance that still answers but cannot do its job - e.g. one that booted before its model volume finished mounting. Running deployments on the wasm (vm) backend only.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Restarted in place.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "note": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "No such deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Not running, or a restart is already in progress.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Relaunch failed - the lease was handed back; the fleet retries.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/deployments/{id}/logs": {
      "get": {
        "tags": [
          "Deployments"
        ],
        "operationId": "getLogs",
        "summary": "Worker logs (owner only)",
        "description": "Stdout/stderr from inside the enclave as text/plain. ?tail=N (default 200, max 2000).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tail",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 200,
              "maximum": 2000
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Log text.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No such deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/attestation": {
      "get": {
        "tags": [
          "Attestation"
        ],
        "operationId": "getEnclaveAttestation",
        "summary": "Get the enclave attestation (public)",
        "description": "Enclave-level attestation, no auth: the Tinfoil Remote Attestation Document (the live CPU attestation report binding this origin's TLS key - AMD SEV-SNP on today's fleet; Intel TDX quotes are handled identically), parsed measurement registers, and NVIDIA GPU CC evidence from a short server-side cache. Verify client-side BEFORE logging in or sending data. For a fresh GPU nonce challenge, use the per-deployment endpoint.",
        "security": [],
        "responses": {
          "200": {
            "description": "Attestation material.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Attestation"
                }
              }
            }
          }
        }
      }
    },
    "/deployments/{id}/attestation": {
      "get": {
        "tags": [
          "Attestation"
        ],
        "operationId": "getAttestation",
        "summary": "Get a deployment's attestation",
        "description": "The live CPU attestation report (AMD SEV-SNP on today's fleet; relayed Tinfoil Remote Attestation Document plus parsed registers) and the NVIDIA GPU attestation report, with honest per-app coverage notes. Public: no session needed (an app's users are exactly who should verify it). With the owner's session the GPU report is regenerated fresh over your ?nonce; anonymous callers share a short-lived cached report and get freshness from the TLS-bound quote instead. Verify client-side before sending data.",
        "security": [
          {},
          {
            "siwe": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "nonce",
            "in": "query",
            "required": false,
            "description": "32-byte hex freshness challenge folded into the GPU attestation report. Honored only with the owner's session (fresh NVML generation is the owner's perk); ignored otherwise - a non-matching gpu.nonce in the response means the report came from the shared cache. Random if omitted by the owner.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-fA-F]{64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Attestation material.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Attestation"
                }
              }
            }
          },
          "404": {
            "description": "No such deployment.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "System"
        ],
        "operationId": "getHealth",
        "summary": "Health",
        "security": [],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                }
              }
            }
          }
        }
      }
    },
    "/version": {
      "get": {
        "tags": [
          "System"
        ],
        "operationId": "getVersion",
        "summary": "Build metadata",
        "security": [],
        "responses": {
          "200": {
            "description": "Version.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Version"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "siwe": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Bearer token from POST /auth/login. Bound to one address; short-lived."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "example": "invalid_spec"
          },
          "message": {
            "type": "string",
            "example": "resources.cpuShare must be in (0, 1]."
          }
        }
      },
      "Asset": {
        "type": "string",
        "enum": [
          "USDC"
        ],
        "description": "Settlement asset. Payments are USDC on Base."
      },
      "DeploymentStatus": {
        "type": "string",
        "description": "terminated = owner-stopped and torn down (final). stopping is the legacy name older enclaves report for the same state.",
        "enum": [
          "awaiting_payment",
          "running",
          "expired",
          "terminated",
          "stopping",
          "failed"
        ]
      },
      "AuthChallenge": {
        "type": "object",
        "properties": {
          "address": {
            "type": "string"
          },
          "nonce": {
            "type": "string",
            "description": "Single-use, expires in minutes.",
            "example": "z8Qa2K9fP1"
          },
          "message": {
            "type": "string",
            "description": "Canonical SIWE message to sign."
          },
          "issuedAt": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": [
          "message",
          "signature"
        ],
        "properties": {
          "message": {
            "type": "string"
          },
          "signature": {
            "type": "string",
            "example": "0x..."
          }
        }
      },
      "Session": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "example": "eyJhbGciOiJIUzI1NiJ9..."
          },
          "tokenType": {
            "type": "string",
            "example": "Bearer"
          },
          "address": {
            "type": "string"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PaymentInstructions": {
        "type": "object",
        "description": "LEGACY (pre-on-chain records only): how to top up by paying the EnclavePay forwarder. Current deployments live on the EnclaveDeployments ledger and fund there instead (fundWithAuthorization / fundEth); paying the forwarder cannot credit an on-chain balance. Funds pass straight through to Enclave either way; nothing is custodied.",
        "properties": {
          "chainId": {
            "type": "integer",
            "example": 8453
          },
          "asset": {
            "type": "string",
            "example": "USDC"
          },
          "usdc": {
            "type": "string",
            "description": "USDC token address on Base.",
            "example": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
          },
          "forwarder": {
            "type": [
              "string",
              "null"
            ],
            "description": "EnclavePay contract to call. null until configured.",
            "example": "0x..."
          },
          "deploymentRef": {
            "type": "string",
            "description": "bytes32 to pass as payWithAuthorization()'s deploymentId (the signed nonce must start with its first 16 bytes).",
            "example": "0x9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
          },
          "ratePerSecondUsdc": {
            "type": "string",
            "example": "0.0004167"
          },
          "method": {
            "type": "string",
            "example": "payWithAuthorization(bytes32 deploymentId, address from, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes signature)"
          },
          "usdcDomain": {
            "type": [
              "object",
              "null"
            ],
            "description": "EIP-712 domain of the USDC token - sign the EIP-3009 ReceiveWithAuthorization against exactly these fields (to = forwarder). null until the enclave's first token read.",
            "properties": {
              "name": {
                "type": "string",
                "example": "USD Coin"
              },
              "version": {
                "type": "string",
                "example": "2"
              },
              "chainId": {
                "type": "integer",
                "example": 8453
              },
              "verifyingContract": {
                "type": "string",
                "example": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
              }
            }
          },
          "note": {
            "type": "string"
          },
          "assets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "USDC",
              "ETH"
            ],
            "description": "Accepted payment assets."
          },
          "payEthMethod": {
            "type": "string",
            "example": "payEth(bytes32 deploymentId) payable",
            "description": "Native-ETH alternative: one payable call, no signature. The enclave watches PaidEth and credits the wei as USDC-equivalent at the live Chainlink ETH/USD rate."
          },
          "ethUsd": {
            "type": [
              "string",
              "null"
            ],
            "example": "1630.31",
            "description": "The enclave's cached Chainlink ETH/USD read (use to size msg.value); null until first refresh."
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "address": {
            "type": "string"
          },
          "chainId": {
            "type": "integer",
            "example": 8453
          },
          "payment": {
            "type": "object",
            "properties": {
              "forwarder": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "usdc": {
                "type": "string",
                "example": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
              },
              "asset": {
                "type": "string",
                "example": "USDC"
              }
            }
          },
          "deployments": {
            "type": "object",
            "properties": {
              "running": {
                "type": "integer",
                "example": 1
              },
              "awaitingPayment": {
                "type": "integer",
                "example": 0
              },
              "total": {
                "type": "integer",
                "example": 3
              },
              "totalTimeRemainingSec": {
                "type": "integer",
                "example": 5400
              }
            }
          }
        }
      },
      "PricingExample": {
        "type": "object",
        "properties": {
          "gpuShare": {
            "type": "number",
            "example": 0.25
          },
          "cpuShare": {
            "type": "number",
            "example": 0.05
          },
          "vramGb": {
            "type": "number",
            "description": "What the GPU share grants on this card (gpuShare x cardVramGb). GPU examples only.",
            "example": 35.3
          },
          "gpuTflops": {
            "type": "number",
            "description": "What the GPU share grants in compute (gpuShare x cardTflops). GPU examples only.",
            "example": 247.3
          },
          "ramGb": {
            "type": "number",
            "description": "What the CPU share grants in RAM (cpuShare x nodeRamGb).",
            "example": 3.2
          },
          "vcpus": {
            "type": "number",
            "description": "What the CPU share grants in vCPUs.",
            "example": 0.8
          },
          "cpuGflops": {
            "type": "number",
            "description": "What the CPU share grants in compute (GFLOPS - the whole node is ~1000).",
            "example": 50
          },
          "ratePerSecondUsdc": {
            "type": "string",
            "example": "0.000459"
          },
          "ratePerHourUsdc": {
            "type": "string",
            "example": "1.65"
          }
        }
      },
      "Pricing": {
        "type": "object",
        "properties": {
          "assets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "ETH",
              "USDC"
            ]
          },
          "model": {
            "type": "string"
          },
          "card": {
            "type": "object",
            "description": "The GPU card's spec: exact asks are divided by these to derive the GPU share.",
            "properties": {
              "vramGb": {
                "type": "number",
                "example": 141
              },
              "tflops": {
                "type": "number",
                "description": "GPU compute per card (TFLOPS, H200 FP16 dense).",
                "example": 989
              },
              "count": {
                "type": "integer",
                "example": 1
              },
              "sms": {
                "type": "integer",
                "example": 132
              },
              "wholeCardPerSecondUsdc": {
                "type": "string",
                "example": "0.0016667"
              },
              "wholeCardPerHourUsdc": {
                "type": "string",
                "example": "6.00"
              }
            }
          },
          "node": {
            "type": "object",
            "description": "The node's spec - present on every flavor (cpuShare is priced everywhere); exact asks are divided by these to derive the CPU share.",
            "properties": {
              "vcpus": {
                "type": "integer",
                "example": 16
              },
              "ramGb": {
                "type": "integer",
                "example": 64
              },
              "gflops": {
                "type": "number",
                "description": "CPU compute per node (GFLOPS; ~1000 for 16 vCPU - about 1/1000 of a card, which is why CPU uses the smaller unit).",
                "example": 1000
              },
              "wholeNodePerSecondUsdc": {
                "type": "string",
                "example": "0.000834"
              },
              "wholeNodePerHourUsdc": {
                "type": "string",
                "example": "3.00"
              }
            }
          },
          "computeGranularity": {
            "type": "object",
            "properties": {
              "unit": {
                "type": "string",
                "example": "percent"
              },
              "step": {
                "type": "integer",
                "example": 1
              },
              "minPercent": {
                "type": "integer",
                "example": 1
              },
              "note": {
                "type": "string"
              }
            }
          },
          "vramGranularityGb": {
            "type": "number",
            "example": 1
          },
          "formula": {
            "type": "string",
            "example": "ratePerSecondUsdc = gpuShare x wholeCardPerSecond + cpuShare x wholeNodePerSecond; minGpuShare = ceilPct(max(vramGb / cardVramGb, gpuTflops / cardTflops)); minCpuShare = ceilPct(max(memMb / nodeRam, cpuGflops / nodeGflops))"
          },
          "examples": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PricingExample"
            }
          },
          "billingIncrementSeconds": {
            "type": "integer",
            "example": 1
          }
        }
      },
      "Availability": {
        "type": "object",
        "description": "Live free capacity as TWO shares (0 to 1), served at the root origin, not under /v1. gpuShareFree is the largest slice ONE card can still take (0 on CPU-only enclaves); cpuShareFree is the node's leftover vCPU+RAM share - on a GPU enclave that leftover is rentable by CPU-only apps. Route on the pair: GPU work needs gpuShareFree, CPU-only work fits wherever cpuShareFree is big enough (CPU-only enclaves first).",
        "properties": {
          "gpu": {
            "type": "boolean",
            "description": "true = GPU enclave, false = CPU-only enclave.",
            "example": true
          },
          "type": {
            "type": "string",
            "enum": [
              "gpu",
              "cpu"
            ]
          },
          "gpuShareFree": {
            "type": "number",
            "description": "Largest GPU share a single card can still take (0 to 1; 0 on CPU-only enclaves). Requests above this get 409.",
            "example": 0.55
          },
          "cpuShareFree": {
            "type": "number",
            "description": "Free share of the node's vCPU+RAM (0 to 1). CPU-only apps fit here on either flavor.",
            "example": 0.9
          },
          "usedGpuShare": {
            "type": "number",
            "example": 0.45
          },
          "usedCpuShare": {
            "type": "number",
            "example": 0.1
          },
          "maxShare": {
            "type": "number",
            "description": "DEPRECATED alias of the flavor's primary pool (gpuShareFree on GPU enclaves, cpuShareFree on CPU-only ones); kept one release for old routers.",
            "example": 0.55
          },
          "smFree": {
            "type": "integer",
            "example": 72
          },
          "smTotal": {
            "type": "integer",
            "example": 132
          },
          "vramFreeGb": {
            "type": "number",
            "example": 77
          },
          "gpuTflopsFree": {
            "type": "number",
            "description": "Free GPU compute on the biggest single-card slice (gpuShareFree \u00d7 cardTflops).",
            "example": 543.9
          },
          "cpuGflopsFree": {
            "type": "number",
            "description": "Free CPU compute in the node's leftover pool (cpuShareFree \u00d7 nodeGflops, GFLOPS).",
            "example": 900
          },
          "aggregate": {
            "type": "boolean",
            "description": "Present (true) when served by the api.enclave.host gateway: the numbers are a FLEET view - the best single-card slice and the best node pool across live enclaves, which can be different machines. Absent on an enclave's own /availability."
          },
          "enclaves": {
            "type": "integer",
            "description": "Aggregate only: live enclaves behind the numbers.",
            "example": 2
          },
          "gpuEnclaveCpuShareFree": {
            "type": "number",
            "description": "Aggregate only: the free CPU pool on the best GPU enclave itself. A GPU deployment's cpuShare must fit THIS pool (its CPU slice rides the card's node), while cpuShareFree may come from a CPU-only enclave.",
            "example": 0.9
          },
          "cardVramGb": {
            "type": "number",
            "example": 141
          },
          "cardTflops": {
            "type": "number",
            "description": "GPU compute per card (TFLOPS); routers divide exact compute asks by this.",
            "example": 989
          },
          "cards": {
            "type": "integer",
            "example": 1
          },
          "volumes": {
            "type": "array",
            "description": "Attested read-only model volumes this enclave carries (Tinfoil Modelwrap). A version attaches one or more by name (the `volumes` key of its immutable, approval-covered config); the runner mounts each read-only at /models/<name> inside the guest, so large model weights reach a tenant without riding the app wasm. On the fleet /availability, each entry also carries `endpoints`: which enclaves can mount it (a volume only lives where its enclave declares it).",
            "items": {
              "type": "object",
              "properties": {
                "name": { "type": "string", "example": "qwen2.5-0.5b" },
                "bytes": { "type": "integer", "description": "total size on disk", "example": 483003582 },
                "onnx": { "type": "boolean", "description": "the dir contains an ONNX model (model.onnx or *.onnx)" },
                "gguf": { "type": "boolean", "description": "the dir carries an unambiguous GGUF (or split-GGUF family) the runtime preloads as a named wasi-nn graph on the llama.cpp backend" },
                "sd": { "type": "boolean", "description": "the volume is an image checkpoint preloaded through the stable-diffusion.cpp wasi-nn backend (image generation)" },
                "endpoints": { "type": "array", "items": { "type": "string" }, "description": "fleet /availability only: enclaves carrying this volume" }
              }
            }
          },
          "source": {
            "type": "string",
            "enum": [
              "worker",
              "vmmanager",
              "fallback"
            ]
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "vcpusFree": {
            "type": "number",
            "description": "Free vCPUs on the node (cpuShareFree \u00d7 nodeVcpus).",
            "example": 14.4
          },
          "ramGbFree": {
            "type": "number",
            "description": "Free RAM (GB) on the node (cpuShareFree \u00d7 nodeRamGb).",
            "example": 57.6
          },
          "nodeVcpus": {
            "type": "integer",
            "description": "Node size in vCPUs.",
            "example": 16
          },
          "nodeRamGb": {
            "type": "integer",
            "description": "Node RAM (GB).",
            "example": 64
          },
          "nodeGflops": {
            "type": "number",
            "description": "CPU compute per node (GFLOPS; ~1000 for 16 vCPU); routers divide exact compute asks by this.",
            "example": 1000
          }
        },
        "example": {
          "gpu": true,
          "type": "gpu",
          "gpuShareFree": 0.55,
          "cpuShareFree": 0.9,
          "usedGpuShare": 0.45,
          "usedCpuShare": 0.1,
          "maxShare": 0.55,
          "vcpusFree": 14.4,
          "ramGbFree": 57.6,
          "cpuGflopsFree": 900,
          "nodeVcpus": 16,
          "nodeRamGb": 64,
          "nodeGflops": 1000,
          "smFree": 72,
          "smTotal": 132,
          "vramFreeGb": 77,
          "gpuTflopsFree": 543.9,
          "cardVramGb": 141,
          "cardTflops": 989,
          "cards": 1,
          "source": "vmmanager",
          "updatedAt": "2026-06-29T17:04:30Z"
        }
      },
      "Gpu": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "role": {
            "type": "string",
            "example": "manager"
          },
          "mpsActive": {
            "type": "boolean"
          },
          "capacity": {
            "type": "object",
            "description": "Live allocator capacity."
          },
          "smTotal": {
            "type": "integer",
            "example": 132
          },
          "tenants": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "pct": {
                  "type": "integer",
                  "example": 25
                },
                "status": {
                  "type": "string",
                  "example": "running"
                },
                "smGranted": {
                  "type": "integer",
                  "description": "SMs the MPS cap actually granted.",
                  "example": 33
                }
              }
            }
          }
        }
      },
      "ImageRef": {
        "type": "object",
        "description": "Reference to the Wasm app to run: a wasi:http component published in the on-chain app catalog, addressed by IPFS CID. The enclave fetches the bytes, verifies they hash to the CID (a tampering gateway fails the check), and folds the verified CID into the attestation.",
        "required": [
          "reference"
        ],
        "properties": {
          "reference": {
            "type": "string",
            "description": "`catalog://<appId>/<versionIndex>` - the on-chain record of a wasi:http component version the catalog owner has APPROVED in the app catalog. Nothing else deploys: the enclave image ships no deployable apps, CID references are refused (a CID names bytes, not a version - versions sharing bytes can differ in approved config), and the record supplies the wasm, config and ports. No arbitrary registry pull.",
            "example": "catalog://0x06a4ac8d5440fcfc3c9a6a028d1704c8f878d53555ed12016d3c81de062ecc13/5"
          },
          "digest": {
            "type": "string",
            "description": "Optional sha256 of the wasm component. If set, it is pinned and folded into the measurement.",
            "example": "sha256:9f86..."
          }
        }
      },
      "ResourceSpec": {
        "type": "object",
        "description": "The TWO dials a deployment buys: gpuShare (0 to 1 of ONE GPU card - VRAM and compute move together; 0 = CPU-only app) and cpuShare (0 to 1 of the node's vCPU+RAM; also sets the wasm guest's memory cap: cpuShare x node RAM). The app's exact specs in the catalog (vramMb/gpuGflops/memMb/cpuGflops) set the MINIMUM shares: each pool's minimum is the spec divided by this server's spec (the larger of the memory and compute axes, rounded up to the whole percent; a GPU app's CPU minimum also lifts its GPU minimum). Requests below either minimum are refused (422). A GPU app's gpuShare must be >= its cpuShare. Routing: gpuShare > 0 needs a GPU enclave; CPU-only apps go to CPU-only enclaves first, and to GPU enclaves with leftover CPU/RAM (a tenant buying 100% GPU + 10% CPU leaves 90% of that node for CPU-only apps). Exact-resource fields are refused here - apps declare those in the catalog, deployments buy shares.",
        "properties": {
          "gpuShare": {
            "type": "number",
            "minimum": 0,
            "maximum": 1,
            "description": "Share of one GPU card (VRAM + compute), 0 to 1. 0 = CPU-only app. Must be >= the app's minimum and >= cpuShare when > 0. Dialed in whole percent. A share > 0 also grants the app the wasi-nn interface: ONNX (ONNX Runtime), GGUF (llama.cpp) and image-diffusion (stable-diffusion.cpp) inference through the enclave's runtime, hardware-capped per process via MPS at exactly this share (SM% = share, VRAM = share x 141 GB). ExecutionTarget gpu is strict - the backend runs on CUDA or load() fails; no silent CPU fallback. Apps importing wasi:nn fail to instantiate when deployed with gpuShare 0.",
            "example": 0.25
          },
          "cpuShare": {
            "type": "number",
            "minimum": 0.01,
            "maximum": 1,
            "description": "Share of the node's vCPU+RAM, 0 to 1. Must be >= the app's minimum. Defaults to the app's minimum (catalog apps) or 0.05. Dialed in whole percent.",
            "example": 0.05
          }
        }
      },
      "NetworkSpec": {
        "type": "object",
        "properties": {
          "port": {
            "type": "integer",
            "example": 8080
          },
          "protocol": {
            "type": "string",
            "example": "https"
          },
          "endpoint": {
            "type": "string",
            "description": "Public URL on the enclave's single pinned origin: a path under the shim.",
            "example": "https://svalbard.enclave.containers.tinfoil.dev/x/dep_3xK9f2Qa"
          },
          "address": {
            "type": "string",
            "description": "The deployment's DEDICATED IPv6 (present when it declares tcp/udp ports and the fleet has dedicated addressing on). Deterministic from the id; its declared tcp:N ports are reachable at [address]:N via the tcp6-relay and its udp:N ports at [address]:N via the udp-relay - at the real port the app declared, no SNI, no remapping.",
            "example": "2a01:4f9:c013:9b52:a1b2:c3d4:e5f6:7890"
          },
          "tcp": {
            "type": "object",
            "description": "Dedicated-IP TCP endpoint: connect to [address]:port for each declared tcp:N (any protocol - raw passthrough; the app owns TLS/plaintext).",
            "properties": {
              "address": { "type": "string", "example": "2a01:4f9:c013:9b52:a1b2:c3d4:e5f6:7890" },
              "ports": { "type": "array", "items": { "type": "integer" }, "example": [5432, 443] }
            }
          },
          "udp": {
            "type": "object",
            "description": "Dedicated-IP UDP endpoint: send to [address]:port for each declared udp:N.",
            "properties": {
              "address": { "type": "string", "example": "2a01:4f9:c013:9b52:a1b2:c3d4:e5f6:7890" },
              "ports": { "type": "array", "items": { "type": "integer" }, "example": [9000] }
            }
          }
        }
      },
      "AttestationSummary": {
        "type": "object",
        "properties": {
          "available": {
            "type": "boolean"
          },
          "vmTechnology": {
            "type": [
              "string",
              "null"
            ],
            "description": "CPU-TEE technology, detected from the enclave's own attestation document (amd-sev-snp on today's fleet; intel-tdx verifies through the same pipeline). null only in the brief window before the enclave has fetched its own document.",
            "example": "amd-sev-snp"
          },
          "gpuTechnology": {
            "type": [
              "string",
              "null"
            ],
            "example": "nvidia-cc"
          },
          "href": {
            "type": "string",
            "example": "/v1/deployments/dep_3xK9f2Qa/attestation"
          }
        }
      },
      "CreateDeployment": {
        "type": "object",
        "required": [
          "image",
          "resources"
        ],
        "properties": {
          "image": {
            "$ref": "#/components/schemas/ImageRef"
          },
          "resources": {
            "$ref": "#/components/schemas/ResourceSpec"
          },
          "attestationPolicy": {
            "type": "object",
            "properties": {
              "requireGpuAttestation": {
                "type": "boolean"
              }
            }
          },
          "region": {
            "type": "string",
            "example": "auto"
          },
          "public": {
            "type": "boolean",
            "default": false,
            "description": "If true, anyone can reach the app's data path (`/x/{id}/*`) with no authentication, for hosting public websites, APIs, and servers. If false (default), only the owner's SIWE bearer token can reach it. Management endpoints are always owner-only regardless. Confidentiality is unaffected: the TEE still hides the app from the operator; `public` only governs who may send it requests."
          },
          "firewall": {
            "type": "object",
            "description": "Ports the app may bind, from its catalog version's firewall config (the store's Use-in-Deploy prefills this). Entries: `http` (default: standard wasi:http web app), `http:N` (app serves HTTP on N itself), `tcp:N`, `udp:N`; N in 1-19999 excluding the reserved loopback ports 1080/8080/8090/8091 (labels below 1024, e.g. udp:53, are always remapped to an unprivileged actual). Omitted/empty = standard web app with NO socket capability. Ports are LOGICAL: each deployment gets its own actual loopback bind (see `portMap`; passed to the app as `ENCLAVE_PORTS=tcp:5432=31245`), so multiple tenants can run the same app simultaneously without conflicts. When ports are declared the app gets wasi:sockets and the enclave audits its binds; binding an unassigned port kills the app. Declared TCP ports are reached through the attested origin as a WebSocket bridge at `/x/{id}/tcp/{logical-port}` (auth follows `public`).",
            "properties": {
              "ports": {
                "type": "array",
                "maxItems": 8,
                "items": {
                  "type": "string",
                  "pattern": "^(http|http:\\d{1,5}|tcp:\\d{1,5}|udp:\\d{1,5})$"
                },
                "example": [
                  "tcp:5432",
                  "udp:9053"
                ]
              }
            }
          }
        },
        "example": {
          "image": {
            "reference": "catalog://0x06a4ac8d5440fcfc3c9a6a028d1704c8f878d53555ed12016d3c81de062ecc13/5"
          },
          "resources": {
            "gpuShare": 0.25,
            "cpuShare": 0.05
          },
          "public": true,
          "attestationPolicy": {
            "requireGpuAttestation": true
          },
          "region": "auto"
        }
      },
      "Deployment": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "dep_3xK9f2Qa"
          },
          "owner": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/DeploymentStatus"
          },
          "public": {
            "type": "boolean",
            "description": "Whether the app's data path is publicly reachable (no auth) or owner-only. Management stays owner-only either way."
          },
          "image": {
            "$ref": "#/components/schemas/ImageRef"
          },
          "resources": {
            "type": "object",
            "description": "The two shares this deployment bought (its app's catalog specs only set the minimums).",
            "properties": {
              "gpuShare": {
                "type": "number",
                "description": "The slice of one GPU card held (0 = CPU-only app)."
              },
              "cpuShare": {
                "type": "number",
                "description": "The slice of the node's vCPU+RAM held; the wasm guest's memory cap is cpuShare x node RAM."
              },
              "cardId": {
                "type": "integer",
                "description": "Which card holds the GPU slice (GPU deployments only)."
              }
            }
          },
          "network": {
            "$ref": "#/components/schemas/NetworkSpec"
          },
          "attestation": {
            "$ref": "#/components/schemas/AttestationSummary"
          },
          "region": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "startedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set when payment provisions the worker."
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "ESTIMATED wall-clock expiry (now + timeRemainingSec) while running and healthy; null when the clock is frozen (paused) or awaiting payment. The balance in timeRemainingSec is authoritative."
          },
          "digest": {
            "type": [
              "string",
              "null"
            ]
          },
          "payRef": {
            "type": "string",
            "description": "bytes32 payment reference (pass to payWithAuthorization())."
          },
          "ratePerSecondUsdc": {
            "type": "string",
            "example": "0.0004167"
          },
          "spentUsdc": {
            "type": "string",
            "description": "Elapsed runtime cost so far (informational).",
            "example": "0.41"
          },
          "paidUsdc": {
            "type": "string",
            "description": "Total USDC paid into this deployment.",
            "example": "10.00"
          },
          "timeRemainingSec": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Funded runtime balance in seconds (null = unlimited pilot deployment). Drains only while the platform is healthy; frozen (see paused) during outages, so it never shrinks while service is down.",
            "example": 21600
          },
          "paused": {
            "type": "boolean",
            "description": "True while the billing clock is FROZEN because the platform is not serving (enclave backend down, payment watcher blind, or the app instance is being respawned). Time resumes automatically when service is restored; users are never billed for downtime.",
            "example": false
          },
          "pauseReason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why the clock is frozen: backend_down | watcher_stale | instance_missing | restart_recovery.",
            "example": null
          },
          "payment": {
            "$ref": "#/components/schemas/PaymentInstructions"
          },
          "firewall": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Normalized firewall config this deployment runs with ([] = standard web app). Declared TCP ports are bridgeable at /x/{id}/tcp/{port}."
          },
          "portMap": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Logical firewall entry -> actual loopback port this deployment bound (e.g. {\"tcp:5432\": 31245}). Set once provisioned. The bridge URL always uses the logical port; this mapping is informational.",
            "example": {
              "tcp:5432": 31245
            }
          }
        }
      },
      "DeploymentList": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Deployment"
            }
          },
          "cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "TerminationReceipt": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "example": "terminated"
          },
          "paidUsdc": {
            "type": "string",
            "example": "10.00"
          },
          "ranSeconds": {
            "type": "integer",
            "example": 1240
          },
          "note": {
            "type": "string",
            "example": "On-chain deployment: lease released (unused lease time refunded to its balance). It stays claimable by any enclave while active and funded - call setActive(false) on EnclaveDeployments to stop it for good."
          }
        }
      },
      "TopupResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/DeploymentStatus"
          },
          "timeRemainingSec": {
            "type": "integer",
            "example": 3600
          },
          "payment": {
            "$ref": "#/components/schemas/PaymentInstructions",
            "description": "Legacy pre-on-chain records only. Absent for on-chain deployments, which get `funding` instead."
          },
          "funding": {
            "type": "object",
            "description": "On-chain deployments only: fund the EnclaveDeployments contract (fundWithAuthorization / fundEth); a payment to the legacy forwarder cannot credit an on-chain balance.",
            "properties": {
              "contract": { "type": "string", "nullable": true, "description": "EnclaveDeployments contract address on Base." },
              "chainId": { "type": "integer", "example": 8453 },
              "usdc": { "type": "string" },
              "ratePerSecondUsdc": { "type": "string", "example": "0.000917" },
              "fundMethod": { "type": "string", "example": "fundWithAuthorization(bytes32 id, address from, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes signature)" },
              "fundEthMethod": { "type": "string", "example": "fundEth(bytes32 id) payable" },
              "usdcDomain": { "type": "object", "nullable": true, "description": "USDC EIP-712 domain to sign the ReceiveWithAuthorization against (to = the contract; nonce = first 16 bytes of the id + 16 random bytes)." },
              "ethUsd": { "type": "string", "nullable": true },
              "note": { "type": "string" }
            }
          }
        }
      },
      "Measurements": {
        "type": "object",
        "description": "Registers parsed out of the report (AMD SEV-SNP: measurement; Intel TDX: mrTd + rtmr0-3). Compare against the Sigstore-signed measurements published on the enclave repo's release.",
        "additionalProperties": true
      },
      "VmAttestation": {
        "type": "object",
        "description": "Live CPU-TEE evidence. `quote` is the raw hardware quote (base64, decompressed); feed it to any DCAP/SNP verifier. `reportData` binds the enclave TLS key: bytes 0-31 are sha256 of the TLS public key (SPKI DER) this origin presents.",
        "properties": {
          "technology": {
            "type": "string",
            "description": "Detected from the attestation document's format: amd-sev-snp (today's fleet) or intel-tdx.",
            "example": "amd-sev-snp"
          },
          "quote": {
            "type": "string",
            "description": "Base64 raw quote."
          },
          "quoteVersion": {
            "type": [
              "integer",
              "null"
            ],
            "description": "DCAP quote version - Intel TDX only; absent for AMD SEV-SNP reports.",
            "example": 4
          },
          "measurements": {
            "$ref": "#/components/schemas/Measurements"
          },
          "reportData": {
            "type": [
              "string",
              "null"
            ],
            "description": "64-byte report_data, hex."
          }
        },
        "additionalProperties": true
      },
      "GpuAttestation": {
        "type": "object",
        "description": "NVIDIA confidential-computing evidence, signed by the GPU over `nonce`. Verify `report` + `certChain` with NVIDIA NRAS or nvtrust's local_gpu_verifier and confirm it signs your nonce.",
        "properties": {
          "technology": {
            "type": "string",
            "example": "nvidia-cc"
          },
          "ccMode": {
            "type": [
              "string",
              "null"
            ],
            "example": "on"
          },
          "nonce": {
            "type": "string",
            "description": "32-byte hex freshness challenge folded into the report."
          },
          "driverVersion": {
            "type": [
              "string",
              "null"
            ]
          },
          "report": {
            "type": [
              "string",
              "null"
            ],
            "description": "Base64 GPU attestation report (first card)."
          },
          "certChain": {
            "type": [
              "string",
              "null"
            ],
            "description": "Base64 attestation cert chain (first card)."
          },
          "gpus": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "EnclaveAttestation": {
        "type": "object",
        "description": "The Tinfoil Remote Attestation Document relayed verbatim from /.well-known/tinfoil-attestation; feed `attestationDocument` to Tinfoil's verifier (tinfoil-cli `attestation verify`, or the @tinfoilsh/verifier npm package). This copy is a convenience: fetch the document over your OWN connection for the binding to mean anything.",
        "properties": {
          "attestationDocument": {
            "type": "object",
            "properties": {
              "format": {
                "type": "string",
                "example": "https://tinfoil.sh/predicate/sev-snp-guest/v2"
              },
              "body": {
                "type": "string",
                "description": "Base64 (gzip-compressed) hardware attestation report."
              }
            }
          },
          "fetchedFrom": {
            "type": "string"
          },
          "fetchedAt": {
            "type": "string",
            "format": "date-time"
          },
          "observedTlsKeyFingerprint": {
            "type": [
              "string",
              "null"
            ],
            "description": "sha256 (hex) of the TLS public key the shim presented when this copy was fetched."
          }
        },
        "additionalProperties": true
      },
      "Attestation": {
        "type": "object",
        "description": "Live attestation material. The server never asserts trust on your behalf: `verification.selfCheck` is the enclave running the same five checks a client would, reported as a clearly-labeled diagnostic; reproduce it on your side (cli/npm/browser pointers included) for it to carry trust.",
        "properties": {
          "deploymentId": {
            "type": "string"
          },
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "verification": {
            "type": "object",
            "description": "`selfCheck`: the enclave verifying ITSELF with @tinfoilsh/verifier; `result` is `pass`/`fail`/`pending`/`unavailable` with per-step detail (`fetchDigest`, `verifyEnclave`, `verifyCode`, `compareMeasurements`, `verifyCertificate`), the release digest and measurement it matched, and a note explaining that a self-check is a diagnostic, not trust. Alongside it, how to reproduce client-side: instructions (`how`), a ready-to-run tinfoil-cli command (`cli`), the npm verifier package (`npm`, works in Node and browsers), an in-browser verifier (`browser`), the enclave repo whose release holds the signed measurements (`repo`, exact GitHub casing), and the well-known attestation endpoint.",
            "properties": {
              "selfCheck": {
                "type": "object",
                "properties": {
                  "result": {
                    "type": "string",
                    "enum": [
                      "pass",
                      "fail",
                      "pending",
                      "unavailable"
                    ],
                    "example": "pass"
                  }
                },
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          },
          "tlsKeyFingerprint": {
            "type": [
              "string",
              "null"
            ],
            "description": "sha256:<hex> of the enclave TLS public key, extracted from the quote's report_data.",
            "example": "sha256:4b8c\u2026e2a1"
          },
          "enclave": {
            "$ref": "#/components/schemas/EnclaveAttestation"
          },
          "vm": {
            "$ref": "#/components/schemas/VmAttestation"
          },
          "gpu": {
            "$ref": "#/components/schemas/GpuAttestation"
          },
          "app": {
            "type": "object",
            "description": "What app ran, with an honest `coverage` note: catalog apps are covered by the enclave measurement; ipfs apps are CID-verified by attested code but not in a hardware register.",
            "additionalProperties": true
          },
          "guideUrl": {
            "type": "string",
            "example": "https://enclave.host/#attest"
          }
        }
      },
      "Health": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "ok"
          },
          "deployments": {
            "type": "integer",
            "example": 2
          }
        }
      },
      "Version": {
        "type": "object",
        "properties": {
          "service": {
            "type": "string",
            "example": "enclave-supervisor/0.1.0"
          },
          "contract": {
            "type": "string",
            "example": "enclave-openapi/1.0.0"
          },
          "chainId": {
            "type": "integer",
            "example": 8453
          }
        }
      }
    }
  }
}
