Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api-reference/general/get-token-rate-v1.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ openapi: "openapi-v1.yaml GET /rates/{token}/{amount}/{fiat}"

Retrieve an exchange rate for a token amount and fiat pair (legacy v1 API). Response `data` is a **single** rate value (string/number).

For **network in the path** and **buy/sell** quotes, use [Get Token Rate (v2)](/api-reference/general/get-token-rate) — `GET /v2/rates/{network}/{token}/{amount}/{fiat}`.
For **network in the path** and **buy/sell** quotes, use [Get Token Rate (v2)](/api-reference/general/get-token-rate) — `GET /v2/rates/{network}/{from}/{amount}/{to}`.
27 changes: 18 additions & 9 deletions api-reference/general/get-token-rate.mdx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
title: 'Get Token Rate'
openapi: "openapi-v2.yaml GET /rates/{network}/{token}/{amount}/{fiat}"
openapi: "openapi-v2.yaml GET /rates/{network}/{from}/{amount}/{to}"
---

Get buy and/or sell quotes for a token amount and fiat pair on a **specific network**. This is the recommended public rates URL for v2 integrations.
Get buy and/or sell quotes on a **specific network**—either a **token + fiat** pair or a **fiat + fiat** corridor (via USDC). This is the recommended public rates URL for v2 integrations.

<Note>
The legacy endpoint **`GET /v1/rates/{token}/{amount}/{fiat}`** (optional `network` query) returns a **single numeric** `data` field. The v2 path below returns a structured **`buy` / `sell`** payload.
Expand All @@ -14,23 +14,32 @@ Get buy and/or sell quotes for a token amount and fiat pair on a **specific netw
| Parameter | Description |
|-----------|-------------|
| `network` | Network id: `ethereum`, `base`, `bnb-smart-chain`, `lisk`, `scroll`, `celo`, `arbitrum-one`, `polygon`, `asset-chain` |
| `token` | Token symbol (e.g. `USDT`, `USDC`) |
| `amount` | Crypto notional the quote applies to |
| `fiat` | Fiat currency code (e.g. `NGN`, `KES`) |
| `from` | Fiat currency code **or** token symbol on `network` (paired with `to`) |
| `amount` | For **token + fiat**: crypto (token) notional. For **fiat + fiat**: amount in **`from`** fiat. |
| `to` | Fiat currency code **or** token symbol on `network` (paired with `from`) |

**Token + fiat:** exactly **one** fiat and **one** token (`.../USDT/100/NGN` ≡ `.../NGN/100/USDT`). **`from`/`to` cannot both be tokens.** **Path order does not imply trade direction**—use `side` or read **`data.buy`** / **`data.sell`** for onramp vs offramp display.

**Fiat + fiat:** both are fiat codes; the server quotes through **USDC** on the same network. **`sell.rate`** = **`to` per 1 `from`**. **`buy.rate`** = **`from` per 1 `to`** (inverse corridor, **not** the reciprocal of `sell`—each side uses the appropriate leg bid/ask).

## Query parameters

| Parameter | Required | Description |
|-----------|----------|-------------|
| `side` | No | `buy` or `sell` to return **only** that side. Omit to receive **both** when available. |
| `from_source` | No | If `from` matches **both** a fiat currency and a crypto asset on the network, set to **`fiat`** or **`crypto`**. Omit when unambiguous. |
| `to_source` | No | If `to` matches **both** a fiat currency and a crypto asset on the network, set to **`fiat`** or **`crypto`**. Omit when unambiguous. |
| `provider_id` | No | Exactly **8 alphabetic** characters (`A–Z`, `a–z`) to pin the quote to one provider. |

## Example

```bash
# Both buy and sell quotes on Base
# Both buy and sell quotes on Base (token then fiat)
GET https://api.paycrest.io/v2/rates/base/USDT/100/NGN

# Same pair with fiat then token
GET https://api.paycrest.io/v2/rates/base/NGN/100/USDT

# Sell side only (typical for offramp display)
GET https://api.paycrest.io/v2/rates/base/USDT/100/NGN?side=sell

Expand All @@ -44,7 +53,7 @@ Each populated side includes:

| Field | Type | Description |
|-------|------|-------------|
| `rate` | string | Quoted rate (fiat per crypto) for that side |
| `rate` | string | For token+fiat: fiat per crypto. For fiat+fiat: see path rules above (`sell` = `to` per `from`, `buy` = `from` per `to`). |
| `providerIds` | string[] | Provider id(s) for the quote |
| `orderType` | string | e.g. `regular` or `otc` |
| `refundTimeoutMinutes` | integer | Minutes until automatic refund for that flow |
Expand Down Expand Up @@ -76,13 +85,13 @@ When `side=buy` or `side=sell`, only that key is present under `data`.

| HTTP | Typical cause |
|------|----------------|
| **400** | Invalid `amount`, invalid `side` (must be `buy` or `sell`), invalid `provider_id` (must be exactly 8 letters A–Z or a–z), token not supported on network, unsupported fiat, or conversion rules (e.g. stable only to its base fiat) |
| **400** | Invalid `amount`, invalid `side`, invalid `from_source`/`to_source` (must be `fiat` or `crypto`), ambiguous segment without disambiguation, invalid `provider_id`, unsupported asset on network, or conversion rules (e.g. stable only to its base fiat) |
| **404** | No provider available for the amount/currency/network |
| **503** | Banking/mobile network issues affecting providers for the currency |
| **500** | Internal error while resolving the rate |

## Rate resolution

Behavior matches the priority-queue–based resolution used when creating orders: the quote reflects provider selection and validation for the requested **side** (`buy` vs `sell`). Use **`sell`** for **offramp** (crypto → fiat) UX and **`buy`** for **onramp** (fiat → crypto) when displaying a rate before `POST /v2/sender/orders`.
Behavior matches the priority-queue–based resolution used when creating orders: the quote reflects provider selection and validation for the requested **side** (`buy` vs `sell`). **Path segment order (`from` / `to`) does not select direction**—use `?side=`, or read **`data.sell`** for **offramp** (crypto → fiat) display and **`data.buy`** for **onramp** (fiat → crypto) before `POST /v2/sender/orders`.

See also [Get Token Rate (v1)](/api-reference/general/get-token-rate-v1) for the legacy scalar response.
2 changes: 1 addition & 1 deletion api-reference/introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ For protocol information and utilities:
- **`GET /currencies`** - List supported fiat currencies
- **`GET /institutions/{currency_code}`** - List supported institutions
- **`GET /tokens`** - List supported tokens and contract addresses
- **`GET /rates/{network}/{token}/{amount}/{fiat}`** - Get public buy/sell rate quotes for a token/fiat pair on a network (no API key)
- **`GET /rates/{network}/{from}/{amount}/{to}`** - Get public buy/sell rate quotes (token+fiat or fiat+fiat via USDC on a network; no API key)
- **`GET /pubkey`** - Get aggregator public key
- **`POST /verify-account`** - Verify bank account details before creating an order
- **`GET /orders/{chain_id}/{id}`** - Get order status by onchain Gateway ID
Expand Down
57 changes: 56 additions & 1 deletion implementation-guides/sender-api-integration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,59 @@ order = requests.post(

---

## KES mobile money (M-Pesa, Till, Paybill)

Kenya offramp to mobile rails uses institution **`SAFAKEPC`** (Safaricom M-Pesa) or another KES **mobile_money** code from **[GET /institutions/KES](/api-reference/general/list-supported-institutions)**. **Till** and **Paybill** are not separate institution codes—you pass them in **`destination.recipient.metadata`**.

| Channel | `metadata.channel` | `accountIdentifier` | Notes |
|---------|-------------------|---------------------|--------|
| Phone M-Pesa | `Mobile` or omit | Mobile number (e.g. `254712345678`) | Default when channel is omitted and the identifier looks like a phone number. Use **`Mobile`** in new integrations (not `MoMo`, which is provider-internal). |
| Till (buy goods) | `Till` | Till / buy-goods number | Do not rely on phone normalization; till numbers are not E.164 phones. |
| Paybill | `Paybill` | Paybill account or reference | Set **`metadata.businessNumber`** to the paybill business number when required. |

**Example — Till**

```json
"destination": {
"type": "fiat",
"currency": "KES",
"recipient": {
"institution": "SAFAKEPC",
"accountIdentifier": "123456",
"accountName": "Merchant Name",
"memo": "Order 42",
"metadata": { "channel": "Till" }
}
}
```

**Example — Paybill**

```json
"destination": {
"type": "fiat",
"currency": "KES",
"recipient": {
"institution": "SAFAKEPC",
"accountIdentifier": "INV-001",
"accountName": "Customer Name",
"memo": "Invoice",
"metadata": {
"channel": "Paybill",
"businessNumber": "400200"
}
}
}
```

**KYC on offramp:** optional **`destination.kyc`** applies to the **recipient** only. Do not send sender or provider KYB on offramp create.

**GET / webhooks (v2):** for offramp orders, **`destination.recipient.metadata`** returns the metadata you supplied on create (same shape as above). **`destination.kyc`** returns destination KYC when stored. There is no v1-style flat metadata fallback on v2 GET.

**Verify-account:** Till and Paybill identifiers are not normalized as mobile phone numbers. Use the exact till or paybill values you will send on the order.

---

## Handle the create response

The create response includes a `providerAccount` whose shape depends on direction: **offramp** gives a **crypto receive address**; **onramp** gives **virtual account / fiat transfer** instructions.
Expand Down Expand Up @@ -479,6 +532,8 @@ Use this when you want **clearer UX** before the user confirms—not because the
- **Offramp** — verify the **fiat recipient** you will send in `destination.recipient` (same `institution`, `accountIdentifier`, and `currency` as the order).
- **Onramp** — verify **`source.refundAccount`** (where fiat refunds go), not the crypto wallet in `destination.recipient`.

For **KES Till or Paybill**, pass the same **`accountIdentifier`** and **`metadata.channel`** you will use on create. Phone-style normalization does not apply to till or paybill numbers.

```bash
curl -X POST "https://api.paycrest.io/v2/verify-account" \
-H "API-Key: YOUR_API_KEY" \
Expand All @@ -494,7 +549,7 @@ A `200 OK` response returns the resolved `accountName` in `data`. If `data` is a

### Prefetch a quote for the UI

**`GET /v2/rates/{network}/{token}/{amount}/{fiat}`** is **public** (no API key). Use it when you want to **show** a rate before submit, or when you will pass **`rate`** on create (it must stay within market tolerance). If you omit **`rate`** on create, the API still picks an acceptable rate.
**`GET /v2/rates/{network}/{from}/{amount}/{to}`** is **public** (no API key). **`from` and `to`** are the fiat code and token symbol in either order. Use it when you want to **show** a rate before submit, or when you will pass **`rate`** on create (it must stay within market tolerance). If you omit **`rate`** on create, the API still picks an acceptable rate.

<Note>
The JSON `data` object includes **`buy`** and **`sell`** (unless you pass `?side=buy` or `?side=sell`). Use **`data.sell.rate`** for **offramp** and **`data.buy.rate`** for **onramp** when displaying a quote. **`provider_id`** is optional and must be exactly **8 letters** (`A–Z` or `a–z`) if you pin a provider.
Expand Down
Loading