> ## Documentation Index
> Fetch the complete documentation index at: https://tetrafi.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Conventions

> The rules every TetraFi API request and response follows: amounts, times, ids, chains, paging, errors, request ids, rate limits, idempotency and versioning.

These rules hold on every endpoint, so a client can implement them once. The examples come from one script, [`samples/http/conventions.sh`](https://github.com/tetrafi/docs/blob/main/samples/http/conventions.sh), which reads them from live answers and stops if one is broken. It only lists assets and asks for one quote: nothing is ordered.

> [!NOTE]
>
> The API may change until launch. Every change is listed in the [Changelog](https://tetrafi.mintlify.site/changelog.md).

## Requests

| Rule | Detail |
| - | - |
| Base URL | `https://api.tetrafi.io`. Every path starts with `/api/v1`. |
| Credentials | `X-API-Key` on every request, plus a signature when request signing is on. The key implies its workspace, so `X-Workspace-Id` is optional; when sent, it must be the key's own. See [Authentication](https://tetrafi.mintlify.site/authentication.md). |
| Bodies | JSON, sent with `Content-Type: application/json`. |

## Values

| Value | Form | Example |
| - | - | - |
| Amounts | Decimal strings, never JSON numbers. Parse them with a decimal type, not a float. | `"10.0147"` |
| Times | RFC 3339, in UTC. | `"2026-10-30T00:40:13.162Z"` |
| Durations | ISO 8601. | `"PT2S"` |
| Chains | CAIP-2 chain ids. | `"eip155:8453"` |
| Field names | camelCase. | `expiresAt` |
| Ids | Opaque strings. Store and compare them whole; never parse them. | `"q_01JAY7Q5M3ZK8V2N4R6T8W0XYB"` |
| Enumerations | Open sets: new values may be added. Treat a value you do not recognise as "other". | `"status": "answered"` |

```bash cURL theme={null}
# Amounts are decimal strings, never JSON numbers: parse them with a decimal type, not a float.
# Times are RFC 3339 in UTC. Field names are camelCase.
jq -c '.quotes[0] // empty | {buy: .buy.amount, buyIsString: (.buy.amount | type == "string"),
  expiresAt, observedAt}' <<<"$quotes" >&2
```

```json Output on test networks theme={null}
{"buy":"10.0147","buyIsString":true,"expiresAt":"2026-10-30T00:40:13.162Z","observedAt":"2026-10-30T00:40:04.126Z"}
```

## Paging

A list that pages takes `limit` and answers `nextCursor`. Send it back as `cursor` until `nextCursor` is absent. A cursor is valid while the list it came from is unchanged: after a change it answers `409 CURSOR_STALE`, and you start again from the first page.

`GET /api/v1/assets` pages this way, from 1 to 200 assets a page, and `GET /api/v1/orders`, up to 20 orders a page. An order page can hold fewer than `limit` orders, even none, while more follow: stop only when `nextCursor` is absent or `null`.

```bash cURL theme={null}
# Lists that page take limit and return nextCursor; pass it back as cursor until it is absent.
# A cursor is valid while the list it came from is unchanged. After a change it answers
# 409 CURSOR_STALE: start again from the first page.
listed=() pages=0 cursor="" restarted=false
while :; do
  path="/api/v1/assets?limit=5${cursor:+&cursor=$cursor}"
  if ! page=$(tetrafi GET "$path" 2>/dev/null); then
    if [[ $(jq -r '.error' <<<"$page") == CURSOR_STALE && $restarted == false ]]; then
      listed=() pages=0 cursor="" restarted=true
      continue
    fi
    printf '%s\n' "$page" >&2
    exit 1
  fi
  pages=$((pages + 1))
  while IFS= read -r id; do listed+=("$id"); done < <(jq -r '.assets[].assetId' <<<"$page")
  cursor=$(jq -r '.nextCursor // empty' <<<"$page")
  [[ -z $cursor ]] && break
done
echo "assets: ${#listed[@]} across $pages pages" >&2
```

```json Output on test networks theme={null}
assets: 52 across 11 pages
```

## Errors

Every refusal is one JSON body with the same fields: `error`, `message`, `requestId`, `timestamp`, `docUrl`, and `next` and `details` when they apply. Each code has one HTTP status, and `docUrl` links its entry in the [error-code reference](https://tetrafi.mintlify.site/reference/error-codes.md). Match on `error`, never on `message`. [Errors and refusals](https://tetrafi.mintlify.site/trade/errors.md) shows how to read one, and the [error-code reference](https://tetrafi.mintlify.site/reference/error-codes.md) lists every code.

## Request ids

Every response carries an `X-Request-Id` header, and every refusal carries the same value as `requestId`. Quote it to support.

You can send your own `X-Request-Id`, up to 64 letters, digits, `-` and `_`, to find a request in your logs and ours. The response echoes it. A value outside that form is replaced with a generated UUID, and the response carries that instead.

```bash cURL theme={null}
# Send your own X-Request-Id (letters, digits, - and _, up to 64 characters) to find a request
# in your logs and ours; the response echoes it. Without one, TetraFi generates a UUID.
# The rate-limit headers say how many requests this key has left before a 429.
request_id="conventions-$(date +%s)"
headers_file=$(mktemp)
quotes=$(tetrafi POST /api/v1/quotes "$(jq -cn '{sell: {asset: "USDC"}, buy: {asset: "USDT"},
  sellAmount: "10"}')" "X-Request-Id: $request_id" "$headers_file")
header() { awk -v name="$1" 'tolower($1) == tolower(name ":") { sub(/\r$/, "", $2); print $2 }' "$headers_file" | tail -1; }
echo "{\"requestId\":\"$(header x-request-id)\",\"limit\":\"$(header x-ratelimit-limit)\",\"remaining\":\"$(header x-ratelimit-remaining)\",\"reset\":\"$(header x-ratelimit-reset)\"}" >&2
```

```json Output on test networks theme={null}
{"requestId":"conventions-1793320806","limit":"","remaining":"","reset":""}
```

## Rate limits

You may send one live quote request every 3 seconds per API key; a requote counts as one. A request answered entirely from cached prices does not count.

A request over a limit answers `429` with a `Retry-After` header in whole seconds and the same wait in `details.retryAfter`, for example `PT2S`. Wait that long, then send it once more. [Errors and refusals](https://tetrafi.mintlify.site/trade/errors.md#the-quote-rate-limit) shows a retry.

When your key has a per-minute limit, every answer to it, successes and refusals alike, carries three headers:

| Header | Meaning |
| - | - |
| `X-RateLimit-Limit` | Requests per minute this key may send. |
| `X-RateLimit-Remaining` | Requests it may still send now. |
| `X-RateLimit-Reset` | Seconds until its full allowance is back. |

Without a per-key limit, as in the output above, the headers are absent; `X-RateLimit-Remaining` alone can still appear when a limit on your address counted the request. Treat an absent header as "no count", never as zero.

## Idempotency

Send an `Idempotency-Key` with every `POST /api/v1/orders`. A retry with the same key and body returns the first answer, with the header `Idempotent-Replayed: true`. The same key with a different body is refused. [Idempotency](https://tetrafi.mintlify.site/trade/idempotency.md) covers replays, conflicts and recovering an order.

## Versioning

The API is versioned in its path, `/api/v1`. After launch, changes within a version are additive: new operations, optional fields, headers, enumeration values and error codes. A removal is announced in the changelog at least 6 months ahead. Until it happens, every answer of the affected operation, successes and refusals alike, carries three headers:

| Header | Meaning |
| - | - |
| `Deprecation` | When the operation was deprecated: `@` and Unix seconds, such as `@1735689600` ([RFC 9745](https://www.rfc-editor.org/rfc/rfc9745)). |
| `Sunset` | When it stops working, as an HTTP date such as `Tue, 01 Jul 2025 00:00:00 GMT` ([RFC 8594](https://www.rfc-editor.org/rfc/rfc8594)). |
| `Link` | The notice that says what to move to, marked `rel="deprecation"`. |

No operation is deprecated today, so no answer carries them yet. Log any answer that does. Before launch, a change can still remove or rename something.

[Compatibility](https://tetrafi.mintlify.site/compatibility.md) is the full policy. The [Changelog](https://tetrafi.mintlify.site/changelog.md) lists every change a client can see, with what to do about it.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.