{
  "openapi": "3.1.0",
  "info": {
    "title": "RAVN Swap API",
    "version": "1.0.0",
    "description": "Cross-chain swap quoting and execution across 12 live venues and 16 chains, including native (non-wrapped) Bitcoin as either source or destination. Free, self-serve, 0% protocol fee. Two ways to call it: the REST API below (works with no signup, an optional x-api-key raises your rate limit) or the pay-per-call x402 variant (see the /api/v1/x402/* paths) for agents that skip API-key signup entirely.",
    "x-guidance": "Standard flow: POST /api/v1/quote to get a quoteToken and expected output for a swap between any two supported chains/tokens (including native Bitcoin as source or destination). Then POST /api/v1/execute with that quoteToken to receive one of three shapes tagged by executionType: TRANSACTION (sign & broadcast yourself), SIGNATURE (sign and POST to /api/v1/submit-signature, RAVN submits), or DEPOSIT (send the origin asset to the returned address). Poll GET /api/v1/status to track settlement. RAVN never custodies funds — execute only returns what the caller's own wallet needs to sign and broadcast.",
    "contact": { "email": "hello@ravn.exchange" }
  },
  "servers": [{ "url": "https://app.ravn.exchange" }],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Optional. Omit it entirely to call anonymously (rate-limited by caller IP). A self-serve key from POST /api/v1/keys raises the limit; it does not gate access — nothing here requires signup."
      }
    },
    "schemas": {
      "ChainId": {
        "type": "number",
        "enum": [1, 10, 56, 130, 137, 324, 480, 999, 143, 8453, 42161, 59144, 43114, 4663, -1, -2],
        "description": "Numeric chain id, e.g. 1=Ethereum, 8453=Base, -1=Bitcoin, -2=Solana"
      },
      "Meta": {
        "type": "object",
        "properties": {
          "requestId": { "type": "string" },
          "version": { "type": "string" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": ["INVALID_REQUEST", "UNSUPPORTED_TOKEN", "UNSUPPORTED_CHAIN", "NO_LIQUIDITY", "QUOTE_EXPIRED", "QUOTE_INVALID", "QUOTE_NOT_EXECUTABLE", "RATE_LIMITED", "UNAUTHORIZED", "NOT_CONFIGURED", "NOT_FOUND", "INTERNAL"]
              },
              "message": { "type": "string" },
              "details": {}
            },
            "required": ["code", "message"]
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      }
    }
  },
  "paths": {
    "/api/v1/quote": {
      "post": {
        "operationId": "quote",
        "summary": "Get a cross-chain swap quote",
        "tags": ["Swap"],
        "security": [{ "ApiKeyAuth": [] }, {}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "inputChainId": { "$ref": "#/components/schemas/ChainId" },
                  "outputChainId": { "$ref": "#/components/schemas/ChainId" },
                  "inputToken": { "type": "string", "minLength": 1, "description": "Token contract address, or the native-token sentinel 0xEeee...EEeE" },
                  "outputToken": { "type": "string", "minLength": 1 },
                  "inputAmount": { "type": "string", "description": "Positive integer string, in the input token's smallest unit" },
                  "userAddress": { "type": "string", "minLength": 1 },
                  "destinationAddress": { "type": "string", "description": "Where output should land. For a Solana-touching Across quote or a cross-ecosystem Eco quote, this must be supplied here, at quote time — omitting it returns a priced-but-unexecutable preview (see QuoteDTO.executable), since neither venue supports fixing the destination after quoting." },
                  "refundAddress": { "type": "string", "description": "Where to refund the input asset if the swap fails. For a Bitcoin-source Relay quote specifically, this must be supplied here, at quote time — Relay has no execute-time re-bind, so omitting it returns a priced-but-unexecutable preview and supplying it later on execute has no effect. Other BTC-source venues (Garden, THORChain, Chainflip, Rift) support binding this at execute time instead." },
                  "slippageBps": { "type": "integer", "minimum": 1, "maximum": 5000 },
                  "rankingMode": { "type": "string", "enum": ["best_output", "fastest"], "description": "Defaults to best_output." }
                },
                "required": ["inputChainId", "outputChainId", "inputToken", "outputToken", "inputAmount", "userAddress"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote produced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "quoteToken": { "type": "string", "description": "Opaque token — pass to POST /api/v1/execute" },
                        "venue": { "type": "object" },
                        "routeType": { "type": "string" },
                        "input": { "type": "object" },
                        "output": { "type": "object" },
                        "fee": { "type": "object" },
                        "executable": { "type": "boolean", "description": "False = preview-only price, priced against a placeholder destination/refund address. POST /v1/execute will fail with QUOTE_NOT_EXECUTABLE — request a fresh quote with destinationAddress/refundAddress supplied instead." },
                        "slippage": { "type": ["object", "null"], "description": "{ bps, isFirm, guaranteedMin } — null when the venue reports no slippage bound." },
                        "gas": { "type": ["object", "null"], "description": "{ native, nativeSymbol, usd, estimated } — the native gas the user must hold; null on gasless venues." },
                        "approval": { "type": "object", "description": "Present when the input token needs an ERC-20 approval before executing (TRANSACTION/SIGNATURE routes only)." },
                        "estimatedTimeSeconds": { "type": "number" },
                        "estimatedTimeIsGuess": { "type": "boolean", "description": "True when the venue didn't report a time for this quote." },
                        "expiresAt": { "type": "number" }
                      }
                    },
                    "meta": { "$ref": "#/components/schemas/Meta" }
                  }
                }
              }
            }
          },
          "400": { "description": "Invalid request / unsupported token or chain", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "404": { "description": "No liquidity for this pair/amount", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/execute": {
      "post": {
        "operationId": "execute",
        "summary": "Build the execution payload for a quote",
        "tags": ["Swap"],
        "security": [{ "ApiKeyAuth": [] }, {}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quoteToken": { "type": "string", "minLength": 1, "description": "Token returned from POST /api/v1/quote" },
                  "destinationAddress": { "type": "string", "description": "Late-bound output recipient, for venues that support binding it at execution time (e.g. NEAR Intents, Garden, Chainflip, Rift). Does NOT apply to Relay, Across, Eco, or THORChain — those fix the recipient at quote time with no execute-time re-bind, so this is silently ignored for them; supply destinationAddress on the quote call instead." },
                  "refundAddress": { "type": "string", "description": "Late-bound refund recipient, for venues that support binding it at execution time. Does NOT apply to a Bitcoin-source Relay quote — Relay bakes the refund address in at quote time, so this is silently ignored for those; supply refundAddress on the quote call instead." }
                },
                "required": ["quoteToken"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Execution produced — branch on executionType",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "executionType": { "type": "string", "enum": ["DEPOSIT", "TRANSACTION", "SIGNATURE"] },
                        "approval": { "type": "object", "description": "ERC-20 approval step to complete before signing/broadcasting, if the input token isn't already approved." },
                        "transaction": { "type": "object", "description": "TRANSACTION only: raw tx to sign & broadcast yourself." },
                        "typedData": { "type": "object", "description": "SIGNATURE only: EIP-712 payload to sign." },
                        "approvalData": { "type": "object", "description": "SIGNATURE only, 0x Gasless: a second EIP-712 payload some venues need signed for the approval itself." },
                        "submit": { "type": "object", "description": "SIGNATURE only: { url, payload? } — POST the signed payload(s) to POST /api/v1/submit-signature." },
                        "deposit": { "type": "object", "description": "DEPOSIT only: address + amount to send the origin asset to." },
                        "statusRef": { "type": "string", "description": "DEPOSIT only (the deposit address) — for SIGNATURE, poll status with the statusRef POST /api/v1/submit-signature returns instead." }
                      },
                      "required": ["executionType"]
                    },
                    "meta": { "$ref": "#/components/schemas/Meta" }
                  }
                }
              }
            }
          },
          "400": { "description": "Invalid request or malformed quoteToken", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "410": { "description": "Quote expired", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/submit-signature": {
      "post": {
        "operationId": "submitSignature",
        "summary": "Submit a signed order for SIGNATURE-type venues (0x Gasless, CoW, Bebop, Relay permit)",
        "tags": ["Swap"],
        "security": [{ "ApiKeyAuth": [] }, {}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quoteToken": { "type": "string", "minLength": 1 },
                  "signature": { "type": "string", "pattern": "^0x[0-9a-fA-F]+$" },
                  "approvalSignature": { "type": "string", "pattern": "^0x[0-9a-fA-F]+$", "description": "0x Gasless only: signature over the approval typed data from /api/v1/execute." }
                },
                "required": ["quoteToken", "signature"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Order submitted — poll GET /api/v1/status with the returned statusRef",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "statusRef": { "type": "string" },
                        "venue": { "type": "string" }
                      }
                    },
                    "meta": { "$ref": "#/components/schemas/Meta" }
                  }
                }
              }
            }
          },
          "400": { "description": "Invalid request, or venue does not use signature submission", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/status": {
      "get": {
        "operationId": "status",
        "summary": "Get normalized swap status",
        "tags": ["Swap"],
        "security": [{ "ApiKeyAuth": [] }, {}],
        "parameters": [
          { "name": "quoteToken", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "ref", "in": "query", "required": true, "schema": { "type": "string" }, "description": "The deposit address (DEPOSIT venues) or the statusRef from /api/v1/submit-signature (SIGNATURE venues)." }
        ],
        "responses": {
          "200": {
            "description": "Status resolved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": { "type": "string" },
                        "venue": { "type": "string" },
                        "venueStatus": { "type": "string" },
                        "tracking": { "type": "string", "enum": ["unavailable"], "description": "Present instead of venueStatus when this venue (Rift, Jupiter) has no live tracker yet." }
                      },
                      "required": ["status", "venue"]
                    },
                    "meta": { "$ref": "#/components/schemas/Meta" }
                  }
                }
              }
            }
          },
          "400": { "description": "Invalid request or malformed quoteToken", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/tokens/resolve": {
      "get": {
        "operationId": "resolveToken",
        "summary": "Resolve an arbitrary token address to metadata",
        "tags": ["Tokens"],
        "security": [{ "ApiKeyAuth": [] }, {}],
        "parameters": [
          { "name": "chainId", "in": "query", "required": true, "schema": { "$ref": "#/components/schemas/ChainId" } },
          { "name": "address", "in": "query", "required": true, "schema": { "type": "string" } }
        ],
        "responses": {
          "200": {
            "description": "Token resolved",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "token": {
                          "type": "object",
                          "properties": {
                            "chainId": { "$ref": "#/components/schemas/ChainId" },
                            "address": { "type": "string" },
                            "symbol": { "type": "string" },
                            "name": { "type": "string" },
                            "decimals": { "type": "integer" }
                          }
                        }
                      }
                    },
                    "meta": { "$ref": "#/components/schemas/Meta" }
                  }
                }
              }
            }
          },
          "400": { "description": "Not a valid token on this chain", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/tokens/btc-coverage": {
      "get": {
        "operationId": "btcTokenCoverage",
        "summary": "Which listed tokens on a chain can be reached FROM native Bitcoin, and how many venues serve each",
        "tags": ["Tokens"],
        "security": [{ "ApiKeyAuth": [] }, {}],
        "parameters": [
          { "name": "chainId", "in": "query", "required": true, "schema": { "$ref": "#/components/schemas/ChainId" } }
        ],
        "responses": {
          "200": {
            "description": "Coverage computed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "chainId": { "$ref": "#/components/schemas/ChainId" },
                        "filtered": { "type": "boolean", "description": "false means upstream venue lists could not be reached — treat as unknown, not zero coverage." },
                        "coverage": { "type": "object", "description": "Map of token address -> number of BTC-source-capable venues." }
                      }
                    },
                    "meta": { "$ref": "#/components/schemas/Meta" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/health": {
      "get": {
        "operationId": "health",
        "summary": "Venue liveness",
        "tags": ["Health"],
        "responses": {
          "200": {
            "description": "Always 200 — check the status field, not the HTTP code",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "status": { "type": "string", "enum": ["ok", "degraded"] },
                        "venues": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": { "type": "string" },
                              "name": { "type": "string" },
                              "healthy": { "type": "boolean" }
                            }
                          }
                        }
                      }
                    },
                    "meta": { "$ref": "#/components/schemas/Meta" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/keys": {
      "post": {
        "operationId": "createApiKey",
        "summary": "Self-serve API key signup — instant, no review",
        "tags": ["API Keys"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": { "type": "string", "format": "email" },
                  "projectName": { "type": "string", "minLength": 1 },
                  "website": { "type": "string", "format": "uri" },
                  "telegram": { "type": "string", "minLength": 1 },
                  "twitter": { "type": "string", "minLength": 1 },
                  "discord": { "type": "string" },
                  "blurb": { "type": "string" },
                  "category": { "type": "string" },
                  "chains": { "type": "array", "items": { "type": "string" } },
                  "apisPlanned": { "type": "array", "items": { "type": "string" } }
                },
                "required": ["email", "projectName", "website", "telegram", "twitter"]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key issued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "apiKey": { "type": "string", "description": "Pass as the x-api-key header on every other v1 request." },
                        "partnerId": { "type": "string" }
                      }
                    },
                    "meta": { "$ref": "#/components/schemas/Meta" }
                  }
                }
              }
            }
          },
          "400": { "description": "Invalid request", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "description": "Rate limited", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
        }
      }
    },
    "/api/v1/x402/quote": {
      "post": {
        "operationId": "x402Quote",
        "summary": "Get a cross-chain swap quote (pay-per-call)",
        "tags": ["Swap"],
        "x-payment-info": {
          "price": { "mode": "fixed", "currency": "USD", "amount": "0.001000" },
          "protocols": [{ "x402": {} }]
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "inputChainId": { "$ref": "#/components/schemas/ChainId" },
                  "outputChainId": { "$ref": "#/components/schemas/ChainId" },
                  "inputToken": { "type": "string", "minLength": 1, "description": "Token contract address, or the native-token sentinel 0xEeee...EEeE" },
                  "outputToken": { "type": "string", "minLength": 1 },
                  "inputAmount": { "type": "string", "description": "Positive integer string, in the input token's smallest unit" },
                  "userAddress": { "type": "string", "minLength": 1 },
                  "destinationAddress": { "type": "string", "description": "Where output should land. For a Solana-touching Across quote or a cross-ecosystem Eco quote, this must be supplied here, at quote time — omitting it returns a priced-but-unexecutable preview (see QuoteDTO.executable), since neither venue supports fixing the destination after quoting." },
                  "refundAddress": { "type": "string", "description": "Where to refund the input asset if the swap fails. For a Bitcoin-source Relay quote specifically, this must be supplied here, at quote time — Relay has no execute-time re-bind, so omitting it returns a priced-but-unexecutable preview and supplying it later on execute has no effect. Other BTC-source venues (Garden, THORChain, Chainflip, Rift) support binding this at execute time instead." },
                  "slippageBps": { "type": "integer", "minimum": 1, "maximum": 5000 },
                  "rankingMode": { "type": "string", "enum": ["best_output", "fastest"] }
                },
                "required": ["inputChainId", "outputChainId", "inputToken", "outputToken", "inputAmount", "userAddress"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Quote produced",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "quoteToken": { "type": "string" },
                        "venue": { "type": "object" },
                        "routeType": { "type": "string" },
                        "input": { "type": "object" },
                        "output": { "type": "object" },
                        "fee": { "type": "object" },
                        "executable": { "type": "boolean", "description": "False = preview-only price, priced against a placeholder destination/refund address. POST /v1/execute will fail with QUOTE_NOT_EXECUTABLE — request a fresh quote with destinationAddress/refundAddress supplied instead." },
                        "slippage": { "type": ["object", "null"] },
                        "gas": { "type": ["object", "null"] },
                        "estimatedTimeSeconds": { "type": "number" },
                        "estimatedTimeIsGuess": { "type": "boolean" },
                        "expiresAt": { "type": "number" }
                      }
                    }
                  }
                }
              }
            }
          },
          "402": { "description": "Payment Required" }
        }
      }
    },
    "/api/v1/x402/execute": {
      "post": {
        "operationId": "x402Execute",
        "summary": "Execute a previously quoted swap (pay-per-call)",
        "tags": ["Swap"],
        "x-payment-info": {
          "price": { "mode": "fixed", "currency": "USD", "amount": "0.010000" },
          "protocols": [{ "x402": {} }]
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quoteToken": { "type": "string", "minLength": 1, "description": "Token returned from POST /api/v1/x402/quote (or the free /api/v1/quote)" },
                  "destinationAddress": { "type": "string", "description": "Late-bound output recipient, for venues that support binding it at execution time (e.g. NEAR Intents, Garden, Chainflip, Rift). Does NOT apply to Relay, Across, Eco, or THORChain — those fix the recipient at quote time with no execute-time re-bind, so this is silently ignored for them; supply destinationAddress on the quote call instead." },
                  "refundAddress": { "type": "string", "description": "Late-bound refund recipient, for venues that support binding it at execution time. Does NOT apply to a Bitcoin-source Relay quote — Relay bakes the refund address in at quote time, so this is silently ignored for those; supply refundAddress on the quote call instead." }
                },
                "required": ["quoteToken"]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Execution produced — a DEPOSIT (Bitcoin-source swaps) or a signable TRANSACTION/SIGNATURE (EVM/Solana-source swaps)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "executionType": { "type": "string", "enum": ["DEPOSIT", "TRANSACTION", "SIGNATURE"] },
                        "approval": { "type": "object" },
                        "transaction": { "type": "object", "description": "TRANSACTION only" },
                        "typedData": { "type": "object", "description": "SIGNATURE only" },
                        "approvalData": { "type": "object", "description": "SIGNATURE only, 0x Gasless" },
                        "submit": { "type": "object", "description": "SIGNATURE only: { url, payload? } — POST to /api/v1/submit-signature" },
                        "deposit": { "type": "object", "description": "DEPOSIT only" },
                        "statusRef": { "type": "string", "description": "DEPOSIT only" }
                      }
                    }
                  }
                }
              }
            }
          },
          "402": { "description": "Payment Required" }
        }
      }
    }
  }
}
