Appearance
Rates feed
A public, read-only feed of Textile's live rates and executed trades across every deployed corridor, in the standard exchange-integration format (the tickers / pairs / historical_trades shape aggregators like CoinGecko and Monierate consume). Poll it directly: no key, no signing.
Every corridor we run is listed automatically: cNGN (NGN), plus BRL, ARS, WETH, USDC and more. New corridors appear as soon as they deploy, with no config.
Base URL: https://api.textilecredit.com
GET only. CORS is open. Responses are cached for a few seconds, so poll as often as you like.
Conventions
- Each pair is quoted in its natural direction, so the price reads as a normal number:
- Fiat corridors quote the foreign currency per stablecoin:
USDT_NGN= NGN per USDT (~1394). cNGN is the naira stablecoin, 1:1 with NGN, so it's published asNGN. - Asset corridors quote the stablecoin per unit:
WETH_USDT= USDT per WETH (~1918).
- Fiat corridors quote the foreign currency per stablecoin:
ticker_idisBASE_TARGET.last_priceis always target per base (the CoinGecko convention).- Numeric fields are decimal strings.
GET /tickers
Live rate for every pair. Pass ticker_id (or base + optional target) to filter. Both sides are quoted at a ~100-stable notional (100 USDT, or 100 USDT's worth of the other leg at the market mid) because that is the app's default trade size, and a rate that size can't clear is not a rate.
| Param | Required | Example | Description |
|---|---|---|---|
ticker_id | no | USDT_NGN | Return only this pair. Unknown id → 404. |
base | no | USDT | Return every pair with this base. |
target | no | NGN | Combine with base to pin one pair. |
No parameters returns all pairs.
json
[
{
"ticker_id": "USDT_NGN",
"base_currency": "USDT",
"target_currency": "NGN",
"last_price": "1394.02",
"bid": "1394.02",
"ask": "1394.28",
"high": "1394.28",
"low": "1394.02",
"base_volume": "0",
"target_volume": "0"
}
]| Field | Description |
|---|---|
ticker_id | Pair identifier, BASE_TARGET. |
base_currency / target_currency | The two legs. |
last_price | Most recent cleared price in the last 24h, else the indicative reference (the better of bid/ask, or the market mid if neither side is quoting). |
bid / ask | The rate a ~100-stable trade clears at right now, net of fees: bid = selling the base, ask = buying it. 0 means that side has nothing fillable. |
high / low | Highest / lowest cleared price over the last 24h, else the same indicative reference as last_price. |
base_volume / target_volume | 24h traded volume in the base / target asset. |
bid/ask are the same numbers /s/swap shows for that trade size. For an RFQ corridor (every live fiat corridor today) they come from the makers' published levels, the same read as POST /v2/rfq/preview with no steering: indicative, refreshed by the makers every few seconds, no inventory locked. A corridor that still trades on the v1 book is priced by the book matcher instead, so the field means the same thing either way.
A firm RFQ quote reserves a maker's inventory for its accept window (seconds). Rather than flash 0 for that side while the reservation stands, the feed keeps showing the newest price it cleared at, for up to 90 seconds. A 0 past that, or a 0 with no recent quote at all, means no maker is quoting that side.
last_price / high / low may fall back to the market mid when there are no recent fills.
GET /pairs
The pairs the feed supports. Updates automatically as corridors deploy.
json
[
{ "ticker_id": "USDT_NGN", "base": "USDT", "target": "NGN" },
{ "ticker_id": "USDC_NGN", "base": "USDC", "target": "NGN" },
{ "ticker_id": "WETH_USDT", "base": "WETH", "target": "USDT" }
]GET /historical_trades
Executed trades for one pair, newest first. Same source as the 24h volume on /tickers: the swaps that actually filled, so these are real cleared prices, not quotes. Every settlement on the corridor's reactor counts, RFQ and v1-book alike; a filled RFQ shows up here the same as any other fill.
ticker_id is required (a full-history firehose across every pair isn't useful to anyone).
| Param | Required | Example | Description |
|---|---|---|---|
ticker_id | yes | USDT_NGN | The pair. base + target works too. Unknown pair → 404. |
type | no | buy | buy or sell. Omit for both. |
limit | no | 500 | Max trades per side. Default 200, max 1000 (higher is clamped). |
start_time | no | 1754800000 | Unix seconds, inclusive lower bound. |
end_time | no | 1754886400 | Unix seconds, inclusive upper bound. |
json
{
"buy": [
{
"trade_id": "0xa1b2c3d4…-7",
"price": "1394.02",
"base_volume": "100.00",
"target_volume": "139402.00",
"trade_timestamp": 1754800000,
"type": "buy"
}
],
"sell": []
}| Field | Description |
|---|---|
trade_id | The fill's transaction hash and log index, joined by -. Stable and unique, so it works as a dedupe key when polling. |
price | Cleared price, target per base. |
base_volume / target_volume | The two legs of that trade. |
trade_timestamp | Unix seconds. |
type | buy = the base was bought, sell = the base was sold: the same side convention as bid/ask above. |
One row is one filled order. A swap that settled against several makers prints one trade per fill, each at the price that fill actually cleared at: so the rows are real executions, not batch averages.
type=buy/type=sell still returns both keys: the filtered-out side is just an empty array.
To walk back through history, take the oldest trade_timestamp in the page you just got and request end_time = that - 1. Repeat until a page comes back empty.
Stepping one second back is safe because a page never splits a second: every second we return is complete. That also means a page can come back slightly larger than limit: if a single second holds more trades than you asked for, you get all of them rather than a fragment you'd have no way to ask for the rest of. Treat limit as a target, not a hard cap.
buy and sell share one cutoff, so a side can also come back shorter than limit, or empty, when the other side is the busier one. That's what keeps a single end_time valid for the whole response: the next page picks up whatever was held back.
Errors
Errors return { "error": "..." } with an appropriate status: 400 for a missing or malformed parameter, 404 for an unknown ticker_id, 405 for a non-GET method, 502 when the feed can't be built.