Version: 1.1 · Effective: 2026-09-25 · Applies to: https://yield.trdefi.com/api/ and https://yield.trdefi.com/v1/
The endpoint reference, the response schemas, the integration flow, and — the part that matters most to an integrator — the write path: how a client turns an intent into a position without ever giving us a key.
Whatever else is in this document, these three hold:
1 · Reading is free and needs no key. Every GET /api/* endpoint is open to anyone. Browse the catalogue, price a trade, pull stats, embed a badge — no signup, no key, no account.
2 · To trade you need an API key. To initiate a trade — to create a position or swap through the API — you need a key, issued after a short review. Request one here. The key prepares; it never signs.
3 · We never hold, move or sign with a client's funds. The write path is prepare-only: the API returns a transaction that the client's own wallet signs. No endpoint accepts a private key, a seed phrase or a signing grant. There is nothing of ours to compromise, because we never hold anything.
4 · The protocol fee is inside the position, not on top of it. Creating a position through TRDEFI — the web app or the API alike — embeds a 0.05 % protocol fee in the order the maker signs. It is written into the signed order data, executed on-chain, and cannot be removed by the integrator, by us after the fact, or by anyone else. Positions created before this rule was in force are unaffected: their terms are immutable on-chain.
Part 4 specifies the write path. Part 7 states the fee in full.
These twelve properties are identical for every client. They are the contract. Nothing here is negotiable per client, because the moment one client's contract differs from the base, the base stops being a contract.
| # | Property | Value |
|---|---|---|
| 1 | Credentials | Read surface: none. Write path: an API key, held server-side by the client, that prepares but cannot sign. |
| 2 | Request id | X-Request-Id on every response, echoed in the body |
| 3 | Versioning | Path-versioned resources; OpenAPI 3.1 as the contract |
| 4 | Errors | { error: { code, message, requestId, retryable, documentation } } — always |
| 5 | Preflight | A result union that says whether the client must approve, is short of balance, or can proceed |
| 6 | Idempotency | Idempotency-Key on every write; 24 h retention; documented 5xx rule |
| 7 | Rate limits | RateLimit-* on success and failure; Retry-After on every 429 |
| 8 | Webhooks | Signed, retried, deduplicated, reorg-aware, with a simulate endpoint |
| 9 | Values | Amounts as strings in base units with explicit decimals; ISO-8601 UTC timestamps |
| 10 | Freshness | Every derived value states its age |
| 11 | Caching | Cache-Control always sent; meta.cached always honest |
| 12 | Deprecation | Public calendar, 90-day minimum notice, replacement before removal |
GET /api/statsCatalogue totals plus per-chain and top-pair roll-ups.
curl -s https://yield.trdefi.com/api/stats
{
"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": "…" } ],
"top_pairs_by_volume": [ { "pair": "USDC/USDT", "chain": "Ethereum", "count": 104, "maker_count": 87, "volume_30d_usd": 135300000, "logo_a": "…", "logo_b": "…" } ],
"top_pairs_by_positions": [ ]
},
"meta": { "requestId": "…", "generatedAt": "…", "apiVersion": "v1", "freshnessSeconds": 10800, "cached": false }
}
Bucket: stats (120/60 s) · Cache: 60 s
GET /api/chainsThe live set of chains. Treat as a catalog — never hardcode.
{ "chains": [ { "chain": "Ethereum", "count": 3426 } ], "requestId": "…" }
Bucket: chains (60/60 s)
GET /api/strategiesOpen liquidity positions.
| Parameter | Type | Notes |
|---|---|---|
resource | enum | list (default), chains, stats, detail, search |
pair | string | Exact pair, e.g. USDC/EURC |
chain | string | Chain key, e.g. base |
q | string | Free-text search (with resource=search) |
Bucket: strategies (20/60 s)
GET /api/strategy-detail?hash=<strategyHash>One position in detail. Bucket: strategy-detail (60/60 s)
GET /api/quote?hash=<strategyHash>&chain=<chain>&amount=<baseUnits>An indicative quote for one strategy. Nothing is signed; nothing moves. A signable order is produced only by the write path, and is still signed by the client (Part 4).
Quote simulation is enabled only for verified USDC/USDT strategies attached to the verified router. Any other strategy returns UNPROCESSABLE (422) with a reason rather than a guessed price.
| Parameter | Type | Notes |
|---|---|---|
hash | string | Required. Strategy hash, 64 hex characters. |
chain | string | Chain key. Defaults to ethereum. |
amount | string | Required. Amount in token base units, as a string. |
direction | enum | aToB (default) or bToA. |
Bucket: quote (60/60 s)
GET /api/badge?metric=<name>A shields.io endpoint payload for a repository README.
https://img.shields.io/endpoint?url=https%3A%2F%2Fyield.trdefi.com%2Fapi%2Fbadge%3Fmetric%3Dvolume30
Metrics: volume30 volume7 volume1 strategies makers pairs chains. Bucket: badge (120/60 s)
A client integration has five steps. Steps 1–3 need no credential at all. Step 5 is where a bespoke requirement belongs.
1. Discover GET /api/chains, GET /api/stats
→ learn what is live, do not hardcode
2. Read GET /api/strategies?chain=&pair= · GET /api/strategy-detail?hash=
→ list the positions, or inspect one
3. Price GET /api/quote?hash=&chain=&amount=
→ indicative only; never treat as a signed commitment
4. Prepare POST /v1/positions [API key, server-side]
→ returns UNSIGNED transactions + the fee breakdown
5. Configure (Enterprise) policy sets, approval groups, event subscriptions,
pre-signing hook → your custom revision, as data — see Part 6
A worked read integration:
# 1 · what is live
curl -s https://yield.trdefi.com/api/chains | jq '.chains[0]'
# 2 · the top pairs by settled volume
curl -s https://yield.trdefi.com/api/stats | jq '.data.top_pairs_by_volume[0:3]'
# 3 · an indicative quote
curl -s "https://yield.trdefi.com/api/quote?hash=<strategyHash>&chain=ethereum&amount=1000000" | jq
# 4 · how old is what I just read?
curl -s https://yield.trdefi.com/api/stats | jq '.meta.freshnessSeconds'
This is the part that makes the API non-custodial in the strict sense. The API never signs. It returns transactions that the client's own wallet signs, from the client's own address. There is no delegated signing, no signing grant, and no moment at which a credential of ours can move value.
client server TRDEFI API client's wallet
───────────── ─────────── ───────────────
1 POST /v1/positions ───────────► authenticate the key
Authorization: Bearer <key> validate the intent
Idempotency-Key: <uuid> embed the 0.05 % protocol fee
{ chain, wallet, read live allowance + balance
deposits:[{symbol,amount}×2], build UNSIGNED tx 1..n
makerFeeBps } (approve ×0-2, ship)
◄──── 200 { transactions[],
fee, strategyHash,
preflight }
2 (nothing has happened yet)
3 sign each tx locally ──────► user / HSM / multisig
(we are not involved)
4 broadcast ───────────────────────────────────────────────────► chain
Step 1 is the only call that needs a key. It is a computation of what must be signed, and it hands the result back. It cannot execute anything, and there is no code path in it that touches a private key.
POST /v1/positions — prepare a positioncurl -s -X POST https://yield.trdefi.com/v1/positions \
-H "Authorization: Bearer trd_live_<keyId>_<secret>" \
-H "Idempotency-Key: 8f14e45f-ea2b-4c1a-9b3e-1d2c3b4a5f60" \
-H "Content-Type: application/json" \
-d '{
"chain": "arc",
"wallet": "0xYourOwnWalletThatWillSign",
"deposits": [
{ "symbol": "USDC", "amount": "1000000" },
{ "symbol": "EURC", "amount": "900000" }
],
"makerFeeBps": 30000,
"checkBalances": true
}'
| Field | Type | Notes |
|---|---|---|
chain | enum | Required. base (8453), arc (5042) or baseSepolia (84532). |
wallet | address | Required. The address that will sign. It must be yours. |
deposits | array | Required. Exactly two { symbol, amount } entries. amount is in base units, as an integer string. |
makerFeeBps | integer | Your own commission, in the 1e7 scale (30000 = 0.30 %). 0–2000000. Default 0. |
includeApprovals | boolean | Default true. Set false to get only the ship step. |
checkBalances | boolean | Default false. Adds live balance checks to preflight. |
Two things are deliberately not parameters:
position.impliedRate is reported for your own sanity check, not used.{
"data": {
"status": "prepared",
"chain": "arc",
"chainId": 5042,
"wallet": "0xYourOwnWalletThatWillSign",
"strategyHash": "0x9f71222e…",
"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,
"explanation": "The 0.05% protocol fee is written into the order you sign. It is executed on-chain by the position itself and cannot be removed — not by you, not by us."
},
"position": {
"tokenA": { "symbol": "USDC", "address": "0x3600…0000", "amount": "1000000", "decimals": 6 },
"tokenB": { "symbol": "EURC", "address": "0xbef5…21c1", "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": "d2f7201ef219bb3a52228bb813ba779c",
"apiVersion": "v1",
"preparedAt": "2026-09-24T23:59:52.414Z",
"note": "Nothing has been signed or broadcast. Sign each transaction in `transactions` with your own wallet, in order, then broadcast."
}
}
Rules that apply to every POST /v1/positions:
transactions[] is ordered. Execute in the order given. The approve steps come first and are omitted when your live allowance already covers the amount — we read it from the chain before answering. The last step is always the operation itself.fee is informational and non-negotiable. It reports what is already embedded in the unsigned order data. There is no parameter that removes it, and no tier on which it is waived.strategyHash is the identity of the position. It is the keccak256 of the order you are about to sign, so you can compute it yourself from the returned data and check that what you sign is what we built.(your key, Idempotency-Key). The same key therefore returns the same strategyHash, so a retried request cannot create a second position. Omit the header and every call is a new position.preflight.status is ready, or review when one of its checks failed. It exists so you can tell a human "this will not go through" before asking them to approve — not after.GET /v1/positions/<strategyHash> — track itThe strategyHash returned by POST /v1/positions is the position's id. Use it to find out whether your own transaction landed — you do not have to poll an RPC to know.
curl -s https://yield.trdefi.com/v1/positions/0x9f71222e57269fd7525375aca3c66c13c7f27137eb052e4bf1e3dc8f9a284b08 \
-H "Authorization: Bearer $TRDEFI_KEY"
{ "data": {
"positionId": "0x9f7122…",
"status": "filled",
"chain": "arc", "chainId": 5042,
"wallet": "0xYourOwnWalletThatWillSign",
"tokenA": { "symbol": "USDC", "address": "0x3600…0000", "amount": "1000000" },
"tokenB": { "symbol": "EURC", "address": "0xbef5…21c1", "amount": "900000" },
"makerFeeBps": 30000, "protocolFeeBps": 5000,
"observedOnChain": true, "volumeUsd": 22013772.64,
"preparedAt": "…", "firstSeenOnChainAt": "…", "firstSeenFilledAt": "…",
"signer": "client — we never signed or broadcast anything" },
"meta": { "requestId": "…", "checkedAt": "…",
"timestampsMeaning": "…when TRDEFI first OBSERVED each state, not when it actually happened." } }
| Status | Meaning — and only what we have observed |
|---|---|
prepared | We returned unsigned transactions and have observed nothing on-chain yet. |
shipped | The position exists in the live catalogue — so your wallet signed, broadcast and it was included. |
filled | It has settled volume. |
Two deliberate omissions, both stated rather than papered over:
signed state. A signature is not observable until the transaction is broadcast, and by then it is shipped. Reporting a signed state would mean claiming knowledge we do not have.failed state. We watch the catalogue, not every reverted transaction. If your transaction reverts, your own receipt — not this endpoint — is the authority.firstSeenOnChainAt and firstSeenFilledAt are named for what they are: when we observed the state, which is not necessarily when it happened. Your transaction receipt carries the true time.
A key can only ever see positions it prepared itself. Another organisation's position id, and an id that does not exist, both return 404 — deliberately indistinguishable.
| With a write key | Possible? |
|---|---|
| Read the public catalogue | Yes (no key needed, but a key also works) |
| Prepare unsigned transactions | Yes |
| See the live allowance and balance of the wallet you name | Yes |
| Track a position you prepared | Yes — and only yours |
| Sign anything | Never |
| Broadcast on your behalf | Never |
| Move, withdraw or transfer value | Never |
| Spend outside the allowance you signed | Never |
Act on any wallet other than one you name as wallet | Never |
| See another organisation's keys, positions or activity | Never |
Every resource accepts a bounded free-form metadata map (no secrets). A client's cost centre, desk id or accounting tag travels with our objects and returns in webhooks and exports.
/api/chains and the asset catalog are live by default. A tenant may pin the exact set it tested against, for stability, and unpin later. Opt-in, never the default.
This is the part that matters most to a client with a requirement we have never seen. It describes the Enterprise tier.
Principle: the base is fixed; a client's differences are data. A client-specific revision is a configuration object plus, at most, the client's own endpoint. It is never a code change on our side, never a bespoke response shape, and never a client-only endpoint.
POST /v1/permissions
{
"rules": [
{ "effect": "allow", "action": "prepare", "chain": "base", "pair": "USDC/EURC",
"maxAmount": "25000", "window": "1d", "enforced": "service" },
{ "effect": "deny", "action": "prepare", "chain": "arbitrum", "enforced": "service" }
],
"approvalGroups": [ { "when": { "amountOver": "100000" }, "require": ["role:treasury_lead"] } ]
}
Ordered. Explicit deny wins. An amount scope that distinguishes a single transaction from a windowed total. Approval groups. And — the field that matters — enforced: every rule states where it is enforced, so the client can draw its own trust boundary without asking us. On this tier the enforcement point is service (we refuse to prepare) and, for anything that must bind the position itself, onchain (the allowance that the client's own wallet signed).
{ "profile": "transfer_restricted",
"actions": { "prepare": {"allow": true}, "transfer_out": {"allow": false, "reason": "requires_whitelisted_destination"} } }
Presets: open, transfer_restricted, redemption_only, freeze_only. A client may define its own without touching us.
{ "subscriptions": [
{ "events": ["position.shipped","position.filled","position.failed"], "url": "…", "signingKeyId": "…" },
{ "events": ["risk.ltv_changed"], "url": "…", "thresholdPct": "80" } ] }
Per-subscription URLs let a client route to different internal systems. Adding an event is the cheapest custom revision available, and it is the only thing a client should ever pin a product to.
The strongest extension point. Before we return a prepared transaction, we call the client's endpoint with the exact intent. The client returns allow or deny.
POST <client_url> { "intent": { … }, "tenant": "…", "requestId": "…" }
← 200 { "allow": true } or { "allow": false, "reason": "over my internal limit" }
This is how a client applies a rule we have never heard of — its own risk model, its own treasury policy, its own approval matrix — without us shipping a feature. Their logic, our preparation, their signature.
One rule, for everyone. A position created through TRDEFI — through the web app or through the API — carries a 0.05 % protocol fee, written into the order data the maker signs.
| Rate | 0.05 % of the traded amount |
| Where it lives | Inside the signed order data of the position. It is not a separate transfer and not an invoice. |
| Who is charged | Everyone who creates a new position. There is no tier, no volume threshold and no integration path that avoids it. |
| What the integrator controls | Their own makerFeeBps (their commission). The 0.05 % is on top and is not theirs to set. |
| Removable? | No. Not by the integrator, not by us, not after the fact. The fee is executed by the position itself when it trades. |
| Existing positions | Unaffected. Terms are immutable on-chain; positions created before this rule keep their original terms. |
| Reading data | Free, always. The fee applies to creating positions, never to reading. |
The fee is disclosed three times before anything is signed: in POST /v1/positions (the fee object), in the web app before the confirmation step, and in this document. An integrator that resells access must pass the fee through — it is part of the position, not a cost we absorb.
| Tier | Status | Read surface | Write path | Rate limits | Support |
|---|---|---|---|---|---|
| Open | Live | All read endpoints, no key | — | As advertised on each reply | |
| Integrator | Live (after review) | Same | POST /v1/positions — prepare-only, key held server-side, origin-restricted | Raised buckets per key, agreed | Email, named contact |
| Enterprise | Roadmap | Same | Same + policy sets, approval groups, pre-preparation hook, event subscriptions | Dedicated buckets | Named contact, agreed SLO |
Open and Integrator are live today. Enterprise describes the shape we are building towards. Where a capability is not yet available it is labelled above rather than implied.
Tier differences are limits, support and write-path features — never response shape. A client on any tier can be confident that the payload it parses today is the payload it parses tomorrow.
# 1 · discover the live set
curl -s https://yield.trdefi.com/api/chains | jq '.chains | length'
# 2 · headline numbers
curl -s https://yield.trdefi.com/api/stats \
| jq '{strategies: .data.totals.strategies, vol30: .data.totals.volume_30d_usd, fresh: .meta.freshnessSeconds}'
# 3 · the three deepest pairs
curl -s https://yield.trdefi.com/api/stats | jq '.data.top_pairs_by_positions[0:3]'
# 4 · inspect one position
curl -s "https://yield.trdefi.com/api/strategy-detail?hash=<hash>" | jq
# 5 · price an intended trade (indicative)
curl -s "https://yield.trdefi.com/api/quote?hash=<strategyHash>&chain=ethereum&amount=1000000" | jq '.rate'
# 6 · prepare a real position (needs a key)
curl -s -X POST https://yield.trdefi.com/v1/positions \
-H "Authorization: Bearer $TRDEFI_KEY" -H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"chain":"arc","wallet":"0x…","checkBalances":true,
"deposits":[{"symbol":"USDC","amount":"1000000"},{"symbol":"EURC","amount":"900000"}]}' \
| jq '.data.fee, (.data.transactions[] | {step, purpose, to})'
# → sign each transaction with your own wallet, in order, then broadcast
# 7 · read your own quota before a burst
curl -sD - -o /dev/null https://yield.trdefi.com/api/stats | grep -i '^ratelimit'
| Element | Where |
|---|---|
| OpenAPI 3.1 | https://yield.trdefi.com/openapi.json |
| Markdown twin of the docs | https://yield.trdefi.com/docs/api.md |
| Agent index | https://yield.trdefi.com/llms.txt |
| Request an API key | https://yield.trdefi.com/docs/api#access |
| Error code table | https://yield.trdefi.com/docs/api#errors |
| Rate limits | https://yield.trdefi.com/docs/api#rate-limits |
| Method of Statement | https://yield.trdefi.com/docs/method-of-statement |
| General Rules | https://yield.trdefi.com/docs/general-rules |
TRDEFI Ltd, London, United Kingdom. See also: Method of Statement, General Rules.