{
  "openapi": "3.1.0",
  "info": {
    "title": "Swaps Agent API",
    "version": "1.1.0",
    "description": "OpenAPI manifest for the Swaps agentic surface. Covers two endpoint families: (1) the legacy hosted-tool routes (`/geo`, `/providers`, `/quote`, `/checkout`, `/feedback`) that proxy the Supabase Domain Hub `/functions/v1/swaps` and require an `action` query parameter; (2) the public read-only LLM routes (`/llm/quote`, `/llm/routing-explainer`) at `https://agent.swaps.app/api/llm/*` that return LLM-friendly responses with structured `LLMExplainPayload`."
  },
  "x-llms-stable-url": "https://agent.swaps.app/openapi.json",
  "x-llms-fallback-url": "https://www.swaps.app/agent/openapi.json",
  "x-llms-taxonomy-url": "https://agent.swaps.app/taxonomy.json",
  "x-llms-schema-version": "1.1.0",
  "servers": [
    {
      "url": "https://agent.swaps.app",
      "description": "Canonical Swaps agentic surface (Vercel rewrites to Supabase Edge Functions)."
    },
    {
      "url": "https://www.swaps.app",
      "description": "Fallback alias (path-form): /agent/* and /api/llm/* under the main domain."
    },
    {
      "url": "https://{projectRef}.functions.supabase.co/functions/v1/swaps",
      "description": "Direct Supabase Edge Function Domain Hub (legacy hosted-tool routes only).",
      "variables": {
        "projectRef": {
          "default": "your-project-ref",
          "description": "Supabase project reference identifier (e.g. abcdefghijklmno)."
        }
      }
    }
  ],
  "tags": [{ "name": "agent", "description": "Endpoints used by the Swaps AI Agent" }],
  "paths": {
    "/geo": {
      "get": {
        "tags": ["agent"],
        "summary": "Resolve geo hints for the current user context",
        "operationId": "getGeo",
        "parameters": [
          { "$ref": "#/components/parameters/ActionGeo" },
          {
            "name": "ip",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "format": "ipv4", "example": "203.0.113.42" },
            "description": "Optional user IP address (used for geo lookup)."
          },
          {
            "name": "bin",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "pattern": "^[0-9]{6}$", "example": "457173" },
            "description": "Optional first 6 digits of payment card BIN for payment-method hints."
          }
        ],
        "responses": {
          "200": {
            "description": "Geo information resolved",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/GeoResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/providers": {
      "get": {
        "tags": ["agent"],
        "summary": "List available providers for a given context",
        "operationId": "listProviders",
        "parameters": [
          { "$ref": "#/components/parameters/ActionProviders" },
          {
            "name": "country",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "minLength": 2, "maxLength": 2, "example": "ID" },
            "description": "ISO 3166-1 alpha-2 country code"
          },
          {
            "name": "paymentMethod",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "example": "visa" },
            "description": "Payment rail requested by the user"
          },
          {
            "name": "asset",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "example": "USDT" },
            "description": "Target asset ticker"
          },
          {
            "name": "side",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "enum": ["buy", "sell", "swap"] },
            "description": "Operation side"
          },
          {
            "name": "minReliability",
            "in": "query",
            "required": false,
            "schema": { "type": "number", "minimum": 0, "maximum": 1, "example": 0.8 },
            "description": "Minimum provider reliability score"
          }
        ],
        "responses": {
          "200": {
            "description": "Providers that match the filters",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ProvidersResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/quote": {
      "post": {
        "tags": ["agent"],
        "summary": "Request normalized offers from active providers",
        "operationId": "createQuote",
        "parameters": [{ "$ref": "#/components/parameters/ActionQuote" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/QuoteRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote created successfully",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/QuoteResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "503": {
            "description": "No providers available",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "examples": {
                  "noProviders": {
                    "summary": "No offers returned",
                    "value": { "code": "NO_PROVIDERS", "message": "No providers available" }
                  }
                }
              }
            }
          },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/checkout": {
      "post": {
        "tags": ["agent"],
        "summary": "Initialize checkout and return redirect information",
        "operationId": "initCheckout",
        "parameters": [{ "$ref": "#/components/parameters/ActionCheckout" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/CheckoutRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Checkout initialized",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CheckoutResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/feedback": {
      "post": {
        "tags": ["agent"],
        "summary": "Capture user feedback after a session",
        "operationId": "submitFeedback",
        "parameters": [{ "$ref": "#/components/parameters/ActionFeedback" }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/FeedbackRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Feedback accepted",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/FeedbackResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/llm/quote": {
      "get": {
        "tags": ["llm"],
        "summary": "Public read-only quote for LLM agents",
        "description": "Returns the winning provider quote plus alternatives with a structured `LLMExplainPayload` describing why the winner won. Anonymous-callable, rate-limited 30/min/IP + 1000/day/anon-key. Recommended canonical URL: `https://agent.swaps.app/llm/quote`. Implementation lands in Road A PR-5.",
        "operationId": "getLlmQuote",
        "parameters": [
          { "name": "from", "in": "query", "required": true, "schema": { "type": "string", "example": "USD" }, "description": "ISO fiat code OR crypto symbol the user is paying with." },
          { "name": "to", "in": "query", "required": true, "schema": { "type": "string", "example": "BTC" }, "description": "Crypto symbol the user wants to receive." },
          { "name": "amount", "in": "query", "required": true, "schema": { "type": "string", "example": "100" }, "description": "Decimal string of `from`-side amount (buy) or `to`-side amount when `side=sell`." },
          { "name": "country", "in": "query", "required": true, "schema": { "type": "string", "example": "US", "minLength": 2, "maxLength": 2 }, "description": "ISO-3166-1 alpha-2 country code of the user." },
          { "name": "side", "in": "query", "required": false, "schema": { "type": "string", "enum": ["buy", "sell"], "default": "buy" } },
          { "name": "method", "in": "query", "required": false, "schema": { "type": "string", "example": "card" }, "description": "Optional payment-method slug (card / sepa / ach / pix / apple-pay …)." }
        ],
        "responses": {
          "200": {
            "description": "Winning quote + alternatives with LLM-friendly explain payload.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LLMQuoteResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "description": "Rate limit exceeded — see `Retry-After` header." },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/api/llm/routing-explainer": {
      "get": {
        "tags": ["llm"],
        "summary": "Post-hoc explain for a previously-issued LLM quote",
        "description": "Returns the cached `LLMExplainPayload` for a quote (TTL 600s). Use for re-confirming why a provider won after an agent has already made a decision. Canonical URL: `https://agent.swaps.app/llm/routing-explainer`. Implementation lands in Road A PR-7.",
        "operationId": "getRoutingExplainer",
        "parameters": [
          { "name": "quoteId", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Quote ID returned by a previous `/api/llm/quote` call." }
        ],
        "responses": {
          "200": {
            "description": "Explain payload for the winner + all alternatives.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/LLMExplainerResponse" }
              }
            }
          },
          "404": { "description": "Quote not found or explain cache expired (EXPLAIN_EXPIRED). Call `/api/llm/quote` again." },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "SupabaseAnonKey": {
        "type": "apiKey",
        "in": "header",
        "name": "apikey",
        "description": "Supabase anon key. Provide the same token in `Authorization: Bearer <token>` when required."
      }
    },
    "parameters": {
      "ActionGeo": {
        "name": "action",
        "in": "query",
        "required": true,
        "schema": { "type": "string", "enum": ["swaps.geo_get"] },
        "description": "Domain Hub action identifier. Always set to `swaps.geo_get`."
      },
      "ActionProviders": {
        "name": "action",
        "in": "query",
        "required": true,
        "schema": { "type": "string", "enum": ["swaps.providers_list"] },
        "description": "Domain Hub action identifier. Always set to `swaps.providers_list`."
      },
      "ActionQuote": {
        "name": "action",
        "in": "query",
        "required": true,
        "schema": { "type": "string", "enum": ["swaps.quote_get"] },
        "description": "Domain Hub action identifier. Always set to `swaps.quote_get`."
      },
      "ActionCheckout": {
        "name": "action",
        "in": "query",
        "required": true,
        "schema": { "type": "string", "enum": ["swaps.redirect_init"] },
        "description": "Domain Hub action identifier. Always set to `swaps.redirect_init`."
      },
      "ActionFeedback": {
        "name": "action",
        "in": "query",
        "required": true,
        "schema": { "type": "string", "enum": ["swaps.feedback_submit"] },
        "description": "Domain Hub action identifier. Always set to `swaps.feedback_submit`."
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Validation error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" },
            "examples": {
              "missing": {
                "summary": "Missing required field",
                "value": { "code": "BAD_REQUEST", "message": "Missing required fields" }
              }
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected server error",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" },
            "examples": {
              "error": {
                "summary": "Generic error",
                "value": { "code": "SERVER_ERROR", "message": "Internal server error" }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "GeoResponse": {
        "type": "object",
        "required": ["country"],
        "properties": {
          "country": { "type": "string", "description": "ISO 3166-1 alpha-2 country code" },
          "region": { "type": "string", "nullable": true },
          "currency": { "type": "string", "nullable": true }
        }
      },
      "ProvidersResponse": {
        "type": "object",
        "properties": {
          "providers": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Provider" }
          }
        },
        "required": ["providers"]
      },
      "Provider": {
        "type": "object",
        "required": ["id", "name", "capabilities", "status"],
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "capabilities": {
            "type": "array",
            "items": { "type": "string", "enum": ["onramp", "offramp", "c2c"] }
          },
          "status": {
            "type": "string",
            "enum": ["active", "degraded", "offline"],
            "default": "active"
          },
          "methods": {
            "type": "array",
            "items": { "type": "string" },
            "nullable": true,
            "description": "Supported payment methods"
          },
          "countries": {
            "type": "array",
            "items": { "type": "string" },
            "nullable": true,
            "description": "Countries covered by the provider"
          },
          "assets": {
            "type": "array",
            "items": { "type": "string" },
            "nullable": true,
            "description": "Supported assets"
          },
          "limits": {
            "type": "object",
            "nullable": true,
            "properties": {
              "min": { "type": "number" },
              "max": { "type": "number" }
            }
          }
        }
      },
      "QuoteRequest": {
        "type": "object",
        "required": ["from", "to", "amount"],
        "properties": {
          "from": { "type": "string", "description": "Input currency or asset" },
          "to": { "type": "string", "description": "Output currency or asset" },
          "amount": { "type": "number", "description": "Amount expressed in the input asset" },
          "country": { "type": "string", "nullable": true },
          "paymentMethod": { "type": "string", "nullable": true },
          "side": { "type": "string", "enum": ["buy", "sell", "swap"], "default": "buy" }
        }
      },
      "QuoteResponse": {
        "type": "object",
        "required": ["quoteId", "offers"],
        "properties": {
          "quoteId": { "type": "string" },
          "offers": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Offer" }
          },
          "createdAt": { "type": "string", "format": "date-time" },
          "expiresAt": { "type": "string", "format": "date-time" },
          "meta": {
            "type": "object",
            "properties": {
              "latency": { "type": "number" },
              "providersQueried": { "type": "integer" },
              "providersResponded": { "type": "integer" }
            },
            "nullable": true
          }
        }
      },
      "Offer": {
        "type": "object",
        "required": ["provider", "rate", "fees", "etaSeconds", "offerId"],
        "properties": {
          "provider": {
            "type": "object",
            "required": ["id", "displayName"],
            "properties": {
              "id": { "type": "string" },
              "displayName": { "type": "string" },
              "badges": {
                "type": "array",
                "items": { "type": "string" },
                "nullable": true
              }
            }
          },
          "rate": { "type": "string" },
          "fees": {
            "type": "object",
            "required": ["provider"],
            "properties": {
              "provider": { "type": "string" },
              "network": { "type": "string", "nullable": true },
              "fx": { "type": "string", "nullable": true }
            }
          },
          "finalOut": { "type": "string", "nullable": true },
          "etaSeconds": { "type": "integer" },
          "requiresKyc": { "type": "boolean", "default": true },
          "limits": {
            "type": "object",
            "nullable": true,
            "properties": {
              "min": { "type": "string" },
              "max": { "type": "string" }
            }
          },
          "deepLink": { "type": "string", "format": "uri", "nullable": true },
          "coverageScore": { "type": "number", "nullable": true },
          "reliabilityScore": { "type": "number", "nullable": true },
          "offerId": { "type": "string" },
          "providerPayload": { "type": "object", "nullable": true }
        }
      },
      "CheckoutRequest": {
        "type": "object",
        "required": ["providerId", "quoteId", "offerId", "returnUrl"],
        "properties": {
          "providerId": { "type": "string" },
          "quoteId": { "type": "string" },
          "offerId": { "type": "string" },
          "returnUrl": { "type": "string", "format": "uri" }
        }
      },
      "CheckoutResponse": {
        "type": "object",
        "required": ["rid", "redirectUrl", "expiresAt"],
        "properties": {
          "rid": { "type": "string" },
          "redirectUrl": {
            "type": "string",
            "format": "uri",
            "description": "Signed return URL for redirect"
          },
          "signedReturnUrl": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Deprecated alias kept for backward compatibility"
          },
          "signature": {
            "type": "string",
            "nullable": true,
            "description": "Signature computed from rid and offerId"
          },
          "deepLink": {
            "type": "string",
            "nullable": true,
            "description": "Application deep link"
          },
          "expiresAt": { "type": "string", "format": "date-time" }
        }
      },
      "FeedbackRequest": {
        "type": "object",
        "required": ["session_id", "rating"],
        "properties": {
          "session_id": { "type": "string" },
          "rating": { "type": "integer", "minimum": 1, "maximum": 5 },
          "comment": { "type": "string", "nullable": true },
          "issues": {
            "type": "array",
            "items": { "type": "string" },
            "nullable": true
          }
        }
      },
      "FeedbackResponse": {
        "type": "object",
        "required": ["status"],
        "properties": {
          "status": { "type": "string", "enum": ["ok"] }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["code", "message"],
        "properties": {
          "code": { "type": "string" },
          "message": { "type": "string" },
          "correlationId": { "type": "string", "nullable": true }
        }
      },
      "LLMExplainPayload": {
        "type": "object",
        "description": "Structured, LLM-readable explanation of why a provider was chosen or rejected. Designed for autonomous agents that need to defend a recommendation to their user.",
        "required": ["rankingCriteria", "providerTaxonomy", "sourceOfTruth", "generatedAt"],
        "properties": {
          "rankingCriteria": {
            "type": "array",
            "description": "Per-criterion contribution to the ranking decision.",
            "items": {
              "type": "object",
              "required": ["name", "weight", "value", "score"],
              "properties": {
                "name": { "type": "string", "enum": ["final_output", "eta", "kyc_friction", "reliability"] },
                "weight": { "type": "number" },
                "value": { "type": "string" },
                "score": { "type": "number" }
              }
            }
          },
          "constraintsActive": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["type", "description"],
              "properties": {
                "type": { "type": "string" },
                "description": { "type": "string" }
              }
            }
          },
          "alternatives": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["providerId", "reasonNotPicked"],
              "properties": {
                "providerId": { "type": "string" },
                "reasonNotPicked": { "type": "string" }
              }
            }
          },
          "providerTaxonomy": {
            "type": "object",
            "description": "Subset of the 6-axis taxonomy from /agent/taxonomy.json relevant to this offer.",
            "required": ["domain", "flowShape", "integrationStyle", "settlementRail", "agenticExposure"],
            "properties": {
              "domain": { "type": "array", "items": { "type": "string" } },
              "flowShape": { "type": "array", "items": { "type": "string" } },
              "integrationStyle": { "type": "string" },
              "settlementRail": { "type": "array", "items": { "type": "string" } },
              "agenticExposure": { "type": "array", "items": { "type": "string" } }
            }
          },
          "sourceOfTruth": { "type": "string", "enum": ["swaps_internal", "provider_api", "cached"] },
          "generatedAt": { "type": "string", "format": "date-time" },
          "diagnostic": { "type": "string", "description": "Free-form diagnostic from the SmartRouter (e.g. `OK in 245ms`, `SKIP: rate limit`)." },
          "circuitState": { "type": "string", "enum": ["open", "closed", "half_open"], "nullable": true }
        }
      },
      "LLMQuoteResponse": {
        "type": "object",
        "required": ["_meta", "request", "winner", "alternatives"],
        "properties": {
          "_meta": {
            "type": "object",
            "required": ["schemaVersion", "generatedAt", "ttlSeconds"],
            "properties": {
              "schemaVersion": { "type": "string", "example": "1.0.0" },
              "generatedAt": { "type": "string", "format": "date-time" },
              "ttlSeconds": { "type": "integer" }
            }
          },
          "request": { "type": "object", "additionalProperties": true },
          "winner": {
            "type": "object",
            "required": ["providerId", "rate", "estimatedOutput", "fees", "constraints", "explain"],
            "properties": {
              "providerId": { "type": "string" },
              "quoteId": { "type": "string", "description": "Use this to call /api/llm/routing-explainer for post-hoc explain." },
              "rate": { "type": "string" },
              "estimatedOutput": { "type": "string" },
              "fees": {
                "type": "object",
                "properties": {
                  "total": { "type": "string" },
                  "currency": { "type": "string" },
                  "breakdown": { "type": "array", "items": { "type": "object", "additionalProperties": true } }
                }
              },
              "etaSeconds": { "type": "integer", "nullable": true },
              "expiresAt": { "type": "string", "format": "date-time" },
              "constraints": {
                "type": "object",
                "required": ["requiresKyc"],
                "properties": {
                  "requiresKyc": { "type": "boolean" },
                  "kycTier": { "type": "string", "enum": ["none", "basic", "full", "provider_delegated"] },
                  "limits": {
                    "type": "object",
                    "properties": {
                      "min": { "type": "string" },
                      "max": { "type": "string" }
                    }
                  }
                }
              },
              "checkoutUrl": { "type": "string", "format": "uri" },
              "explain": { "$ref": "#/components/schemas/LLMExplainPayload" }
            }
          },
          "alternatives": {
            "type": "array",
            "items": {
              "type": "object",
              "required": ["providerId", "estimatedOutput", "reasonNotPicked"],
              "properties": {
                "providerId": { "type": "string" },
                "estimatedOutput": { "type": "string" },
                "reasonNotPicked": { "type": "string" },
                "explain": { "$ref": "#/components/schemas/LLMExplainPayload" }
              }
            }
          }
        }
      },
      "LLMExplainerResponse": {
        "type": "object",
        "required": ["_meta", "quoteId", "winner", "alternatives"],
        "properties": {
          "_meta": {
            "type": "object",
            "properties": {
              "schemaVersion": { "type": "string" },
              "cachedAt": { "type": "string", "format": "date-time" },
              "ttlSeconds": { "type": "integer" }
            }
          },
          "quoteId": { "type": "string" },
          "winner": { "$ref": "#/components/schemas/LLMExplainPayload" },
          "alternatives": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "providerId": { "type": "string" },
                "explain": { "$ref": "#/components/schemas/LLMExplainPayload" }
              }
            }
          }
        }
      }
    }
  },
  "security": [{ "SupabaseAnonKey": [] }]
}
