{
  "openapi": "3.1.0",
  "info": {
    "title": "Printwright API",
    "version": "1.9.1",
    "description": "Agent-buyable marketplace for licensed 3D-printable models. Paid downloads use the x402 protocol (HTTP 402) settling on Hedera testnet in HBAR or USDC; each delivery returns a portable license proof bundle and queues its opaque HCS commitment for asynchronous anchoring.",
    "contact": { "url": "https://github.com/Nikodem01/printwright-x402" },
    "x-guidance": "Search GET /api/v1/models, inspect offers, then buy one license with GET /api/v1/models/{id}/download or many with POST /api/v1/batches (up to 200 line items and 250 licenses; per-line quantity buys N licenses). An unpaid request returns an x402 v2 challenge; sign one exact payment option and retry the identical request. Delivery always carries `files`: a paid request never returns 200 without one, and a settled purchase that cannot be delivered returns 503 no_deliverable_file and stays claimable. KEEP the `receipt` capability — the `files` URLs ride a download grant that expires, but GET {receipt.files_url}?token={receipt.token} re-lists live download URLs indefinitely, with no account. There are no refunds, so store receipt.token at settlement. Paid requests settle to Printwright's treasury; designer payout happens separately after delivery and does not change buyer rights. For a zero-funds rehearsal, send X-Sandbox: true on both requests; sandbox artifacts are explicitly not payments or licenses."
  },
  "servers": [{ "url": "/api/v1" }],
  "paths": {
    "/batches": {
      "post": {
        "operationId": "buyLicenseBatch",
        "summary": "Buy many licenses with one x402 settlement",
        "description": "Send the same JSON body for both legs. The first returns one aggregate x402 challenge to Printwright's treasury; the signed retry settles once and returns one independently verifiable certificate per item, including mixed-designer batches. Designer payout happens separately after delivery. An optional buyer webhook reports certificate.anchored only after the paid commitment lands on HCS; it never creates a license.",
        "security": [],
        "x-payment-info": {
          "price": { "mode": "dynamic", "currency": "USD", "min": "0.01", "max": "200000.00" },
          "protocols": [{ "x402": {} }]
        },
        "parameters": [
          { "name": "PAYMENT-SIGNATURE", "in": "header", "schema": { "type": "string" }, "description": "base64 x402 PaymentPayload from the aggregate challenge" },
          { "name": "X-Sandbox", "in": "header", "schema": { "type": "string", "const": "true" }, "description": "zero-funds rehearsal; no printable files or Hedera records" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchRequest" } } }
        },
        "responses": {
          "200": {
            "description": "One settlement and one delivery/certificate link per requested license",
            "headers": {
              "PAYMENT-RESPONSE": { "schema": { "type": "string" }, "description": "base64 settlement response" },
              "X-Printwright-Sandbox": { "schema": { "type": "string", "const": "true" } }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BatchDelivery" } } }
          },
          "400": { "description": "invalid_batch or invalid_payload", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "402": {
            "description": "Aggregate PaymentRequired challenge. After a signed retry, error=offer_changed returns fresh current-revision requirements when an offer changed before reservation.",
            "headers": {
              "PAYMENT-REQUIRED": { "schema": { "type": "string" }, "description": "base64 of the same PaymentRequired JSON" },
              "WWW-Authenticate": { "schema": { "type": "string", "const": "x402" } }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaymentRequired" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "Replayed payment (delivered batches return the original BatchDelivery), or error: sales_paused before a new reservation", "content": { "application/json": { "schema": { "oneOf": [ { "$ref": "#/components/schemas/BatchDelivery" }, { "$ref": "#/components/schemas/Error" } ] } } } },
          "410": { "description": "At least one requested offer is sold out, or error: listing_retired before a new reservation", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "incompatible_payees or invalid_webhook", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "description": "facilitator_unavailable, or no_deliverable_file (+ recoverable: true, batch_id, transaction_id, model_ids) when the batch settled but a model in it has no downloadable file. Either way, retry the identical signed batch; nothing is paid again.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/models": {
      "get": {
        "operationId": "searchModels",
        "summary": "Search the catalog (keyword + trigram fuzzy matching)",
        "security": [],
        "parameters": [
          { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "keywords, e.g. 'beaver hat'" },
          { "name": "category", "in": "query", "schema": { "type": "string", "enum": ["desk-organization", "home-and-garden", "kitchen-and-dining", "workshop-tools", "replacement-parts", "toys-and-games"] } },
          { "name": "collection", "in": "query", "schema": { "type": "string", "enum": ["support-free-essentials", "under-an-hour", "small-space", "maker-basics"] } },
          { "name": "material", "in": "query", "schema": { "type": "string" }, "description": "e.g. PLA" },
          { "name": "supports", "in": "query", "schema": { "type": "string", "enum": ["true", "false"] } },
          { "name": "max_price_cents", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": {
            "description": "Ranked results",
            "content": { "application/json": { "schema": {
              "type": "object",
              "properties": {
                "count": { "type": "integer" },
                "models": { "type": "array", "items": { "$ref": "#/components/schemas/ModelSummary" } }
              }
            } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/models/{id}": {
      "get": {
        "operationId": "getModel",
        "summary": "Full model metadata (offers, terms, file hash)",
        "security": [],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": {
          "200": { "description": "Model detail", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ModelDetail" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/models/{id}/download": {
      "get": {
        "operationId": "downloadModel",
        "summary": "The paid endpoint: x402 negotiation",
        "description": "Without a payment header, responds 402 with PaymentRequirements (JSON body AND base64 PAYMENT-REQUIRED header). Retry the same request with the base64 PaymentPayload in the PAYMENT-SIGNATURE header (X-PAYMENT also accepted). The server verifies and settles through an x402 facilitator on Hedera. Set X-Sandbox: true on both legs for the built-in zero-funds mock facilitator; sandbox results never contain printable geometry or on-chain proof.",
        "x-payment-info": {
          "price": { "mode": "dynamic", "currency": "USD", "min": "0.01", "max": "10000.00" },
          "protocols": [{ "x402": {} }]
        },
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } },
          { "name": "license", "in": "query", "schema": { "type": "string", "enum": ["personal", "commercial_unit"], "default": "personal" } },
          { "name": "PAYMENT-SIGNATURE", "in": "header", "schema": { "type": "string" }, "description": "base64 x402 PaymentPayload (v2). X-PAYMENT is accepted as an alias." },
          { "name": "X-Sandbox", "in": "header", "schema": { "type": "string", "const": "true" }, "description": "Opt into the local mock facilitator and throwaway topic. Send on both the 402 probe and retry. No credentials or funds; no real license or printable model." }
        ],
        "responses": {
          "402": {
            "description": "Payment required — PaymentRequired object in body and PAYMENT-REQUIRED header. `error` is \"payment required\" when no payment is attached, \"invalid_payment_requirements\" when the attached payment matches none of the offer's `accepts` entries, or \"offer_changed\" when a revision won the reservation race (all return fresh requirements to retry against).",
            "headers": {
              "PAYMENT-REQUIRED": { "schema": { "type": "string" }, "description": "base64 of the same PaymentRequired JSON" },
              "WWW-Authenticate": { "schema": { "type": "string", "const": "x402" }, "description": "Marks the challenge as x402 for generic HTTP-payment discovery clients" },
              "X-Printwright-Sandbox": { "schema": { "type": "string", "const": "true" }, "description": "Present only on an opted-in sandbox challenge" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PaymentRequired" } } }
          },
          "200": {
            "description": "Paid — delivery payload; settlement receipt in PAYMENT-RESPONSE header (X-PAYMENT-RESPONSE mirror)",
            "headers": {
              "PAYMENT-RESPONSE": { "schema": { "type": "string" }, "description": "base64 settlement response incl. transaction id" },
              "X-Printwright-Sandbox": { "schema": { "type": "string", "const": "true" }, "description": "Present only on an opted-in sandbox delivery" }
            },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Delivery" } } }
          },
          "400": {
            "description": "error: \"invalid_payload\" — the PAYMENT-SIGNATURE header is not decodable base64 JSON",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": {
            "description": "Replay of an already-presented payment. If the purchase already reached \"delivered\", the original Delivery payload is returned again unchanged (an idempotent replay, not an error); for any other in-flight status the body is {error: \"duplicate_payment\", status} naming the purchase's current status. A new purchase on a temporarily paused listing returns {error: \"sales_paused\", listing_status: \"paused\"} before reservation.",
            "content": { "application/json": { "schema": { "oneOf": [
              { "$ref": "#/components/schemas/Delivery" },
              { "$ref": "#/components/schemas/Error" }
            ] } } }
          },
          "410": {
            "description": "error: \"sold_out\" — the offer's max_units is exhausted; or error: \"listing_retired\" when the designer has retired the listing before a new reservation. Capacity is decided under the offer lock before a payment is accepted, so a sold-out offer refuses instead of charging. A sold_out response carrying transaction_id is a defensive backstop that flags the payment for operator review.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": {
            "description": "error: \"facilitator_unavailable\" (+ retry_after: 5) — facilitator down or its circuit breaker is open. The purchase is kept alive; retry the same signed payment. Or error: \"no_deliverable_file\" (+ recoverable: true, cert_id, transaction_id, receipt) — the payment settled and the license is issued, but the model has no downloadable file right now. Retry the same signed payment once it is back, or use the returned receipt capability later; nothing is paid again.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/certificates/{cert_id}": {
      "get": {
        "operationId": "getCertificate",
        "summary": "Proof bundle for one certificate + HCS anchoring links",
        "security": [],
        "parameters": [{ "name": "cert_id", "in": "path", "required": true, "schema": { "type": "string" }, "example": "pw-a47531e0723a76beaa7e7b29" }],
        "responses": {
          "200": {
            "description": "The certificate's proof bundle (revealed certificate, blinding nonce, commitment, licensed terms, HCS coordinates) with status anchored|minting|sandbox",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CertificateResponse" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/licenses/{id}/can": {
      "get": {
        "operationId": "checkLicense",
        "summary": "Decide whether one certificate permits a named use and quantity",
        "description": "Returns a machine-decidable interpretation only when its canonical v1 prose matches the certificate's anchored terms_hash. The prose remains the legal grant. Sandbox receipts always return allowed=false.",
        "security": [],
        "parameters": [
          { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "example": "pw-000036", "description": "certificate disclosure id returned to the paid holder" },
          { "name": "use", "in": "query", "required": true, "schema": { "type": "string", "enum": ["personal_print", "commercial_print", "resell_files", "share_files", "personal_remix", "commercial_remix", "transfer_license", "sublicense"] } },
          { "name": "qty", "in": "query", "schema": { "type": "integer", "minimum": 1, "maximum": 1000000, "default": 1 } }
        ],
        "responses": {
          "200": { "description": "Allowed or denied decision with stable reason code, structured permissions, certificate URL, and anchored terms references", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LicenseDecision" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "permissions_unavailable — no structured policy matches the certificate's anchored terms hash", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "invalid_use or invalid_quantity", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/licenses/{cert_id}/print_reports": {
      "post": {
        "operationId": "reportSuccessfulPrint",
        "summary": "Report one successful print using a paid receipt capability",
        "description": "The signed receipt_token is returned only by a real non-sandbox x402 delivery and is bound to one certificate. A report is idempotent per license. This is a paid holder self-report, not independent physical inspection, and it never changes the certificate or payment ledger.",
        "security": [],
        "parameters": [
          { "name": "cert_id", "in": "path", "required": true, "schema": { "type": "string" }, "example": "pw-000059" }
        ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["receipt_token"], "additionalProperties": false, "properties": {
            "receipt_token": { "type": "string", "writeOnly": true }
          } } } }
        },
        "responses": {
          "200": { "description": "Existing report returned idempotently", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PrintReportReceipt" } } } },
          "201": { "description": "Successful-print report recorded", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PrintReportReceipt" } } } },
          "403": { "description": "paid_license_required — sandbox or non-delivered license", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/licenses/{cert_id}/latest-version": {
      "get": {
        "operationId": "getLatestModelVersion",
        "summary": "Check the latest printable file for a paid x402 license",
        "description": "Requires the private model_updates.receipt_token returned by the real non-sandbox delivery as an Authorization: Bearer header. Version 1 is the original certified bundle. Later versions are additive: the original PWC-1 model_hash never changes, while each changelog hash and replacement file hash are anchored in a PWV-1 HCS event. `files` lists every part of a multi-file bundle in order; `file_kind`/`file_hash` always describe the primary (first) part.",
        "security": [{ "PaidReceipt": [] }],
        "parameters": [{ "name": "cert_id", "in": "path", "required": true, "schema": { "type": "string" }, "example": "pw-000059" }],
        "responses": {
          "200": { "description": "Latest version metadata and authenticated download endpoint", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ModelVersionReceipt" } } } },
          "401": { "description": "receipt_required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "paid_license_required — sandbox or non-delivered license", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/licenses/{cert_id}/latest-version/file": {
      "get": {
        "operationId": "downloadLatestModelVersion",
        "summary": "Download the latest printable file for a paid x402 license",
        "description": "Send the same model_updates.receipt_token as an Authorization: Bearer header. Redirects to version 1's original file when no update exists, otherwise to the highest published version file. Pass `f` to download a specific part of a multi-file bundle instead of the primary file.",
        "security": [{ "PaidReceipt": [] }],
        "parameters": [
          { "name": "cert_id", "in": "path", "required": true, "schema": { "type": "string" }, "example": "pw-000059" },
          { "name": "f", "in": "query", "schema": { "type": "integer", "minimum": 0 }, "description": "0-based index into the version's file bundle; omitted serves the primary file" }
        ],
        "responses": {
          "302": { "description": "Redirect to the latest printable file blob" },
          "401": { "description": "receipt_required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "403": { "description": "paid_license_required", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/stats": {
      "get": {
        "operationId": "getOpenBooks",
        "summary": "Configured HCS topic and immutable revenue-ledger metrics",
        "description": "Separates the configured HCS topic's latest public message sequence from the settlement totals retained in this deployment's immutable ledger. A reused historical topic can contain superseded certificate formats as well as current commitments and model-version events. Amounts are exact asset base units; sandbox rehearsals are excluded.",
        "security": [],
        "responses": {
          "200": { "description": "Current open-books snapshot; hcs.status=unavailable labels mirror outages without presenting a local count as on-chain", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OpenBooksStats" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/files/{token}": {
      "get": {
        "operationId": "redeemDownloadGrant",
        "summary": "Redeem a download grant token (expiring, limited uses)",
        "security": [],
        "parameters": [{ "name": "token", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "302": { "description": "Redirect to the file blob" },
          "410": {
            "description": "error: \"grant_expired\" — the grant is expired or its use-count is exhausted",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/sandbox/topics/{topic_id}/messages/{sequence_number}": {
      "get": {
        "operationId": "getSandboxTopicMessage",
        "summary": "Read a local sandbox certificate envelope (not HCS)",
        "security": [],
        "parameters": [
          { "name": "topic_id", "in": "path", "required": true, "schema": { "type": "string", "const": "printwright-sandbox" } },
          { "name": "sequence_number", "in": "path", "required": true, "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Base64 sandbox message in a mirror-like envelope, always labeled sandbox", "content": { "application/json": { "schema": { "type": "object" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/sandbox/files/{cert_id}": {
      "get": {
        "operationId": "downloadSandboxReceipt",
        "summary": "Download the non-printable sandbox receipt",
        "security": [],
        "parameters": [{ "name": "cert_id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Plain-text sandbox receipt; never model geometry", "content": { "text/plain": { "schema": { "type": "string" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/sandbox/transactions/{transaction_id}": {
      "get": {
        "operationId": "getSandboxTransaction",
        "summary": "Inspect a fake sandbox settlement",
        "security": [],
        "parameters": [{ "name": "transaction_id", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Local settlement record labeled sandbox; not a Hedera transaction", "content": { "application/json": { "schema": { "type": "object" } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "PaidReceipt": { "type": "http", "scheme": "bearer", "description": "Per-license model_updates.receipt_token from a real x402 delivery" }
    },
    "responses": {
      "NotFound": { "description": "Not found", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": {
        "description": "error: \"rate_limited\" (+ retry_after: 60) — request rate exceeded the endpoint's per-minute limit",
        "headers": { "Retry-After": { "schema": { "type": "integer" }, "description": "seconds until the limit resets (60)" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "message": { "type": "string" },
          "sandbox": { "type": "boolean" },
          "warning": { "type": "string" },
          "status": { "type": "string", "description": "included on 409 duplicate_payment: the existing purchase's current status" },
          "listing_status": { "type": "string", "enum": ["paused", "retired"], "description": "included when designer controls stop a new purchase; already-presented payment replays remain recoverable" },
          "retry_after": { "type": "integer", "description": "included on 429 rate_limited (60) and 503 facilitator_unavailable (5)" },
          "transaction_id": { "type": "string", "description": "included on 410 sold_out when the offer sold out after this payment had already settled" }
        }
      },
      "ModelSummary": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" }, "slug": { "type": "string" }, "title": { "type": "string" },
          "category": { "type": ["string", "null"] },
          "collections": { "type": "array", "items": { "type": "string" } },
          "designer": { "type": "object", "properties": { "name": { "type": "string" } } },
          "printability": { "$ref": "#/components/schemas/Printability" },
          "license_offers": { "type": "array", "items": { "type": "object", "properties": {
            "kind": { "type": "string" }, "price_cents": { "type": "integer" }, "currency": { "type": "string" },
            "max_units": { "type": ["integer", "null"] }, "remaining_units": { "type": ["integer", "null"] } } } },
          "url": { "type": "string" }, "render_url": { "type": ["string", "null"] }
        }
      },
      "Printability": {
        "type": "object",
        "description": "Designer-declared slicing guidance, not a guarantee of printer compatibility or evidence of a physical test.",
        "properties": {
          "supports": { "type": "boolean", "description": "Whether the declared print orientation requires support material." },
          "materials": { "type": "array", "items": { "type": "string" }, "description": "Suggested filament/material names such as PLA or PETG." },
          "est_print_minutes": { "type": "integer", "minimum": 1, "description": "Approximate print duration in minutes; slicer, profile, scale, and printer can change it." },
          "bed_min_mm": { "type": "integer", "minimum": 1, "description": "Designer-declared minimum square build-plate side in millimetres at the published scale." }
        },
        "additionalProperties": true
      },
      "ModelDetail": {
        "allOf": [
          { "$ref": "#/components/schemas/ModelSummary" },
          { "type": "object", "properties": {
            "description": { "type": "string" }, "tags": { "type": "array", "items": { "type": "string" } },
            "file_hash": { "type": "string" },
            "files": { "type": "array", "items": { "type": "object", "properties": {
              "kind": { "type": "string" }, "filename": { "type": ["string", "null"] } } } },
            "license_offers": { "type": "array", "items": { "type": "object", "properties": {
              "kind": { "type": "string" }, "price_cents": { "type": "integer" }, "currency": { "type": "string" },
              "max_units": { "type": ["integer", "null"] },
              "remaining_units": { "type": ["integer", "null"], "description": "Current license slots after active reservations; not a physical print count" },
              "terms": { "type": "object", "properties": {
                "version": { "type": ["string", "null"] }, "hash": { "type": "string" },
                "url": { "type": ["string", "null"] },
                "permissions_url": { "type": ["string", "null"] },
                "permissions": { "oneOf": [ { "$ref": "#/components/schemas/LicensePermissions" }, { "type": "null" } ] },
                "text": { "type": "string" } } }
            } } },
            "designer": { "type": "object", "properties": {
              "name": { "type": "string" }, "verified": { "type": "boolean" },
              "payout_destination_verified": { "type": "boolean", "description": "Platform-verified payout destination state. The destination account id itself is private; each license certificate records only the stable studio id." },
              "hedera_account_id": { "type": ["string", "null"], "deprecated": true, "description": "Always null. The current payout destination is the studio's private financial detail; the license certificate's designer field is a stable studio id, not a payout account." } } },
            "download_url": { "type": "string" } } }
        ]
      },
      "LicensePermissions": {
        "type": "object",
        "description": "Convenience interpretation of canonical license prose; the anchored prose governs.",
        "required": ["schema_version", "license_kind", "personal_use", "commercial_units", "resale_files", "share_files", "remix", "attribution", "transfer", "sublicense"],
        "properties": {
          "schema_version": { "type": "string", "const": "1" },
          "license_kind": { "type": "string", "enum": ["personal", "commercial_unit"] },
          "personal_use": { "type": "object", "properties": { "allowed": { "type": "boolean" }, "max_prints": { "type": ["integer", "null"] } } },
          "commercial_units": { "type": "object", "properties": { "allowed": { "type": "boolean" }, "max_units": { "type": "integer", "minimum": 0 } } },
          "resale_files": { "type": "object", "properties": { "allowed": { "type": "boolean" } } },
          "share_files": { "type": "object", "properties": { "allowed": { "type": "boolean" } } },
          "remix": { "type": "object", "properties": { "allowed": { "type": "boolean" }, "scope": { "type": "string", "enum": ["personal_noncommercial_only"] } } },
          "attribution": { "type": "object", "properties": { "required": { "type": "boolean" }, "false_authorship_prohibited": { "type": "boolean" } } },
          "transfer": { "type": "object", "properties": { "allowed": { "type": "boolean" } } },
          "sublicense": { "type": "object", "properties": { "allowed": { "type": "boolean" } } }
        },
        "additionalProperties": false
      },
      "LicenseDecision": {
        "type": "object",
        "required": ["cert_id", "use", "qty", "allowed", "reason_code", "reason"],
        "properties": {
          "cert_id": { "type": "string" },
          "use": { "type": "string" },
          "qty": { "type": "integer" },
          "license_kind": { "type": "string" },
          "unit_serial": { "type": "integer" },
          "allowed": { "type": "boolean" },
          "reason_code": { "type": "string" },
          "reason": { "type": "string" },
          "sandbox": { "type": "boolean" },
          "permissions": { "oneOf": [ { "$ref": "#/components/schemas/LicensePermissions" }, { "type": "null" } ] },
          "certificate_url": { "type": "string" },
          "terms": { "type": "object", "properties": {
            "version": { "type": "string" }, "hash": { "type": "string" },
            "url": { "type": "string" }, "permissions_url": { "type": "string" } } }
        }
      },
      "PrintFeedbackCapability": {
        "type": "object",
        "required": ["url", "receipt_token"],
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "receipt_token": { "type": "string", "writeOnly": true }
        }
      },
      "ModelUpdatesCapability": {
        "type": "object",
        "required": ["url", "download_url", "receipt_token"],
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "download_url": { "type": "string", "format": "uri" },
          "receipt_token": { "type": "string", "writeOnly": true }
        }
      },
      "ModelVersionReceipt": {
        "type": "object",
        "required": ["cert_id", "version", "file_kind", "file_hash", "original_certificate_hash", "download_url"],
        "properties": {
          "cert_id": { "type": "string" },
          "version": { "type": "integer", "minimum": 1 },
          "file_kind": { "type": "string", "enum": ["stl", "3mf", "step"] },
          "file_hash": { "type": "string" },
          "original_certificate_hash": { "type": "string", "description": "Immutable PWC-1 model_hash; differs from file_hash after an update" },
          "changelog": { "type": ["string", "null"] },
          "changelog_hash": { "type": ["string", "null"] },
          "published_at": { "type": ["string", "null"], "format": "date-time" },
          "hcs_topic_id": { "type": ["string", "null"] },
          "hcs_sequence_number": { "type": ["integer", "null"] },
          "hcs_transaction_id": { "type": ["string", "null"] },
          "hcs_mirror_url": { "type": ["string", "null"], "format": "uri" },
          "download_url": { "type": "string", "format": "uri" },
          "files_hash": { "type": ["string", "null"], "description": "sha256: over the RFC 8785 canonicalization of [{kind, hash}, ...] in order — the value the pwv-1 HCS event commits to. Recompute it from `files` to prove the bundle is the published one, whatever its size." },
          "files": { "type": "array", "description": "The version's ordered file bundle. file_hash may be absent for a part whose individual hash was never recorded (e.g. a pre-bundle original file beyond the primary).", "items": { "type": "object", "properties": {
            "kind": { "type": "string", "enum": ["stl", "3mf", "step"] },
            "file_hash": { "type": "string" },
            "download_url": { "type": "string", "format": "uri" }
          } } }
        }
      },
      "PrintReportReceipt": {
        "type": "object",
        "required": ["cert_id", "successful_prints"],
        "properties": {
          "cert_id": { "type": "string" },
          "successful_prints": { "type": "integer", "minimum": 1 }
        }
      },
      "OpenBooksStats": {
        "type": "object",
        "required": ["generated_at", "network", "split", "hcs", "ledger", "recent_settlement_proofs"],
        "properties": {
          "generated_at": { "type": "string", "format": "date-time" },
          "network": { "type": "string", "enum": ["hedera:testnet", "hedera:mainnet"] },
          "split": { "type": "object", "properties": {
            "designer_bps": { "type": "integer" }, "platform_bps": { "type": "integer" } } },
          "hcs": { "type": "object", "properties": {
            "status": { "type": "string", "enum": ["ok", "unavailable"] },
            "topic_id": { "type": "string" },
            "message_count": { "type": ["integer", "null"] },
            "latest_sequence_number": { "type": ["integer", "null"] },
            "latest_consensus_timestamp": { "type": ["string", "null"] },
            "local_anchored_licenses": { "type": "integer" },
            "count_difference": { "type": ["integer", "null"] },
            "mirror_url": { "type": ["string", "null"] },
            "latest_message_url": { "type": ["string", "null"] },
            "topic_url": { "type": "string" }
          } },
          "ledger": { "type": "object", "properties": {
            "settlement_count": { "type": "integer" },
            "assets": { "type": "array", "items": { "type": "object", "properties": {
              "asset": { "type": "string" }, "symbol": { "type": "string" },
              "decimals": { "type": ["integer", "null"] },
              "gross_settled_base_units": { "type": "integer" },
              "designer_share_base_units": { "type": "integer" },
              "platform_fee_base_units": { "type": "integer" }
            } } }
          } },
          "recent_settlement_proofs": { "type": "array", "items": { "type": "object", "properties": {
            "transaction_id": { "type": "string" }, "asset": { "type": "string" },
            "gross_base_units": { "type": "integer" }, "transaction_url": { "type": "string" }
          } } }
        }
      },
      "PaymentRequired": {
        "type": "object",
        "properties": {
          "x402Version": { "type": "integer", "const": 2 },
          "error": { "type": "string" },
          "resource": { "type": "object", "properties": {
            "url": { "type": "string" }, "description": { "type": "string" }, "mimeType": { "type": "string" } } },
          "accepts": { "type": "array", "items": { "type": "object", "properties": {
            "scheme": { "type": "string", "const": "exact" },
            "network": { "type": "string", "enum": ["hedera:testnet", "hedera:mainnet", "hedera:sandbox"] },
            "amount": { "type": "string", "description": "base units as string (tinybars / token smallest unit)" },
            "asset": { "type": "string", "description": "0.0.0 = HBAR, else HTS token id (USDC 0.0.429274)" },
            "payTo": { "type": "string" },
            "maxTimeoutSeconds": { "type": "integer" },
            "extra": { "type": "object", "properties": { "feePayer": { "type": "string" } } }
          } } },
          "help": { "type": "object", "description": "For a buyer that cannot pay yet: what a wallet needs to hold, and where to get one. Buyers fund the payment asset only — the facilitator sponsors gas.", "properties": {
            "message": { "type": "string" }, "wallet_url": { "type": "string" }, "docs_url": { "type": "string" } } }
        }
      },
      "BatchRequest": {
        "type": "object",
        "required": ["items"],
        "additionalProperties": false,
        "properties": {
          "items": {
            "type": "array", "minItems": 1, "maxItems": 200,
            "description": "At most 200 lines totalling at most 250 licenses. These bound server work, not buying: how many units an offer sells is the designer's max_units or nothing at all.",
            "items": {
              "type": "object", "required": ["model_id"], "additionalProperties": false,
              "properties": {
                "model_id": { "type": "integer", "minimum": 1 },
                "license": { "type": "string", "enum": ["personal", "commercial_unit"], "default": "personal" },
                "quantity": { "type": "integer", "minimum": 1, "default": 1, "description": "licenses to buy on this line; each is its own certificate and serial" }
              }
            }
          },
          "webhook": {
            "type": "object",
            "description": "Optional buyer callback for certificate.anchored events after real HCS anchoring. Include identical values in the probe and signed retry. Sandbox emits no callback.",
            "required": ["url", "secret"],
            "additionalProperties": false,
            "properties": {
              "url": { "type": "string", "format": "uri", "description": "Public HTTPS URL on port 443" },
              "secret": { "type": "string", "minLength": 32, "maxLength": 256, "writeOnly": true }
            }
          }
        }
      },
      "BatchDelivery": {
        "type": "object",
        "properties": {
          "batch_id": { "type": "integer" },
          "sandbox": { "type": "boolean" },
          "transaction_id": { "type": "string" },
          "transaction_url": { "type": ["string", "null"] },
          "licenses": {
            "type": "array",
            "description": "One summary per license, not the full single-license payload repeated. `files` stays inline because the file is the purchase; fetch the proof bundle from bundle_url, and the print-feedback and model-update capabilities from receipt.files_url.",
            "items": {
              "type": "object",
              "properties": {
                "model_id": { "type": "integer" }, "kind": { "type": "string" },
                "cert_id": { "type": "string" }, "serial": { "type": "integer" },
                "max_units": { "type": ["integer", "null"] }, "remaining_units": { "type": ["integer", "null"] },
                "verify_url": { "type": "string" },
                "bundle_url": { "type": "string", "description": "fetch the portable proof bundle here" },
                "certificate_pdf_url": { "type": "string" },
                "share_card_url": { "type": "string" },
                "receipt": { "$ref": "#/components/schemas/ReceiptCapability" },
                "files": { "type": "array", "description": "one entry per deliverable part of this license's model", "items": { "type": "object", "properties": {
                  "kind": { "type": "string" }, "url": { "type": "string" },
                  "expires_at": { "type": ["string", "null"] } } } }
              }
            }
          }
        }
      },
      "Delivery": {
        "type": "object",
        "properties": {
          "files": { "type": "array", "description": "One entry per printable part that is actually downloadable. Never empty on a 200: a settled purchase with no deliverable file returns 503 no_deliverable_file with the cert_id and receipt, and stays claimable by replaying the same signed payment.", "items": { "type": "object", "properties": {
            "kind": { "type": "string" }, "url": { "type": "string" },
            "expires_at": { "type": ["string", "null"] }, "sandbox": { "type": "boolean" } } } },
          "license": { "type": "object", "properties": {
            "cert_id": { "type": "string" }, "serial": { "type": "integer" }, "kind": { "type": "string" },
            "max_units": { "type": ["integer", "null"] }, "remaining_units": { "type": ["integer", "null"] } } },
          "certificate": { "type": ["object", "null"] },
          "proof_bundle": { "$ref": "#/components/schemas/ProofBundle" },
          "bundle_url": { "type": "string", "description": "re-fetch the bundle here once the commitment is anchored" },
          "verify_url": { "type": "string" },
          "share_card_url": { "type": "string" },
          "receipt": { "$ref": "#/components/schemas/ReceiptCapability" },
          "print_feedback": { "$ref": "#/components/schemas/PrintFeedbackCapability" },
          "model_updates": { "$ref": "#/components/schemas/ModelUpdatesCapability" },
          "transaction_id": { "type": "string" },
          "transaction_url": { "type": ["string", "null"] },
          "sandbox": { "type": "boolean", "description": "true only for the mock-facilitator rehearsal" },
          "warning": { "type": "string" },
          "sandbox_url": { "type": "string" }
        }
      },
      "ReceiptCapability": {
        "type": "object",
        "description": "The durable no-account delivery path that outlives the download grant in `files`. The token does not expire and needs no account. GET {files_url}?token= returns this license's files with fresh download URLs plus its print-feedback and model-update capabilities; GET {download_url}?token= redirects to the first part (&f=N selects one); GET {url}?token= is the human receipt page. Never returned by sandbox.",
        "required": ["url", "files_url", "download_url", "token"],
        "properties": {
          "url": { "type": "string", "format": "uri", "description": "human receipt page" },
          "files_url": { "type": "string", "format": "uri", "description": "the same receipt as JSON" },
          "download_url": { "type": "string", "format": "uri", "description": "302 to a part; ?f=N selects one" },
          "token": { "type": "string", "writeOnly": true },
          "expires_at": { "type": "null", "description": "always null — this capability is durable" },
          "note": { "type": "string" }
        },
        "additionalProperties": false
      },
      "ProofBundle": {
        "type": "object",
        "description": "Portable proof of one license. Hedera holds only the commitment; this bundle is the reveal. Recompute SHA-256(domain || blinding_nonce || RFC 8785 JCS(certificate)) and compare it with the message at hedera.topic_id/hedera.sequence_number on the public mirror. Normative contract: /pwc-1.schema.json.",
        "properties": {
          "proof_version": { "type": "integer", "const": 1 },
          "algorithm": { "type": "string", "const": "sha256-jcs-v1" },
          "certificate": { "type": ["object", "null"], "properties": {
            "v": { "type": "integer" }, "cert_id": { "type": "string" }, "designer": { "type": "integer", "description": "stable studio id, never a payout wallet" },
            "model_id": { "type": "integer" }, "issued_at": { "type": "string" }, "buyer_hint": { "type": "string" },
            "model_hash": { "type": "string" }, "payment_tx": { "type": "string" }, "terms_hash": { "type": "string" },
            "unit_serial": { "type": "integer" }, "license_type": { "type": "string" } } },
          "blinding_nonce": { "type": ["string", "null"], "description": "32 random bytes, hex-encoded; part of the commitment preimage" },
          "commitment": { "type": ["string", "null"], "description": "the value anchored on HCS" },
          "terms": { "type": "object", "description": "the exact licensed text, so the grant needs no lookup here", "properties": {
            "version": { "type": "string" }, "kind": { "type": "string" },
            "hash": { "type": "string" }, "text": { "type": "string" } } },
          "hedera": { "type": "object", "description": "where the commitment is anchored, or {\"status\":\"minting\"} until it is", "properties": {
            "network": { "type": "string" }, "topic_id": { "type": "string" }, "sequence_number": { "type": "integer" },
            "mirror_url": { "type": "string" },
            "status": { "type": "string" }, "sandbox": { "type": "boolean" } } }
        }
      },
      "CertificateResponse": {
        "allOf": [
          { "$ref": "#/components/schemas/ProofBundle" },
          { "type": "object", "properties": {
            "status": { "type": "string", "enum": ["anchored", "minting", "sandbox"] } } }
        ]
      }
    }
  }
}
