Skip to main content

What is a Rate?

A rate is a firm FX quote that guarantees an exchange rate for converting NGN to USDC/USDT. Each rate has:
  • Exchange Rate: NGN → USD/USDC/USDT conversion ratio
  • TTL (Time-to-Live): ~30-minute validity window
  • Rate ID: Unique identifier for binding to onramps
  • Minimum Deposit: Per-chain minimum amounts
New rates are generated approximately every 10 minutes. Rates expire ~30 minutes after creation.

Rate Properties

PropertyDescriptionExample
rate_idUnique identifierrate_8x7k2mq9p
fromSource currencyNGN
toDestination currencyUSDC, USDT, USD
rateConversion rate (from → to)1545.50 (1 USDC = 1545.50 NGN)
inverse_rateInverse rate (to → from)0.000647 (1 NGN = 0.000647 USDC)
fee_bpsFee in basis points50 (0.5%)
min_deposit_ngnMinimum NGN amount per chain1500.00
created_atWhen rate was generated2026-01-14T15:05:00Z
expires_atWhen rate becomes invalid2026-01-14T15:35:00Z

Getting Rates

Request current exchange rates using GET /v1/rates:
curl --request GET \
  --url 'https://api.daya.xyz/v1/rates?from=NGN&to=USDC' \
  --header 'Authorization: Bearer YOUR_API_KEY'
Response:
{
  "rate_id": "rate_8x7k2mq9p",
  "from": "NGN",
  "to": "USDC",
  "rate": 1545.50,
  "inverse_rate": 0.000647,
  "fee_bps": 50,
  "min_deposit_ngn": 1500.00,
  "created_at": "2026-01-14T15:05:00Z",
  "expires_at": "2026-01-14T15:35:00Z"
}
See Get Rates API for full details.

Query Parameters

ParameterRequiredDescriptionAllowed Values
from✅ YesSource currencyNGN
to❌ NoDestination currency (defaults to USDC)USDC, USDT, USD
USD is treated as equivalent to USDC/USDT in v0.1. Depeg scenarios are not handled.

Rate Validity

Rates expire approximately 30 minutes after creation. Timeline:
Always check expires_at before using a rate_id to create an onramp. Using an expired rate will result in an error.

Rate Generation Schedule

New rates are generated approximately every 10 minutes:
TimeRate IDValid Until
15:05rate_abc15:35
15:15rate_def15:45
15:25rate_ghi15:55
15:35rate_jkl16:05
This ensures continuous availability of valid rates.

Rate Semantics by Onramp Type

Locked to rate_id at creation timeWhen you create a throwaway onramp:
  1. You specify a rate_id
  2. Onramp is bound to that rate
  3. Deposits within rate validity execute at that rate
  4. Deposits after rate expiry are FLAGGED
Guarantee: Rate is firm. No slippage within validity window.Example:
rate_id: rate_abc
rate: 1545.50 NGN/USDC
expires_at: 15:35

- Deposit at 15:20 → Uses 1545.50 ✅
- Deposit at 15:40 → FLAGGED ❌

Minimum Deposits

Each rate includes min_deposit_ngn, which specifies the minimum NGN amount for the destination chain.
ChainMin Deposit NGNApprox USD
BASE1,500~$1.00
Ethereum1,500~$1.00
Polygon1,500~$1.00
Minimum deposits are designed to cover on-chain gas fees. Deposits below minimum are rejected with status FAILED.

Rate Calculation

The rate includes Daya’s spread (fee):
Displayed Rate = Market Rate × (1 - fee_bps / 10000)
Example:
  • Market rate: 1550 NGN/USDC
  • Fee: 50 bps (0.5%)
  • Displayed rate: 1550 × (1 - 0.005) = 1545.50 NGN/USDC
The fee is already included in the displayed rate. Merchants do not need to calculate it separately.

Handling Rate Expiry

1

Request rate before onramp creation

Always call GET /v1/rates immediately before creating an onramp.
2

Check expires_at

Ensure expires_at is at least 25 minutes in the future (for throwaway onramps with 25-min TTL).
3

Show expiry to users

Display a countdown timer so users know they must complete the transfer quickly.
4

Handle expired rate errors

If POST /v1/onramp returns rate_expired, request a fresh rate and retry.

Edge Cases

Scenario: FX venue is temporarily unavailableAPI Response:
{
  "error": {
    "code": "rate_unavailable",
    "message": "No valid exchange rates available at this time",
    "details": "Please try again in a few minutes"
  }
}
Merchant action: Retry after a few minutes or notify users of temporary unavailability.
Scenario: Rate expires between GET /v1/rates and POST /v1/onrampAPI Response:
{
  "error": {
    "code": "rate_expired",
    "message": "The specified rate_id has expired",
    "details": "Request a new rate via GET /v1/rates"
  }
}
Merchant action: Request a fresh rate and retry onramp creation.
Scenario: User transfers NGN after rate expiresResult: Deposit status → FLAGGEDReason: "Deposit received after rate expiry"Resolution: Manual operations review required.

Rate Immutability

Once a rate is published:
  • It cannot be modified
  • It cannot be extended beyond expires_at
  • It cannot be retroactively applied
This ensures onramps have predictable FX behavior.

Best Practices

Cache rates client-side with respect to expires_at. Refresh when approaching expiry.
const rateCache = {
  rate: null,
  expiresAt: null
};

async function getValidRate() {
  if (!rateCache.rate || new Date() > rateCache.expiresAt) {
    const response = await fetch('/v1/rates?from=NGN');
    rateCache.rate = await response.json();
    rateCache.expiresAt = new Date(rateCache.rate.expires_at);
  }
  return rateCache.rate;
}
Display remaining validity time to users:
function getSecondsRemaining(expiresAt) {
  return Math.max(0, (new Date(expiresAt) - new Date()) / 1000);
}
If no rate is available, show a friendly message instead of cryptic errors.
Log which rate_id was used for each onramp creation for troubleshooting.

Testing Rates

In sandbox, rates are simulated but follow the same behavior as production:
  • Generated every ~10 minutes
  • Expire after ~30 minutes
  • Same API surface
You can test:
  • Rate expiry handling
  • Onramp creation with expired rates
  • Deposit after rate expiry scenarios

Next Steps