{
  "openapi": "3.1.0",
  "info": {
    "title": "AdReceipt Agent API",
    "version": "2.0.0",
    "description": "Create policy-bound sponsored placements and verify their settlement receipts. All paths are served through the application's same-origin /api proxy. Placement preparation never broadcasts a transaction."
  },
  "servers": [{ "url": "/api" }],
  "paths": {
    "/v2/status": {
      "get": {
        "operationId": "getAdReceiptStatus",
        "summary": "Inspect runtime and settlement readiness",
        "responses": { "200": { "description": "Current public runtime status" } }
      }
    },
    "/v2/campaigns/suggest": {
      "post": {
        "operationId": "suggestCampaign",
        "summary": "Convert an advertiser brief into a reviewable campaign suggestion",
        "description": "Uses GroqCloud. The returned suggestion is not active and always requires human approval.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["brief", "brandDisplayName", "productRef"],
                "properties": {
                  "brief": { "type": "string", "minLength": 1, "maxLength": 2000 },
                  "brandDisplayName": { "type": "string", "minLength": 1, "maxLength": 120 },
                  "productRef": { "type": "string", "minLength": 1, "maxLength": 500 }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Schema-validated suggestion requiring human approval" },
          "503": { "description": "Model unavailable; no targeting or creative inferred" }
        }
      }
    },
    "/v2/campaigns": {
      "get": {
        "operationId": "listCampaigns",
        "summary": "List persisted campaign revisions",
        "responses": { "200": { "description": "Campaign collection" } }
      },
      "post": {
        "operationId": "createCampaignDraft",
        "summary": "Create a CampaignManifestV2 draft",
        "description": "The draft must be reviewed and signed by advertiserWallet before it becomes active.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": { "schema": { "$ref": "#/components/schemas/CampaignInput" } }
          }
        },
        "responses": {
          "201": { "description": "Persisted draft and EIP-712 typed data" },
          "400": { "description": "Invalid campaign input" },
          "503": { "description": "Storage unavailable" }
        }
      }
    },
    "/v2/campaigns/{campaignId}/revisions/{revision}/approve": {
      "post": {
        "operationId": "approveCampaignRevision",
        "summary": "Activate a campaign revision with its advertiser signature",
        "parameters": [
          { "$ref": "#/components/parameters/CampaignId" },
          {
            "name": "revision",
            "in": "path",
            "required": true,
            "schema": { "type": "integer", "minimum": 1 }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["revisionHash", "signature"],
                "properties": {
                  "revisionHash": { "$ref": "#/components/schemas/Bytes32" },
                  "signature": { "type": "string", "pattern": "^0x[0-9a-fA-F]+$" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Active signed campaign revision" },
          "400": { "description": "Invalid revision, hash, state, or signature" }
        }
      }
    },
    "/v2/decisions": {
      "post": {
        "operationId": "decideSponsoredContext",
        "summary": "Apply age, safety, relevance, locale, and campaign eligibility rules",
        "description": "The public result contains a salted sanitized-context commitment, not the raw query.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["query", "ageEligibility", "coarseLocale"],
                "properties": {
                  "query": { "type": "string", "minLength": 1, "maxLength": 2000 },
                  "ageEligibility": {
                    "type": "string",
                    "enum": ["ADULT_DECLARED", "UNDER_18", "UNKNOWN"]
                  },
                  "coarseLocale": { "type": "string", "minLength": 2, "maxLength": 16 }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "ELIGIBLE, SUPPRESSED, or NO_MATCH decision" },
          "400": { "description": "Invalid decision input" }
        }
      }
    },
    "/v2/placements": {
      "post": {
        "operationId": "preparePlacement",
        "summary": "Create a CRE_SIMULATED PlacementTicketV2 for an eligible decision",
        "description": "Creates deterministic settlement material but does not sign for the publisher and does not broadcast.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["decisionId", "amount"],
                "properties": {
                  "decisionId": { "type": "string", "format": "uuid" },
                  "amount": { "type": "string", "pattern": "^[1-9][0-9]*$" }
                }
              }
            }
          }
        },
        "responses": {
          "201": { "description": "Authorized placement ticket and unsigned quote" },
          "400": { "description": "Decision, campaign, amount, or policy validation failed" },
          "503": { "description": "Storage, ledger, binding, or CRE simulation unavailable" }
        }
      }
    },
    "/v2/placements/{placementId}/verify": {
      "post": {
        "operationId": "verifyPlacementReceipt",
        "summary": "Verify a signed placement against The Graph and Sepolia RPC",
        "parameters": [{ "$ref": "#/components/parameters/PlacementId" }],
        "responses": {
          "200": { "description": "Current verification state" },
          "503": { "description": "Required evidence provider unavailable" }
        }
      }
    },
    "/v2/placements/{placementId}/settle": {
      "post": {
        "operationId": "settlePlacementWithPrivy",
        "summary": "Settle a publisher-signed placement through the bounded Privy wallet",
        "description": "Operator-only. Rechecks the quote, replay state, balance, allowance, chain, and settlement contract before submitting the bounded approval and settlement calls.",
        "security": [{ "operatorBearer": [] }],
        "parameters": [{ "$ref": "#/components/parameters/PlacementId" }],
        "responses": {
          "200": { "description": "Confirmed settlement transaction and receipt ID" },
          "401": { "description": "Operator authorization required" },
          "400": { "description": "Placement is unsigned, expired, invalid, or already used" },
          "503": { "description": "Privy, RPC, payer funds, or settlement service unavailable" }
        }
      }
    },
    "/v2/receipts/{receiptId}/evidence": {
      "get": {
        "operationId": "getPlacementEvidenceBundle",
        "summary": "Reconstruct the human-readable V2 placement behind a receipt",
        "description": "Returns the public campaign, sanitized context decision, placement ticket, signatures, and authorization. Private context salts are never returned.",
        "parameters": [
          {
            "name": "receiptId",
            "in": "path",
            "required": true,
            "schema": { "type": "string", "pattern": "^0x[0-9a-fA-F]{64}$" }
          }
        ],
        "responses": {
          "200": { "description": "Public V2 evidence bundle" },
          "404": { "description": "No stored V2 placement uses this receipt" }
        }
      }
    },
    "/v2/placements/{placementId}/measurements": {
      "post": {
        "operationId": "recordVerifiedPlacementMeasurement",
        "summary": "Record one impression or click per browser session",
        "description": "Only PAID_VERIFIED placements are measurable. A click requires a prior impression for the same session.",
        "parameters": [{ "$ref": "#/components/parameters/PlacementId" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": ["eventId", "sessionId", "kind"],
                "properties": {
                  "eventId": { "type": "string", "format": "uuid" },
                  "sessionId": { "type": "string", "format": "uuid" },
                  "kind": { "type": "string", "enum": ["IMPRESSION", "CLICK"] }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "Updated campaign measurement totals" },
          "400": { "description": "Invalid, unverified, or out-of-order measurement" }
        }
      }
    },
    "/receipts": {
      "get": {
        "operationId": "listVerifiedReceipts",
        "summary": "List live receipts corroborated by The Graph and Sepolia RPC",
        "responses": {
          "200": { "description": "Verified receipt ledger and Graph health" },
          "503": { "description": "Ledger evidence unavailable" }
        }
      }
    },
    "/receipts/{receiptId}": {
      "get": {
        "operationId": "verifyReceipt",
        "summary": "Verify one receipt by its EIP-712 digest",
        "parameters": [{ "$ref": "#/components/parameters/ReceiptId" }],
        "responses": {
          "200": { "description": "PAID_VERIFIED, pending, not found, or invalid result" },
          "503": { "description": "Graph or RPC evidence unavailable" }
        }
      }
    },
    "/subjects/{subjectHash}": {
      "get": {
        "operationId": "verifyRecommendationSubject",
        "summary": "Find and verify receipts for an exact recommendation commitment",
        "parameters": [
          {
            "name": "subjectHash",
            "in": "path",
            "required": true,
            "schema": { "$ref": "#/components/schemas/Bytes32" }
          }
        ],
        "responses": {
          "200": { "description": "Verified receipts and deterministic disclosure decision" },
          "503": { "description": "Graph or RPC evidence unavailable" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "operatorBearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "Separate operator secret. Never expose it to an autonomous agent or public client."
      }
    },
    "parameters": {
      "CampaignId": {
        "name": "campaignId",
        "in": "path",
        "required": true,
        "schema": { "$ref": "#/components/schemas/Bytes32" }
      },
      "PlacementId": {
        "name": "placementId",
        "in": "path",
        "required": true,
        "schema": { "$ref": "#/components/schemas/Bytes32" }
      },
      "ReceiptId": {
        "name": "receiptId",
        "in": "path",
        "required": true,
        "schema": { "$ref": "#/components/schemas/Bytes32" }
      }
    },
    "schemas": {
      "Bytes32": { "type": "string", "pattern": "^0x[0-9a-fA-F]{64}$" },
      "Address": { "type": "string", "pattern": "^0x[0-9a-fA-F]{40}$" },
      "AtomicAmount": { "type": "string", "pattern": "^[1-9][0-9]*$" },
      "CampaignInput": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "advertiserWallet",
          "brandDisplayName",
          "productRef",
          "landingPage",
          "objective",
          "settlementAsset",
          "totalBudget",
          "maxPlacementAmount",
          "targetTopics",
          "targetIntents",
          "allowedLocales",
          "blockedContextClasses",
          "creativeHeadline",
          "creativeBody",
          "validFrom",
          "validUntil"
        ],
        "properties": {
          "advertiserWallet": { "$ref": "#/components/schemas/Address" },
          "brandDisplayName": { "type": "string", "minLength": 1, "maxLength": 120 },
          "productRef": { "type": "string", "minLength": 1, "maxLength": 500 },
          "landingPage": { "type": "string", "format": "uri" },
          "objective": { "const": "WEBSITE_VISIT" },
          "settlementAsset": { "$ref": "#/components/schemas/Address" },
          "totalBudget": { "$ref": "#/components/schemas/AtomicAmount" },
          "maxPlacementAmount": { "$ref": "#/components/schemas/AtomicAmount" },
          "targetTopics": { "type": "array", "minItems": 1, "items": { "type": "string" } },
          "targetIntents": { "type": "array", "minItems": 1, "items": { "type": "string" } },
          "allowedLocales": { "type": "array", "minItems": 1, "items": { "type": "string" } },
          "blockedContextClasses": {
            "type": "array",
            "minItems": 1,
            "items": { "type": "string" }
          },
          "creativeHeadline": { "type": "string", "minLength": 1, "maxLength": 120 },
          "creativeBody": { "type": "string", "minLength": 1, "maxLength": 300 },
          "validFrom": { "type": "string", "pattern": "^[0-9]+$" },
          "validUntil": { "type": "string", "pattern": "^[0-9]+$" }
        }
      }
    }
  }
}
