{
  "openapi": "3.1.0",
  "info": {
    "title": "Textile RFQ API (v2)",
    "version": "2.0.0-beta",
    "description": "Pure request-for-quote venue: the taker asks, the venue solicits\nonline makers, makers reply with firm signed quotes, the venue picks a\nwinner, and the taker settles on-chain. Beta: callers must be\nallowlisted partners (or Textile admin sessions). The resting-book /v1\nAPI is unchanged and remains the default surface.\n\nMakers connect over `WSS /v2/maker/stream` with a maker credential and\nan EIP-712 session challenge; see the venue spec for the frame shapes."
  },
  "servers": [
    {
      "url": "https://api.textilecredit.com/v2"
    },
    {
      "url": "http://localhost:10000/v2",
      "description": "Local dev"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "System"
    },
    {
      "name": "RFQ",
      "description": "Solicit firm quotes and settle them"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Liveness probe",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "This document",
        "security": [],
        "responses": {
          "200": {
            "description": "OpenAPI document"
          }
        }
      }
    },
    "/rfq/request": {
      "post": {
        "tags": [
          "RFQ"
        ],
        "summary": "Request a firm quote",
        "description": "Blocks up to the corridor reply budget (~750ms pilot) while makers are solicited. Returns a firm, taker-bound signed quote with ready-to-broadcast calldata, or a no-quote result with a stable reason. `preferredLiquidityWallets` / `restrictedLiquidityWallets` carry the same contract as /v1: max 10 each, deduped, mutually exclusive (both → 400); a restricted miss returns reason `no_restricted_liquidity`, never an open-market substitute.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateRfqRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Firm quote or no-quote result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RfqOutcome"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/rfq/{id}/submit": {
      "post": {
        "tags": [
          "RFQ"
        ],
        "summary": "Report the settlement transaction for a firm quote",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "txHash"
                ],
                "properties": {
                  "txHash": {
                    "type": "string",
                    "pattern": "^0x[0-9a-fA-F]{64}$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated RFQ status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RfqStatusEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/rfq/{id}": {
      "get": {
        "tags": [
          "RFQ"
        ],
        "summary": "RFQ status",
        "description": "Status, routing summary, and terminal fields. The signed order is returned exactly once, by /rfq/request — never replayed here.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "RFQ status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RfqStatusEnvelope"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Allowlisted partner API key (tx_test_/tx_live_...), or a Textile admin wallet-session JWT."
      }
    },
    "schemas": {
      "RfqStatusEnvelope": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/RfqStatus"
          }
        }
      },
      "RfqStatus": {
        "type": "object",
        "description": "Never carries the signed order: encodedOrder/signature are returned exactly once, by POST /rfq/request.",
        "properties": {
          "rfqId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "soliciting",
              "quoted",
              "no_quote",
              "submitted",
              "filled",
              "failed",
              "expired"
            ]
          },
          "chainId": {
            "type": "integer"
          },
          "sellToken": {
            "type": "string"
          },
          "buyToken": {
            "type": "string"
          },
          "sellAmount": {
            "type": "string",
            "nullable": true,
            "description": "Once a winner exists, the settled sell-token debit (order output + fee) rather than the requested cap."
          },
          "buyAmount": {
            "type": "string",
            "nullable": true,
            "description": "Once a winner exists, the amount actually delivered."
          },
          "feeAmount": {
            "type": "string",
            "nullable": true
          },
          "taker": {
            "type": "string"
          },
          "quoteTtlMs": {
            "type": "integer"
          },
          "replyBy": {
            "type": "string",
            "format": "date-time"
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time",
            "description": "The winning reply's own accept cutoff, not the corridor cap."
          },
          "routing": {
            "type": "object",
            "properties": {
              "preferenceApplied": {
                "type": "boolean"
              },
              "restrictionApplied": {
                "type": "boolean"
              },
              "fallbackUsed": {
                "type": "boolean"
              },
              "preferredQuotesReceived": {
                "type": "integer"
              },
              "openMarketQuotesReceived": {
                "type": "integer"
              }
            }
          },
          "noQuoteReason": {
            "type": "string",
            "nullable": true
          },
          "txHash": {
            "type": "string",
            "nullable": true
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "filledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "failReason": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateRfqRequest": {
        "type": "object",
        "required": [
          "chainId",
          "sellToken",
          "buyToken",
          "taker"
        ],
        "oneOf": [
          {
            "required": [
              "sellAmount"
            ],
            "not": {
              "required": [
                "buyAmount"
              ]
            }
          },
          {
            "required": [
              "buyAmount"
            ],
            "not": {
              "required": [
                "sellAmount"
              ]
            }
          }
        ],
        "properties": {
          "chainId": {
            "type": "integer"
          },
          "sellToken": {
            "type": "string"
          },
          "buyToken": {
            "type": "string"
          },
          "sellAmount": {
            "type": "string",
            "description": "Exact-input gross spend cap (atomic units). Exactly one of sellAmount / buyAmount."
          },
          "buyAmount": {
            "type": "string",
            "description": "Exact-output amount (atomic units). Exactly one of sellAmount / buyAmount."
          },
          "taker": {
            "type": "string",
            "description": "The bound settler. Fills are restricted to this wallet for the whole order life."
          },
          "preferredLiquidityWallets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 10
          },
          "restrictedLiquidityWallets": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 10
          }
        }
      },
      "RfqOutcome": {
        "type": "object",
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "rfqId": {
                "type": "string"
              },
              "status": {
                "type": "string",
                "enum": [
                  "quoted",
                  "no_quote"
                ]
              },
              "reason": {
                "type": "string",
                "description": "no_quote only: no_restricted_liquidity | no_makers_online | no_valid_quote"
              },
              "quote": {
                "type": "object",
                "properties": {
                  "sellAmount": {
                    "type": "string"
                  },
                  "buyAmount": {
                    "type": "string"
                  },
                  "feeAmount": {
                    "type": "string"
                  },
                  "takerPays": {
                    "type": "string"
                  },
                  "rateRay": {
                    "type": "string"
                  },
                  "expiresAt": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "orderDeadline": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "reactor": {
                    "type": "string"
                  },
                  "taker": {
                    "type": "string"
                  },
                  "encodedOrder": {
                    "type": "string"
                  },
                  "signature": {
                    "type": "string"
                  }
                }
              },
              "transactions": {
                "type": "object",
                "properties": {
                  "approval": {
                    "type": "object"
                  },
                  "swap": {
                    "type": "object"
                  }
                }
              },
              "routing": {
                "type": "object",
                "properties": {
                  "preferenceApplied": {
                    "type": "boolean"
                  },
                  "restrictionApplied": {
                    "type": "boolean"
                  },
                  "fallbackUsed": {
                    "type": "boolean"
                  },
                  "targetMakerWallets": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "preferredQuotesReceived": {
                    "type": "integer"
                  },
                  "openMarketQuotesReceived": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error envelope",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
