{
  "openapi": "3.1.0",
  "info": {
    "title": "Textile FX API",
    "version": "1.0.0",
    "description": "Non-custodial REST API for the Textile FX swap product. Quote, build unsigned transactions, and track settlement. The API never signs or holds funds — you sign returned payloads with your own wallet and broadcast them yourself."
  },
  "servers": [
    {
      "url": "https://api.textilecredit.com/v1",
      "description": "Production"
    },
    {
      "url": "http://localhost:8911/v1",
      "description": "Local dev"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "System",
      "description": "Health and readiness (no auth)"
    },
    {
      "name": "Pools",
      "description": "Available FX corridors"
    },
    {
      "name": "Quotes",
      "description": "Live executable prices"
    },
    {
      "name": "Swaps",
      "description": "Instant swaps against resting liquidity"
    },
    {
      "name": "Limit orders",
      "description": "Resting maker orders"
    },
    {
      "name": "Webhooks",
      "description": "Outbound trade-status notifications"
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Liveness probe",
        "operationId": "getHealth",
        "security": [],
        "responses": {
          "200": {
            "description": "Process is up"
          }
        }
      }
    },
    "/status": {
      "get": {
        "tags": [
          "System"
        ],
        "summary": "Readiness probe with dependency health",
        "operationId": "getStatus",
        "security": [],
        "responses": {
          "200": {
            "description": "operational or degraded"
          },
          "503": {
            "description": "down — a critical dependency is unavailable"
          }
        }
      }
    },
    "/pools": {
      "get": {
        "tags": [
          "Pools"
        ],
        "summary": "List FX corridors",
        "operationId": "listPools",
        "security": [
          {
            "apiKey": [
              "quotes:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            },
            "description": "Restrict to one chain. Omit for all env chains."
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/PoolList"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/quote": {
      "get": {
        "tags": [
          "Quotes"
        ],
        "summary": "Get a live quote",
        "operationId": "getQuote",
        "security": [
          {
            "apiKey": [
              "quotes:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sellToken",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Collateral asset address"
          },
          {
            "name": "buyToken",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Debt asset address"
          },
          {
            "name": "sellAmount",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Atomic units, base-10 string"
          },
          {
            "name": "minRate",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Min debt-per-collateral rate (RAY)"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Quote"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/order-book": {
      "get": {
        "tags": [
          "Quotes"
        ],
        "summary": "List live funded offers and available liquidity",
        "description": "Returns the executable resting offers for one swap direction, best rate first, plus aggregate sell and buy capacity. Amounts are atomic unit strings. Signed order payloads are intentionally omitted.",
        "operationId": "getOrderBook",
        "security": [
          {
            "apiKey": [
              "quotes:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sellToken",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Asset the taker sells"
          },
          {
            "name": "buyToken",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Asset the taker receives"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/OrderBook"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/swaps": {
      "post": {
        "tags": [
          "Swaps"
        ],
        "summary": "Quote and build unsigned swap transactions",
        "operationId": "createSwap",
        "security": [
          {
            "apiKey": [
              "trades:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSwapRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "No liquidity — nothing to build (retry later)"
          },
          "201": {
            "$ref": "#/components/responses/Swap"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "get": {
        "tags": [
          "Swaps"
        ],
        "summary": "List swaps",
        "operationId": "listSwaps",
        "security": [
          {
            "apiKey": [
              "trades:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "QUOTED",
                "SUBMITTED",
                "FILLED",
                "EXPIRED",
                "CANCELLED"
              ]
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of swaps"
          }
        }
      }
    },
    "/swaps/{id}": {
      "get": {
        "tags": [
          "Swaps"
        ],
        "summary": "Get a swap",
        "operationId": "getSwap",
        "security": [
          {
            "apiKey": [
              "trades:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/Swap"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/swaps/{id}/submit": {
      "post": {
        "tags": [
          "Swaps"
        ],
        "summary": "Record a broadcast tx hash",
        "operationId": "submitSwap",
        "security": [
          {
            "apiKey": [
              "trades:write"
            ]
          }
        ],
        "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",
                    "description": "0x-prefixed 32-byte hash"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Swap"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/limit-orders/quote": {
      "get": {
        "tags": [
          "Limit orders"
        ],
        "summary": "Guided maker pricing for a resting limit order",
        "description": "The buyAmount a limit order should name to actually fill, computed from the live book. `fastFill` crosses the best affordable operator bid (their taker bots fill within seconds); `queue` rests at the top of the order’s own side for a better rate at unknown latency. Depth figures are advisory and change every operator tick.",
        "operationId": "quoteLimitOrder",
        "security": [
          {
            "apiKey": [
              "quotes:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sellToken",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Asset the maker sells"
          },
          {
            "name": "buyToken",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Asset the maker wants"
          },
          {
            "name": "sellAmount",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Atomic units, base-10 string"
          },
          {
            "name": "marginBps",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000
            },
            "description": "Profit conceded to the filling operator on top of the reactor fee (default 10)"
          }
        ],
        "responses": {
          "200": {
            "description": "fastFill and queue pricing + advisory depth"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/limit-orders/prepare": {
      "post": {
        "tags": [
          "Limit orders"
        ],
        "summary": "Build EIP-712 typed data to sign",
        "operationId": "prepareLimitOrder",
        "security": [
          {
            "apiKey": [
              "trades:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PrepareLimitOrderRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Typed data + digest to sign (echoes assumed pricing when `pricing` was used)"
          }
        }
      }
    },
    "/limit-orders": {
      "post": {
        "tags": [
          "Limit orders"
        ],
        "summary": "Submit a signed limit order",
        "operationId": "submitLimitOrder",
        "security": [
          {
            "apiKey": [
              "trades:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitLimitOrderRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Order accepted into the book"
          },
          "400": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "get": {
        "tags": [
          "Limit orders"
        ],
        "summary": "List a wallet's limit orders",
        "operationId": "listLimitOrders",
        "security": [
          {
            "apiKey": [
              "trades:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "chainId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "wallet",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The wallet's orders"
          }
        }
      }
    },
    "/limit-orders/{id}": {
      "get": {
        "tags": [
          "Limit orders"
        ],
        "summary": "Get one limit order submitted through the API",
        "description": "Lifecycle of an order this API key’s partner submitted. `status` is ACTIVE | FILLED | CANCELLED | EXPIRED | SUPERSEDED; once the fill reconciles, `txHash`/`filledAt` point at the settlement. A filled limit order pays exactly the amounts it named. `statusVerified` is false when a derived status could not be checked against the on-chain Permit2 nonce (chain unreachable) — treat such an EXPIRED as unknown and poll again rather than reposting.",
        "operationId": "getLimitOrder",
        "security": [
          {
            "apiKey": [
              "trades:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The order"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Register a webhook endpoint",
        "operationId": "createWebhook",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Endpoint created (signingSecret shown once)"
          }
        }
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook endpoints",
        "operationId": "listWebhooks",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Endpoints (no secrets)"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Delete a webhook endpoint",
        "operationId": "deleteWebhook",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key as a bearer token: `Authorization: Bearer tx_live_...`. The `X-API-Key` header is also accepted."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        },
        "description": "Recommended for POSTs. Retries with the same key replay the first response instead of repeating the action."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "request_id": {
                "type": "string"
              },
              "details": {}
            }
          }
        }
      },
      "UnsignedTransaction": {
        "type": "object",
        "description": "An unsigned EVM transaction to sign and broadcast.",
        "properties": {
          "to": {
            "type": "string"
          },
          "data": {
            "type": "string"
          },
          "value": {
            "type": "string",
            "description": "wei, always \"0\" for FX swaps"
          },
          "chainId": {
            "type": "integer"
          }
        }
      },
      "CreateSwapRequest": {
        "type": "object",
        "required": [
          "chainId",
          "sellToken",
          "buyToken",
          "sellAmount",
          "taker"
        ],
        "properties": {
          "chainId": {
            "type": "integer"
          },
          "sellToken": {
            "type": "string",
            "description": "Collateral asset address"
          },
          "buyToken": {
            "type": "string",
            "description": "Debt asset address"
          },
          "sellAmount": {
            "type": "string",
            "description": "Atomic units"
          },
          "minRate": {
            "type": "string",
            "description": "Min rate (RAY), optional"
          },
          "taker": {
            "type": "string",
            "description": "Wallet that signs + receives proceeds"
          },
          "requireFullFill": {
            "type": "boolean",
            "description": "Fill-or-kill at the request level: when the book cannot compose the full sellAmount from whole orders, return fillable:false (reason \"partial_fill\") instead of building a partial swap."
          }
        }
      },
      "OrderBook": {
        "type": "object",
        "required": [
          "chainId",
          "sellToken",
          "buyToken",
          "hasLiquidity",
          "liveOffers",
          "executableOffers",
          "availableSellAmount",
          "availableBuyAmount",
          "bestRateRay",
          "asOf",
          "offers"
        ],
        "properties": {
          "chainId": {
            "type": "integer"
          },
          "sellToken": {
            "type": "string",
            "description": "Asset the taker sells"
          },
          "buyToken": {
            "type": "string",
            "description": "Asset the taker receives"
          },
          "hasLiquidity": {
            "type": "boolean"
          },
          "liveOffers": {
            "type": "integer",
            "description": "Total funded offers currently live in the book"
          },
          "executableOffers": {
            "type": "integer",
            "description": "Offers included in the maximum single executable swap (at most 100)"
          },
          "availableSellAmount": {
            "type": "string",
            "description": "Fee-inclusive sell-token amount needed to clear the maximum single executable swap, in atomic units"
          },
          "availableBuyAmount": {
            "type": "string",
            "description": "Total buy-token proceeds paid out if the maximum single executable swap clears, in atomic units"
          },
          "bestRateRay": {
            "type": [
              "string",
              "null"
            ],
            "description": "Best buy-token-per-sell-token rate, RAY-scaled"
          },
          "asOf": {
            "type": "string",
            "format": "date-time"
          },
          "offers": {
            "type": "array",
            "description": "Best-rate-first offers included in the maximum single executable swap",
            "items": {
              "type": "object",
              "required": [
                "id",
                "maker",
                "sellAmount",
                "feeAmount",
                "buyAmount",
                "rateRay",
                "expiresAt"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "maker": {
                  "type": "string"
                },
                "sellAmount": {
                  "type": "string",
                  "description": "Fee-inclusive sell-token amount needed to fill this offer, in atomic units"
                },
                "feeAmount": {
                  "type": "string",
                  "description": "Protocol fee included in sellAmount, in sell-token atomic units"
                },
                "buyAmount": {
                  "type": "string",
                  "description": "Buy-token proceeds, in atomic units"
                },
                "rateRay": {
                  "type": "string"
                },
                "expiresAt": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "PrepareLimitOrderRequest": {
        "type": "object",
        "required": [
          "chainId",
          "maker",
          "sellToken",
          "sellAmount",
          "buyToken",
          "deadline"
        ],
        "example": {
          "chainId": 8453,
          "maker": "0x0000000000000000000000000000000000000000",
          "sellToken": "0x0000000000000000000000000000000000000000",
          "sellAmount": "1000000",
          "buyToken": "0x0000000000000000000000000000000000000000",
          "buyAmount": "1000000",
          "deadline": 1900000000
        },
        "properties": {
          "chainId": {
            "type": "integer"
          },
          "maker": {
            "type": "string"
          },
          "sellToken": {
            "type": "string"
          },
          "sellAmount": {
            "type": "string"
          },
          "buyToken": {
            "type": "string"
          },
          "buyAmount": {
            "type": "string",
            "description": "Explicit price. Exactly one of buyAmount or pricing."
          },
          "pricing": {
            "type": "object",
            "description": "Server-computed price from the live book (same math as GET /limit-orders/quote). The response echoes the assumed rate — verify it before signing.",
            "required": [
              "mode"
            ],
            "properties": {
              "mode": {
                "type": "string",
                "enum": [
                  "fastFill",
                  "queue"
                ]
              },
              "marginBps": {
                "type": "integer",
                "minimum": 0,
                "maximum": 1000
              }
            }
          },
          "deadline": {
            "type": "integer",
            "description": "Unix seconds"
          },
          "nonce": {
            "type": "string",
            "description": "Permit2 nonce, generated if omitted"
          }
        }
      },
      "SubmitLimitOrderRequest": {
        "type": "object",
        "required": [
          "chainId",
          "reactor",
          "maker",
          "inputToken",
          "inputAmount",
          "outputToken",
          "outputAmount",
          "nonce",
          "deadline",
          "signature"
        ],
        "properties": {
          "chainId": {
            "type": "integer"
          },
          "reactor": {
            "type": "string"
          },
          "maker": {
            "type": "string"
          },
          "inputToken": {
            "type": "string"
          },
          "inputAmount": {
            "type": "string"
          },
          "outputToken": {
            "type": "string"
          },
          "outputAmount": {
            "type": "string"
          },
          "nonce": {
            "type": "string"
          },
          "deadline": {
            "type": "string",
            "description": "Unix seconds"
          },
          "signature": {
            "type": "string"
          }
        }
      },
      "CreateWebhookRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "https endpoint"
          },
          "eventTypes": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "swap.filled",
                "limit_order.filled",
                "limit_order.expired",
                "limit_order.cancelled"
              ]
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PoolList": {
        "description": "Available corridors"
      },
      "Quote": {
        "description": "A live quote"
      },
      "OrderBook": {
        "description": "Live funded offers and aggregate liquidity",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "data": {
                  "$ref": "#/components/schemas/OrderBook"
                }
              }
            }
          }
        }
      },
      "Swap": {
        "description": "A swap with unsigned transactions or status"
      }
    }
  }
}
