{
  "openapi": "3.0.3",
  "info": {
    "title": "Operon x402 Advertiser API",
    "description": "Self-serve advertiser onboarding for the Operon ad network. Three HTTP routes run the full advertiser lifecycle: create a funded campaign via x402, read campaign state, and cancel with refund of unspent balance. Live on Base mainnet.",
    "version": "1.0.0",
    "contact": {
      "name": "Operon",
      "email": "hi@operon.so",
      "url": "https://operon.so/x402"
    },
    "termsOfService": "https://operon.so/x402#pricing"
  },
  "servers": [
    {
      "url": "https://api.operon.so",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "campaign",
      "description": "Advertiser campaign lifecycle"
    }
  ],
  "paths": {
    "/x402/campaign": {
      "post": {
        "tags": ["campaign"],
        "summary": "Create and fund an advertiser campaign",
        "description": "x402-gated. The first call returns HTTP 402 with a PAYMENT-REQUIRED header demanding 100 USDC on Base. Sign the USDC payment, attach a PAYMENT-SIGNATURE header, retry. On verification, the server creates the campaign, registers the endpoint with Operon's trust monitoring, and returns 201. The campaign enters Operon's quality-weighted auction immediately. Trust score is null at creation and populates as behavioral signal builds.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignCreateRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Campaign created and funded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignCreateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (e.g. service_url unreachable, unknown category, missing required field)",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "402": {
            "description": "Payment required. Response includes PAYMENT-REQUIRED header with amount (100 USDC) and pay-to wallet on Base. Client signs the USDC transaction, attaches PAYMENT-SIGNATURE header, and retries.",
            "headers": {
              "PAYMENT-REQUIRED": {
                "description": "x402 payment challenge: amount, asset, and recipient wallet",
                "schema": { "type": "string" }
              }
            }
          }
        }
      }
    },
    "/x402/campaign/{id}": {
      "get": {
        "tags": ["campaign"],
        "summary": "Read campaign state",
        "description": "Returns current balance, stats, status, and trust score. No payment required. Bearer auth using the token issued at campaign creation.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Campaign ID",
            "schema": { "type": "string" }
          }
        ],
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "Current campaign state",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CampaignReadResponse" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Campaign not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      },
      "delete": {
        "tags": ["campaign"],
        "summary": "Cancel campaign and queue refund",
        "description": "Cancels the campaign and queues a refund of unspent balance to the original x402 payer wallet. Manual processing within 7 days. Spent balance is not refundable. Bearer auth using the token issued at campaign creation.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Campaign ID",
            "schema": { "type": "string" }
          }
        ],
        "security": [{ "bearerAuth": [] }],
        "responses": {
          "200": {
            "description": "Cancelled, refund queued",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CampaignCancelResponse" }
              }
            }
          },
          "401": {
            "description": "Missing or invalid bearer token",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          },
          "404": {
            "description": "Campaign not found",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bearer token issued at campaign creation. Used for GET and DELETE on the same campaign."
      }
    },
    "schemas": {
      "Category": {
        "type": "string",
        "description": "Targeting category. Matches the publisher SDK targeting taxonomy.",
        "enum": [
          "defi",
          "fintech",
          "travel",
          "insurance",
          "ecommerce",
          "saas",
          "health",
          "education",
          "general"
        ]
      },
      "Intent": {
        "type": "string",
        "description": "Optional intent qualifier",
        "enum": [
          "research",
          "comparison",
          "recommendation",
          "transaction"
        ]
      },
      "CampaignStatus": {
        "type": "string",
        "enum": ["active", "paused", "depleted", "cancelled"]
      },
      "CampaignCreateRequest": {
        "type": "object",
        "required": ["service_url", "service_name", "category"],
        "properties": {
          "service_url": {
            "type": "string",
            "format": "uri",
            "description": "Where the advertiser's service lives. Must be reachable - the server HEAD-pings before accepting the campaign. 200/30x = pass."
          },
          "service_name": {
            "type": "string",
            "maxLength": 64,
            "description": "Display name shown in placements"
          },
          "category": { "$ref": "#/components/schemas/Category" },
          "asset": {
            "type": "string",
            "description": "Optional. Free-form, narrows targeting (e.g. 'ETH', 'yield-farming')."
          },
          "intent": { "$ref": "#/components/schemas/Intent" },
          "bid_per_placement": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "description": "Optional. USDC. Defaults to mid-range CPM for the category if omitted."
          },
          "daily_cap": {
            "type": "number",
            "format": "float",
            "minimum": 0,
            "description": "Optional. USDC. Spreads balance across days; campaign auto-pauses at end-of-day until next-day reset."
          }
        }
      },
      "CampaignCreateResponse": {
        "type": "object",
        "required": [
          "campaign_id",
          "balance_usdc",
          "category",
          "bid_per_placement",
          "status",
          "created_at",
          "x402_payer_wallet"
        ],
        "properties": {
          "campaign_id": { "type": "string" },
          "balance_usdc": {
            "type": "number",
            "format": "float",
            "description": "Funded balance in USDC. Starts at 100 (the minimum deposit)."
          },
          "category": { "$ref": "#/components/schemas/Category" },
          "asset": { "type": "string", "nullable": true },
          "intent": {
            "allOf": [{ "$ref": "#/components/schemas/Intent" }],
            "nullable": true
          },
          "bid_per_placement": {
            "type": "number",
            "format": "float",
            "description": "Resolved bid (input value or category default)."
          },
          "daily_cap": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "trust_score": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "minimum": 0,
            "maximum": 100,
            "description": "0-100 trust score. Null at creation; populates as Operon's trust monitoring gathers behavioral signal on the endpoint."
          },
          "status": { "$ref": "#/components/schemas/CampaignStatus" },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "x402_payer_wallet": {
            "type": "string",
            "description": "Wallet address captured from the x402 payment. Refunds are routed back to this wallet."
          }
        }
      },
      "CampaignReadResponse": {
        "type": "object",
        "required": [
          "campaign_id",
          "balance_usdc",
          "balance_spent_usdc",
          "category",
          "bid_per_placement",
          "status",
          "stats",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "campaign_id": { "type": "string" },
          "balance_usdc": {
            "type": "number",
            "format": "float",
            "description": "Remaining USDC in funded balance."
          },
          "balance_spent_usdc": {
            "type": "number",
            "format": "float",
            "description": "Cumulative spent USDC."
          },
          "category": { "$ref": "#/components/schemas/Category" },
          "asset": { "type": "string", "nullable": true },
          "intent": {
            "allOf": [{ "$ref": "#/components/schemas/Intent" }],
            "nullable": true
          },
          "bid_per_placement": { "type": "number", "format": "float" },
          "daily_cap": {
            "type": "number",
            "format": "float",
            "nullable": true
          },
          "trust_score": {
            "type": "number",
            "format": "float",
            "nullable": true,
            "minimum": 0,
            "maximum": 100,
            "description": "0-100 trust score. Null until behavioral signal builds."
          },
          "status": { "$ref": "#/components/schemas/CampaignStatus" },
          "stats": {
            "type": "object",
            "required": [
              "impressions_served",
              "wins",
              "click_throughs",
              "average_clearing_price_usdc"
            ],
            "properties": {
              "impressions_served": { "type": "integer", "minimum": 0 },
              "wins": { "type": "integer", "minimum": 0 },
              "click_throughs": { "type": "integer", "minimum": 0 },
              "average_clearing_price_usdc": {
                "type": "number",
                "format": "float",
                "minimum": 0
              }
            }
          },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "CampaignCancelResponse": {
        "type": "object",
        "required": [
          "campaign_id",
          "status",
          "refund_amount_usdc",
          "refund_destination_wallet",
          "refund_status",
          "refund_eta"
        ],
        "properties": {
          "campaign_id": { "type": "string" },
          "status": {
            "type": "string",
            "enum": ["cancelled"]
          },
          "refund_amount_usdc": {
            "type": "number",
            "format": "float",
            "description": "Equal to balance_usdc at the time of cancellation. Spent balance is not refundable."
          },
          "refund_destination_wallet": {
            "type": "string",
            "description": "Equal to x402_payer_wallet captured at campaign creation."
          },
          "refund_status": {
            "type": "string",
            "enum": ["queued"]
          },
          "refund_eta": {
            "type": "string",
            "description": "Refund processing window. v1 is manual within 7 days.",
            "example": "within 7 days"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "message"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Short error code"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error description"
          }
        }
      }
    }
  }
}
