Create a card FX quote
curl --request POST \
--url https://sandbox-api.sakawave.example/v1/card-fx-quotes \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"operation": "funding",
"accountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"amountUsd": "5000"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
operation: 'funding',
accountId: '01K2Z3AB4D5E6F7G8H9JKMNPQR',
amountUsd: '5000'
})
};
fetch('https://sandbox-api.sakawave.example/v1/card-fx-quotes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://sandbox-api.sakawave.example/v1/card-fx-quotes"
payload = {
"operation": "funding",
"accountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"amountUsd": "5000"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "01K2Z3AB4V5W6X7Y8ZABCDEFGH",
"operation": "funding",
"sourceCurrency": "NGN",
"destinationCurrency": "USD",
"sourceAmount": "8000000",
"destinationAmount": "5000",
"rate": "0.000625",
"expiresAt": "2026-08-14T09:26:00.000Z"
}{
"statusCode": 400,
"message": "Must be a valid identifier.",
"code": "validation_failed",
"errors": [
{
"message": "Must be a valid identifier.",
"field": "accountId",
"code": "custom",
"path": [
"accountId"
]
}
]
}{
"statusCode": 401,
"code": "INVALID_API_KEY",
"message": "Invalid or inactive API credentials."
}{
"statusCode": 403,
"message": "International wallets for your customers are not available yet. Your own approval cannot be used for a customer wallet.",
"code": "international_customer_wallet_not_supported",
"currency": "USD"
}{
"statusCode": 404,
"code": "resource_not_found",
"message": "Resource not found."
}{
"statusCode": 409,
"code": "resource_conflict",
"message": "The request conflicts with the current resource state."
}{
"statusCode": 429,
"message": "Too many API requests. Try again later.",
"code": "api_key_rate_limit_exceeded",
"retryAfterSeconds": 17
}{
"statusCode": 500,
"code": "internal_server_error",
"message": "An unexpected error occurred."
}{
"statusCode": 503,
"code": "provider_unavailable",
"message": "We could not issue an account number just now. Try again shortly."
}Cards
Create a card FX quote
Locks a rate for funding a card from a non-USD account, or for withdrawing to one. Amounts are always expressed in USD minor units, whichever direction you are going.
POST
/
v1
/
card-fx-quotes
Create a card FX quote
curl --request POST \
--url https://sandbox-api.sakawave.example/v1/card-fx-quotes \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"operation": "funding",
"accountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"amountUsd": "5000"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
operation: 'funding',
accountId: '01K2Z3AB4D5E6F7G8H9JKMNPQR',
amountUsd: '5000'
})
};
fetch('https://sandbox-api.sakawave.example/v1/card-fx-quotes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://sandbox-api.sakawave.example/v1/card-fx-quotes"
payload = {
"operation": "funding",
"accountId": "01K2Z3AB4D5E6F7G8H9JKMNPQR",
"amountUsd": "5000"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "01K2Z3AB4V5W6X7Y8ZABCDEFGH",
"operation": "funding",
"sourceCurrency": "NGN",
"destinationCurrency": "USD",
"sourceAmount": "8000000",
"destinationAmount": "5000",
"rate": "0.000625",
"expiresAt": "2026-08-14T09:26:00.000Z"
}{
"statusCode": 400,
"message": "Must be a valid identifier.",
"code": "validation_failed",
"errors": [
{
"message": "Must be a valid identifier.",
"field": "accountId",
"code": "custom",
"path": [
"accountId"
]
}
]
}{
"statusCode": 401,
"code": "INVALID_API_KEY",
"message": "Invalid or inactive API credentials."
}{
"statusCode": 403,
"message": "International wallets for your customers are not available yet. Your own approval cannot be used for a customer wallet.",
"code": "international_customer_wallet_not_supported",
"currency": "USD"
}{
"statusCode": 404,
"code": "resource_not_found",
"message": "Resource not found."
}{
"statusCode": 409,
"code": "resource_conflict",
"message": "The request conflicts with the current resource state."
}{
"statusCode": 429,
"message": "Too many API requests. Try again later.",
"code": "api_key_rate_limit_exceeded",
"retryAfterSeconds": 17
}{
"statusCode": 500,
"code": "internal_server_error",
"message": "An unexpected error occurred."
}{
"statusCode": 503,
"code": "provider_unavailable",
"message": "We could not issue an account number just now. Try again shortly."
}When you need one
Only when the account on your side is not USD. A USD account moves money to and from a card without a quote, and asking for one anyway is refused with409 card_fx_quote_not_required.
Funding and withdrawal quotes are not interchangeable
A quote is bound to itsoperation, its accountId, and its cardId. Using a funding quote for a withdrawal, or a quote made for another card, fails with card_fx_quote_invalid.
cardId is optional for a funding quote — you may not have a card yet when issuing one — and required for a withdrawal.
expiresAt comes from the FX provider, not from a fixed Sakawave window. Read it rather than assuming a duration, and quote immediately before the movement.Error codes
| Status | code | Cause |
|---|---|---|
400 | validation_failed | A field failed validation, or cardId was omitted on a withdrawal quote. |
403 | card_client_account_required | The account is a customer wallet. |
409 | card_account_ineligible | The account is missing or not active. |
409 | card_fx_quote_not_required | The account is already USD. |
409 | card_account_unavailable | The card balance is not ready at the provider. |
503 | invalid_card_fx_quote | The provider returned an incomplete or already-expired quote. |
401 | INVALID_API_KEY | The key is missing, invalid, inactive, expired, from the other environment, or blocked by its IP allowlist. |
404 | card_not_found | No such card for this business. |
429 | api_key_rate_limit_exceeded | The per-minute limit for this key was exceeded. |
Authorizations
A secret API key, sent as Authorization: Bearer sk_test_… in sandbox or Bearer sk_live_… in live.
Body
application/json
- Option 1
- Option 2
A 26-character ULID.
Required string length:
26Pattern:
^[0-7][0-9A-HJKMNP-TV-Z]{25}$Example:
"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
A positive integer number of minor units.
Pattern:
^[1-9]\d*$Example:
"2500000"
A 26-character ULID.
Required string length:
26Pattern:
^[0-7][0-9A-HJKMNP-TV-Z]{25}$Example:
"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Response
Successful response.
A 26-character ULID.
Required string length:
26Pattern:
^[0-7][0-9A-HJKMNP-TV-Z]{25}$Example:
"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Available options:
funding, withdrawal Available options:
NGN, USD, GBP, EUR, GHS, ZAR, UGX, CAD, MXN, AED, BTC, ETH, USDT, USDC Available options:
NGN, USD, GBP, EUR, GHS, ZAR, UGX, CAD, MXN, AED, BTC, ETH, USDT, USDC An integer number of the asset’s smallest unit. Use this value for arithmetic.
Pattern:
^-?\d+$Example:
"2500000"
An integer number of the asset’s smallest unit. Use this value for arithmetic.
Pattern:
^-?\d+$Example:
"2500000"
Example:
"2026-08-14T09:00:00.000Z"