{
  "openapi": "3.1.0",
  "info": {
    "title": "TRDEFI Liquidity API",
    "version": "1.1.0",
    "summary": "Read the live non-custodial liquidity catalogue, quote it, and prepare unsigned positions for your own wallet to sign.",
    "description": "Public, read-first API over the open liquidity catalogue that TRDEFI normalises, plus a **prepare-only write path** for integrators holding a key.\n\n**Non-custodial, strictly.** The API never signs and never broadcasts. `POST /v1/positions` returns *unsigned* transactions that your own wallet signs from your own address. No endpoint accepts a private key or a signing grant.\n\n**Contract.** Every response carries `X-Request-Id`. Every endpoint advertises `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` and `RateLimit-Policy`, on success and on failure. Every error is the same envelope: `{ error: { code, message, requestId, retryable, documentation } }`.\n\n**Money.** Amounts are returned as USD numbers for read models. On-chain amounts are strings in base units with an explicit `decimals`.\n\n**Fees.** Every newly created position carries a 0.05% protocol fee embedded in the signed order data. It is reported in the `fee` object of every prepared position and cannot be removed by the integrator or by us.\n\n**Keys.** Reading needs no key. Preparing needs `Authorization: Bearer trd_live_<keyId>_<secret>`, held server-side only. Request one at https://yield.trdefi.com/docs/api#access.\n\n**Stability.** See the deprecation policy at https://yield.trdefi.com/docs/api — path-versioned resources, a published calendar, and a minimum 90-day notice window.",
    "contact": { "name": "TRDEFI", "url": "https://trdefi.com", "email": "info@trdefi.com" },
    "license": { "name": "All rights reserved", "url": "https://trdefi.com" }
  },
  "servers": [{ "url": "https://yield.trdefi.com", "description": "Production" }],
  "tags": [
    { "name": "catalogue", "description": "Read-only roll-ups of the live liquidity catalogue." },
    { "name": "quotes", "description": "Indicative pricing. Nothing here signs or moves funds." },
    { "name": "badges", "description": "shields.io endpoint payloads for repository READMEs." },
    { "name": "positions", "description": "The prepare-only write path. Returns UNSIGNED transactions for your own wallet to sign. Requires a key carrying the `prepare` scope. The API never signs and never broadcasts. Tracking a client's own signed transaction is not built yet and is therefore not documented here." },
    { "name": "demo", "description": "Same-origin helpers that back the public testnet demo. Not general-purpose APIs." }
  ],
  "security": [],
  "paths": {
    "/api/stats": {
      "get": {
        "tags": ["catalogue"],
        "operationId": "getStats",
        "summary": "Catalogue totals, per-chain and top-pair roll-ups",
        "description": "Served from pre-aggregated views. The roll-up is refreshed on a schedule, so `meta.freshnessSeconds` tells you how old the underlying figure is. Individual responses are cached for 60 seconds (`meta.cached`).",
        "responses": {
          "200": {
            "description": "Roll-up",
            "headers": {
              "X-Request-Id": { "$ref": "#/components/headers/RequestId" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" },
              "RateLimit-Policy": { "$ref": "#/components/headers/RateLimitPolicy" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/StatsResponse" },
                "example": {
                  "data": {
                    "source": "open liquidity strategies (normalized by TRDEFI)",
                    "totals": {
                      "strategies": 4685,
                      "makers": 693,
                      "pairs": 1385,
                      "chains": 17,
                      "volume_1d_usd": 73972611.94,
                      "volume_7d_usd": 137597554.61,
                      "volume_30d_usd": 205811838.49,
                      "last_activity": "2026-09-23T00:00:00.000Z",
                      "rollup_updated_at": "2026-09-24T00:00:00.000Z"
                    },
                    "chains": [{ "chain": "Ethereum", "count": 3426, "volume_30d_usd": 203020000, "last_activity": "2026-09-22T00:00:00.000Z" }],
                    "top_pairs_by_volume": [],
                    "top_pairs_by_positions": []
                  },
                  "meta": {
                    "requestId": "9f2c1d4e7a8b3c5d6e0f1a2b3c4d5e6f",
                    "generatedAt": "2026-09-24T02:11:05.376Z",
                    "apiVersion": "v1",
                    "freshnessSeconds": 10800,
                    "cached": false
                  }
                }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/api/chains": {
      "get": {
        "tags": ["catalogue"],
        "operationId": "listChains",
        "summary": "Supported chains and their catalogue counts",
        "description": "Treat this as a live catalog: do not hardcode chain identifiers or symbol→address mappings.",
        "responses": {
          "200": {
            "description": "Chains",
            "headers": {
              "X-Request-Id": { "$ref": "#/components/headers/RequestId" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/strategies": {
      "get": {
        "tags": ["catalogue"],
        "operationId": "listStrategies",
        "summary": "List open liquidity positions",
        "parameters": [
          { "name": "resource", "in": "query", "schema": { "type": "string", "enum": ["list", "chains", "stats", "detail", "search"] }, "description": "Sub-resource selector. Defaults to `list`." },
          { "name": "q", "in": "query", "schema": { "type": "string" }, "description": "Free-text search over pair and chain (used when `resource=search`)." },
          { "name": "pair", "in": "query", "schema": { "type": "string" }, "description": "Exact pair filter, e.g. `USDC/EURC`." },
          { "name": "chain", "in": "query", "schema": { "type": "string" }, "description": "Chain key filter, e.g. `base`." }
        ],
        "responses": {
          "200": {
            "description": "Positions",
            "headers": {
              "X-Request-Id": { "$ref": "#/components/headers/RequestId" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          },
          "405": { "$ref": "#/components/responses/MethodNotAllowed" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/Internal" }
        }
      }
    },
    "/api/quote": {
      "get": {
        "tags": ["quotes"],
        "operationId": "getQuote",
        "summary": "Indicative quote against a specific live strategy",
        "description": "Indicative only. Nothing is signed, nothing moves. A signable quote is produced on the write path, which is origin-restricted and requires an explicit authorisation. Quote simulation is currently enabled only for verified USDC/USDT strategies attached to the verified router; other strategies return `UNPROCESSABLE`.",
        "parameters": [
          { "name": "hash", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Strategy hash, 64 hex characters." },
          { "name": "chain", "in": "query", "schema": { "type": "string" }, "description": "Chain key. Defaults to `ethereum`." },
          { "name": "amount", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Amount in token base units, as a string." },
          { "name": "direction", "in": "query", "schema": { "type": "string", "enum": ["aToB", "bToA"], "default": "aToB" }, "description": "Swap direction." }
        ],
        "responses": {
          "200": {
            "description": "Indicative quote",
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/strategy-detail": {
      "get": {
        "tags": ["catalogue"],
        "operationId": "getStrategyDetail",
        "summary": "One position in detail",
        "parameters": [
          { "name": "hash", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Strategy hash." }
        ],
        "responses": {
          "200": { "description": "Position", "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/badge": {
      "get": {
        "tags": ["badges"],
        "operationId": "getBadge",
        "summary": "shields.io endpoint payload",
        "description": "Returns the shields.io `endpoint` schema so a repository README can show a live metric. Consumed as `https://img.shields.io/endpoint?url=<encoded /api/badge?metric=...>`.",
        "parameters": [
          { "name": "metric", "in": "query", "schema": { "type": "string", "enum": ["volume30", "volume7", "volume1", "strategies", "makers", "pairs", "chains"], "default": "volume30" } }
        ],
        "responses": {
          "200": {
            "description": "Badge payload",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Badge" },
                "example": { "schemaVersion": 1, "label": "30-day volume", "message": "$205.72M", "color": "orange", "cacheSeconds": 300, "isError": false }
              }
            }
          }
        }
      }
    },
    "/api/pilot": {
      "get": {
        "tags": ["demo"],
        "operationId": "getPilot",
        "summary": "Live state of the public testnet demo",
        "description": "The maker/taker/fee wallets, the shipped strategy and the swaps observed in a recent block window, on a **test network**. This exists so the public demo page can show a real end-to-end flow. It is **same-origin only** (Origin/Referer must be a TRDEFI page) and carries a tight rate limit. The fee-receiver address is deliberately not returned — only its balance. The same-origin check is a deterrent against casual reuse, not a security boundary.",
        "responses": {
          "200": {
            "description": "Demo state",
            "headers": {
              "X-Request-Id": { "$ref": "#/components/headers/RequestId" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/api/balances": {
      "get": {
        "tags": ["demo"],
        "operationId": "getBalances",
        "summary": "Demo wallet balances on a test network",
        "description": "Reads USDC / USDT / ETH balances for up to 10 addresses on a **test network**, so the demo page can show live wallet state. Not a general-purpose balance API. **Same-origin only** and rate limited.",
        "parameters": [
          { "name": "addresses", "in": "query", "required": true, "schema": { "type": "string" }, "description": "Comma-separated EVM addresses, 1–10." }
        ],
        "responses": {
          "200": {
            "description": "Balances",
            "content": { "application/json": { "schema": { "type": "object", "additionalProperties": true } } }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/positions": {
      "post": {
        "tags": ["positions"],
        "operationId": "preparePosition",
        "summary": "Prepare a position — returns UNSIGNED transactions for your wallet to sign",
        "description": "The only endpoint that needs a write key, and even it cannot execute anything.\n\n**What it does.** Authenticates the key, validates the intent, embeds the 0.05% protocol fee into the order, reads your live allowance (and optionally balance) from the chain, and returns the complete, ordered set of *unsigned* transactions required to create the position. Nothing is broadcast, nothing is signed, no allowance is created.\n\n**What you do.** Sign each transaction in `transactions[]` with the wallet you named, in the order given, then broadcast. We are not involved in either step.\n\n**Approvals are conditional.** The `approve` steps are omitted when your live allowance already covers the amount, so the same logical position may come back as one step or three.\n\n**No expiry, deliberately.** A prepared payload holds no reservation and no server-side lock; it is a pure function of its inputs plus a salt. The salt is derived from `(your key, Idempotency-Key)`, so a retry returns the *same* `strategyHash` and cannot create a second position. Omit the header and every call is a new position.\n\n**Fees.** The `fee` object reports what is already embedded in the unsigned order data. There is no parameter that removes it.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "format": "uuid" }, "description": "Recommended. The salt is derived from this, so repeating the request with the same key returns the same `strategyHash` instead of creating a second position." }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/PositionRequest" },
              "example": {
                "chain": "arc",
                "wallet": "0xYourOwnWalletThatWillSign",
                "deposits": [
                  { "symbol": "USDC", "amount": "1000000" },
                  { "symbol": "EURC", "amount": "900000" }
                ],
                "makerFeeBps": 30000,
                "checkBalances": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Prepared position. Inert: nothing has been signed or broadcast.",
            "headers": {
              "X-Request-Id": { "$ref": "#/components/headers/RequestId" },
              "RateLimit-Limit": { "$ref": "#/components/headers/RateLimitLimit" },
              "RateLimit-Remaining": { "$ref": "#/components/headers/RateLimitRemaining" },
              "RateLimit-Reset": { "$ref": "#/components/headers/RateLimitReset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PreparedPosition" },
                "example": {
                  "data": {
                    "status": "prepared",
                    "chain": "arc",
                    "chainId": 5042,
                    "wallet": "0xYourOwnWalletThatWillSign",
                    "strategyHash": "0x9f71222e57269fd7525375aca3c66c13c7f27137eb052e4bf1e3dc8f9a284b08",
                    "transactions": [
                      { "step": 1, "purpose": "approve", "to": "0x3600000000000000000000000000000000000000", "value": "0", "data": "0x095ea7b3…", "description": "Approve the position registry to move USDC. This is a bounded allowance you can revoke at any time — it is not a transfer, and the funds stay in your wallet." },
                      { "step": 2, "purpose": "ship", "to": "0x1111113ccf1426a8e30e2bff5e005d929bf6a90a", "value": "0", "data": "0xf50b870f…", "description": "Create the USDC/EURC position on Arc. Funds stay in your wallet; the position is backed by the allowance you just signed." }
                    ],
                    "fee": { "protocolFeeBps": 5000, "protocolFeePercent": "0.05", "makerFeeBps": 30000, "makerFeePercent": "0.30", "totalFeePercent": "0.35", "embeddedIn": "signed_order_data", "removable": false },
                    "position": { "tokenA": { "symbol": "USDC", "address": "0x3600000000000000000000000000000000000000", "amount": "1000000", "decimals": 6 }, "tokenB": { "symbol": "EURC", "address": "0xbef5f6d51cb62b58e6a8f77868681825c6fe21c1", "amount": "900000", "decimals": 6 }, "impliedRate": 1.1111111111111112, "programBytes": 223 },
                    "preflight": { "status": "ready", "checks": [{ "name": "wallet_valid", "ok": true }, { "name": "tokens_sorted", "ok": true }, { "name": "both_amounts_positive", "ok": true }, { "name": "fees_wiring", "ok": true }, { "name": "balance_a", "ok": true, "detail": "USDC" }, { "name": "balance_b", "ok": true, "detail": "EURC" }] },
                    "idempotent": true
                  },
                  "meta": { "requestId": "9f2c1d4e7a8b3c5d6e0f1a2b3c4d5e6f", "apiVersion": "v1", "preparedAt": "2026-09-24T23:59:52.414Z", "note": "Nothing has been signed or broadcast." }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/Unprocessable" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "503": { "$ref": "#/components/responses/UpstreamUnavailable" }
        }
      }
    },
    "/v1/positions/{positionId}": {
      "get": {
        "tags": ["positions"],
        "operationId": "getPosition",
        "summary": "Track a position you prepared",
        "description": "`positionId` is the `strategyHash` returned by `POST /v1/positions` — there is no second identifier to keep track of.\n\n**We only report what we have observed.** `prepared` = nothing seen on-chain yet. `shipped` = the strategy is in the live catalogue, which means your wallet signed, broadcast and it was included. `filled` = it has settled volume.\n\n**Two states we deliberately do not report.** There is no `signed`: a signature is unobservable before broadcast, and by then the position is `shipped`. There is no `failed`: we watch the catalogue, not every reverted transaction — your own receipt is the authority on that.\n\nTimestamps are named `firstSeen*` because they record when **we** observed a state, not when it happened.\n\nA key only ever sees positions it prepared itself. Another key's position and a non-existent one both return `404`, deliberately indistinguishable.",
        "security": [{ "bearerAuth": [] }],
        "parameters": [
          { "name": "positionId", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^0x[0-9a-fA-F]{64}$" }, "description": "The `strategyHash` returned by `POST /v1/positions`." }
        ],
        "responses": {
          "200": {
            "description": "The observed lifecycle of your position",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PositionStatus" },
                "example": {
                  "data": {
                    "positionId": "0x9f71222e57269fd7525375aca3c66c13c7f27137eb052e4bf1e3dc8f9a284b08",
                    "strategyHash": "0x9f71222e57269fd7525375aca3c66c13c7f27137eb052e4bf1e3dc8f9a284b08",
                    "status": "filled",
                    "chain": "arc",
                    "chainId": 5042,
                    "wallet": "0xYourOwnWalletThatWillSign",
                    "tokenA": { "symbol": "USDC", "address": "0x3600000000000000000000000000000000000000", "amount": "1000000" },
                    "tokenB": { "symbol": "EURC", "address": "0xbef5f6d51cb62b58e6a8f77868681825c6fe21c1", "amount": "900000" },
                    "makerFeeBps": 30000,
                    "protocolFeeBps": 5000,
                    "observedOnChain": true,
                    "volumeUsd": 22013772.64,
                    "preparedAt": "2026-09-25T00:30:19.018Z",
                    "firstSeenOnChainAt": "2026-09-25T00:32:59.462Z",
                    "firstSeenFilledAt": "2026-09-25T00:30:20.691Z",
                    "signer": "client — we never signed or broadcast anything"
                  },
                  "meta": {
                    "requestId": "2a1a71cb728d503222928b85d6875644",
                    "apiVersion": "v1",
                    "checkedAt": "2026-09-25T00:35:01.400Z",
                    "timestampsMeaning": "When TRDEFI first OBSERVED each state, not when it actually happened."
                  }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "trd_live_<keyId>_<secret>",
        "description": "An API key issued after review. Held **server-side only** — a key in client-side code is treated as compromised. It can prepare unsigned transactions and read the positions it prepared. It can never sign, broadcast or move value. Request one at https://yield.trdefi.com/docs/api#access"
      }
    },
    "headers": {
      "RequestId": { "description": "Unique id for this request. Quote it in support tickets.", "schema": { "type": "string", "example": "9f2c1d4e7a8b3c5d6e0f1a2b3c4d5e6f" } },
      "RateLimitLimit": { "description": "Requests allowed in the current window for this bucket.", "schema": { "type": "integer", "example": 120 } },
      "RateLimitRemaining": { "description": "Requests remaining in the current window.", "schema": { "type": "integer", "example": 119 } },
      "RateLimitReset": { "description": "Seconds until the window resets.", "schema": { "type": "integer", "example": 42 } },
      "RateLimitPolicy": { "description": "Bucket name, window and limit, e.g. `stats;w=60;limit=120`.", "schema": { "type": "string" } }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "required": ["requestId", "generatedAt", "apiVersion"],
        "properties": {
          "requestId": { "type": "string" },
          "generatedAt": { "type": "string", "format": "date-time" },
          "apiVersion": { "type": "string", "example": "v1" },
          "freshnessSeconds": { "type": ["integer", "null"], "description": "How old the underlying roll-up is, in seconds. Null when not derived." },
          "cached": { "type": "boolean", "description": "True when this response was served from our short-lived cache." }
        }
      },
      "StatsResponse": {
        "type": "object",
        "required": ["data", "meta"],
        "properties": {
          "data": {
            "type": "object",
            "properties": {
              "source": { "type": "string" },
              "totals": {
                "type": "object",
                "properties": {
                  "strategies": { "type": "integer" },
                  "makers": { "type": "integer" },
                  "pairs": { "type": "integer" },
                  "chains": { "type": "integer" },
                  "volume_1d_usd": { "type": "number" },
                  "volume_7d_usd": { "type": "number" },
                  "volume_30d_usd": { "type": "number" },
                  "last_activity": { "type": ["string", "null"], "format": "date-time" },
                  "rollup_updated_at": { "type": ["string", "null"], "format": "date-time" }
                }
              },
              "chains": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
              "top_pairs_by_volume": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
              "top_pairs_by_positions": { "type": "array", "items": { "type": "object", "additionalProperties": true } }
            }
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Badge": {
        "type": "object",
        "required": ["schemaVersion", "label", "message"],
        "properties": {
          "schemaVersion": { "type": "integer", "const": 1 },
          "label": { "type": "string" },
          "message": { "type": "string" },
          "color": { "type": "string" },
          "cacheSeconds": { "type": "integer" },
          "isError": { "type": "boolean" }
        }
      },
      "PositionRequest": {
        "type": "object",
        "required": ["chain", "wallet", "deposits"],
        "properties": {
          "chain": { "type": "string", "enum": ["base", "arc", "baseSepolia"], "description": "Chain key. `base` = 8453, `arc` = 5042, `baseSepolia` = 84532. A test key may only name `baseSepolia`; a live key may not.", "example": "arc" },
          "wallet": { "type": "string", "pattern": "^0x[a-fA-F0-9]{40}$", "description": "The address that will sign. It must be yours. We only ever prepare for the address you name here." },
          "deposits": {
            "type": "array",
            "minItems": 2,
            "maxItems": 2,
            "description": "Exactly two tokens. Each `amount` is an integer string in **base units** — never a float. You supply both sides, so we never have to guess a price.",
            "items": {
              "type": "object",
              "required": ["symbol", "amount"],
              "properties": {
                "symbol": { "type": "string", "description": "Token symbol or address. Supported — base: USDC, USDT, EURC · arc: USDC, EURC · baseSepolia: USDC, USDT.", "example": "USDC" },
                "amount": { "type": "string", "description": "Amount in base units, as a string.", "example": "1000000" }
              }
            }
          },
          "makerFeeBps": { "type": "integer", "minimum": 0, "maximum": 2000000, "default": 0, "description": "**Your** commission, in the protocol's 1e7 fee scale (`30000` = 0.30%). Independent of, and on top of, the 0.05% protocol fee." },
          "includeApprovals": { "type": "boolean", "default": true, "description": "Set `false` to receive only the `ship` step. By default the `approve` steps are included **only where your live allowance is short**." },
          "checkBalances": { "type": "boolean", "default": false, "description": "Add live balance checks to `preflight` so you can tell a human the transfer will succeed before asking them to approve." },
          "metadata": { "type": "object", "additionalProperties": { "type": "string" }, "description": "Bounded free-form map (no secrets). Echoed back; returned in webhooks and exports." }
        }
      },
      "PreparedTransaction": {
        "type": "object",
        "required": ["step", "purpose", "to", "value", "data"],
        "properties": {
          "step": { "type": "integer", "description": "1-based. Execute in this order." },
          "purpose": { "type": "string", "enum": ["approve", "ship", "swap", "dock"], "description": "What this transaction does." },
          "to": { "type": "string", "description": "Target contract." },
          "value": { "type": "string", "description": "Native value in wei, as a string. Almost always `0`." },
          "data": { "type": "string", "description": "Calldata. This is the payload you sign." },
          "description": { "type": "string", "description": "Plain-language explanation of this step, suitable for showing to a human approver." }
        }
      },
      "FeeBreakdown": {
        "type": "object",
        "required": ["protocolFeeBps", "protocolFeePercent", "removable"],
        "properties": {
          "protocolFeeBps": { "type": "integer", "const": 5000, "description": "The protocol fee in the protocol's 1e7 scale. 5000 = 0.05%. A constant, not a parameter." },
          "protocolFeePercent": { "type": "string", "const": "0.05" },
          "makerFeeBps": { "type": "integer", "description": "The integrator's own commission, as supplied." },
          "makerFeePercent": { "type": "string" },
          "totalFeePercent": { "type": "string" },
          "embeddedIn": { "type": "string", "const": "signed_order_data", "description": "The fee lives inside the order data you sign. It is not a separate transfer or an invoice." },
          "removable": { "type": "boolean", "const": false, "description": "Always false. No parameter, tier or agreement waives the protocol fee." },
          "explanation": { "type": "string" }
        }
      },
      "PreparedPosition": {
        "type": "object",
        "required": ["data", "meta"],
        "properties": {
          "data": {
            "type": "object",
            "required": ["status", "chain", "wallet", "strategyHash", "transactions", "fee"],
            "properties": {
              "status": { "type": "string", "const": "prepared", "description": "Always `prepared`. Nothing is signed or broadcast by this call." },
              "chain": { "type": "string" },
              "chainId": { "type": "integer" },
              "wallet": { "type": "string", "description": "The address that must sign, echoed back." },
              "strategyHash": { "type": "string", "description": "keccak256 of the order you are about to sign. The position's identity, and checkable by you from the returned `data`." },
              "transactions": { "type": "array", "items": { "$ref": "#/components/schemas/PreparedTransaction" }, "description": "Ordered. `approve` steps appear only where your live allowance is short." },
              "fee": { "$ref": "#/components/schemas/FeeBreakdown" },
              "position": {
                "type": "object",
                "properties": {
                  "tokenA": { "type": "object", "additionalProperties": true, "description": "Lower address of the two, with symbol, address, amount and decimals." },
                  "tokenB": { "type": "object", "additionalProperties": true },
                  "impliedRate": { "type": ["number", "null"], "description": "amountA / amountB, for your own sanity check. Not used in the order." },
                  "programBytes": { "type": "integer", "description": "Size of the strategy program that carries the fee." }
                }
              },
              "preflight": {
                "type": "object",
                "properties": {
                  "status": { "type": "string", "enum": ["ready", "review"], "description": "`review` means one of `checks` failed — tell a human before asking them to approve." },
                  "checks": { "type": "array", "items": { "type": "object", "properties": { "name": { "type": "string" }, "ok": { "type": "boolean" }, "detail": { "type": "string" } } } }
                }
              },
              "idempotent": { "type": "boolean", "description": "True when an Idempotency-Key was supplied. The salt is derived from it, so the same key returns the same `strategyHash`." }
            }
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "PositionStatus": {
        "type": "object",
        "required": ["data", "meta"],
        "properties": {
          "data": {
            "type": "object",
            "required": ["positionId", "strategyHash", "status"],
            "properties": {
              "positionId": { "type": "string", "description": "Same value as `strategyHash`. There is no second identifier." },
              "strategyHash": { "type": "string" },
              "status": { "type": "string", "enum": ["prepared", "shipped", "filled"], "description": "`prepared` — nothing observed on-chain yet. `shipped` — the strategy is in the live catalogue, so your wallet signed and broadcast it. `filled` — it has settled volume. `signed` and `failed` are deliberately absent: neither is observable by us without claiming knowledge we do not have." },
              "chain": { "type": "string" },
              "chainId": { "type": "integer" },
              "wallet": { "type": "string" },
              "tokenA": { "type": "object", "additionalProperties": true },
              "tokenB": { "type": "object", "additionalProperties": true },
              "makerFeeBps": { "type": ["integer", "null"] },
              "protocolFeeBps": { "type": "integer" },
              "observedOnChain": { "type": "boolean", "description": "True once we have seen the position in the live catalogue." },
              "volumeUsd": { "type": ["number", "null"] },
              "preparedAt": { "type": "string", "format": "date-time" },
              "firstSeenOnChainAt": { "type": ["string", "null"], "format": "date-time", "description": "When **we** first observed it on-chain, not necessarily when it happened." },
              "firstSeenFilledAt": { "type": ["string", "null"], "format": "date-time" },
              "signer": { "type": "string", "description": "Always the client. This endpoint has never signed or broadcast anything." }
            }
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message", "requestId", "retryable"],
            "properties": {
              "code": { "type": "string", "description": "Stable, machine-mappable. Branch on this, never on the message.", "example": "RATE_LIMITED" },
              "message": { "type": "string", "description": "Human-readable. May change without notice." },
              "requestId": { "type": "string" },
              "retryable": { "type": "boolean", "description": "True when the same request may be retried after a backoff." },
              "documentation": { "type": "string", "format": "uri", "description": "A link to the definition of this code." },
              "issues": {
                "type": "array",
                "description": "Structured, client-fixable problems (allowance, balance, chain).",
                "items": {
                  "type": "object",
                  "properties": {
                    "type": { "type": "string", "example": "INSUFFICIENT_ALLOWANCE" },
                    "expected": { "type": "string" },
                    "actual": { "type": "string" },
                    "spender": { "type": "string" }
                  }
                }
              }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed or missing parameters",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "MethodNotAllowed": {
        "description": "Wrong HTTP method",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Forbidden": {
        "description": "Origin or scope not allowed — the endpoint is restricted to TRDEFI pages, or the key does not carry the `prepare` scope.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unauthorized": {
        "description": "Missing, malformed, revoked or out-of-scope key. The envelope never says which — a wrong key and a revoked key are indistinguishable.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": { "code": "UNAUTHORIZED", "message": "A valid API key is required", "requestId": "…", "retryable": false, "documentation": "https://yield.trdefi.com/docs/api#errors" } }
          }
        }
      },
      "NotFound": {
        "description": "No such position — or it belongs to a different key. The two are deliberately indistinguishable.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Unprocessable": {
        "description": "The intent is well-formed but cannot be prepared — for example an expired preparation, a pair that is not live, or a strategy that is not attached to the verified router. Client-fixable problems carry a structured `issues[]`.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. `Retry-After` is always present.",
        "headers": { "Retry-After": { "schema": { "type": "integer" } }, "X-Request-Id": { "$ref": "#/components/headers/RequestId" } },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": { "code": "RATE_LIMITED", "message": "Rate limit exceeded for stats", "requestId": "…", "retryable": true, "documentation": "https://yield.trdefi.com/docs/api/errors#rate_limited" } }
          }
        }
      },
      "UpstreamUnavailable": {
        "description": "Our data source is temporarily unavailable. Retry shortly.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      },
      "Internal": {
        "description": "Unexpected error",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  }
}
