curl --request POST \
--url https://sandbox-api.sakawave.example/v1/transfer-destinations/recipients/{recipientId} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"paymentDetailId": "01K2Z3AB4Q5R6S7T8VWXYZABCD"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({paymentDetailId: '01K2Z3AB4Q5R6S7T8VWXYZABCD'})
};
fetch('https://sandbox-api.sakawave.example/v1/transfer-destinations/recipients/{recipientId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://sandbox-api.sakawave.example/v1/transfer-destinations/recipients/{recipientId}"
payload = { "paymentDetailId": "01K2Z3AB4Q5R6S7T8VWXYZABCD" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "01K2Z3AB4F5G6H7J8KMNPQRSTV",
"type": "external",
"currency": "NGN",
"displayName": "Treasury NGN",
"routeId": null,
"recipientId": "01K2Z3AB4G5H6J7K8MNPQRSTVW",
"institutionName": null,
"accountNumberLast4": null,
"expiresAt": "2026-08-15T09:05: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": 404,
"code": "resource_not_found",
"message": "Resource not found."
}{
"statusCode": 409,
"code": "resource_conflict",
"message": "The request conflicts with the current resource state."
}{
"statusCode": 422,
"message": "Payment detail validation failed.",
"code": "payment_detail_invalid",
"errors": [
{
"message": "Account number is required.",
"field": "accountNumber",
"code": "required",
"path": [
"fields",
"accountNumber"
]
}
]
}{
"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."
}Create a destination from a recipient
Resolves a saved recipient into a destination you can spend. Omit paymentDetailId to use the recipient’s default. Like every destination, the result expires after 24 hours and can be spent once.
curl --request POST \
--url https://sandbox-api.sakawave.example/v1/transfer-destinations/recipients/{recipientId} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"paymentDetailId": "01K2Z3AB4Q5R6S7T8VWXYZABCD"
}
'const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({paymentDetailId: '01K2Z3AB4Q5R6S7T8VWXYZABCD'})
};
fetch('https://sandbox-api.sakawave.example/v1/transfer-destinations/recipients/{recipientId}', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://sandbox-api.sakawave.example/v1/transfer-destinations/recipients/{recipientId}"
payload = { "paymentDetailId": "01K2Z3AB4Q5R6S7T8VWXYZABCD" }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text){
"id": "01K2Z3AB4F5G6H7J8KMNPQRSTV",
"type": "external",
"currency": "NGN",
"displayName": "Treasury NGN",
"routeId": null,
"recipientId": "01K2Z3AB4G5H6J7K8MNPQRSTVW",
"institutionName": null,
"accountNumberLast4": null,
"expiresAt": "2026-08-15T09:05: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": 404,
"code": "resource_not_found",
"message": "Resource not found."
}{
"statusCode": 409,
"code": "resource_conflict",
"message": "The request conflicts with the current resource state."
}{
"statusCode": 422,
"message": "Payment detail validation failed.",
"code": "payment_detail_invalid",
"errors": [
{
"message": "Account number is required.",
"field": "accountNumber",
"code": "required",
"path": [
"fields",
"accountNumber"
]
}
]
}{
"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."
}409 recipient_payment_detail_required. Add a detail first.
Legacy details
A payment detail whose routing values live at the provider rather than with Sakawave is markedproviderLocked. Sakawave will try to resolve it; when it cannot, you get 409 payment_detail_provider_locked and the fix is to delete that detail and add it again with its routing fields.
Error codes
| Status | code | Cause |
|---|---|---|
400 | validation_failed | An identifier is not a ULID. |
404 | payment_detail_not_found | The named detail does not belong to this recipient. |
409 | recipient_payment_detail_required | The recipient has no payment detail to use. |
409 | payment_detail_provider_locked | A legacy detail that must be re-entered before it can be used. |
409 | payment_detail_incomplete | The stored detail is missing routing data the route needs. |
409 | currency_capability_unavailable | The destination currency is closed for external transfers. |
409 | transfer_provider_unavailable | No provider is currently bound to the route. |
422 | recipient_profile_incomplete | The saved recipient is missing relationship, email, address.line1, address.city, or address.country. Every missing field is listed in errors. |
503 | provider_unavailable | The provider could not be reached. Retry shortly. |
401 | INVALID_API_KEY | The key is missing, invalid, inactive, expired, from the other environment, or blocked by its IP allowlist. |
404 | recipient_not_found | No such recipient for this business, or it has been deleted. |
429 | api_key_rate_limit_exceeded | The per-minute limit for this key was exceeded. All of these operations share a 120-per-minute allowance. |
Authorizations
A secret API key, sent as Authorization: Bearer sk_test_… in sandbox or Bearer sk_live_… in live.
Path Parameters
Saved recipient ULID. A 26-character ULID.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Body
Which saved detail to use. Defaults to the recipient’s default detail.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
Response
Successful response.
Send this as destinationId when previewing or creating a transfer.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
own_account, sakawave_tag, external NGN, USD, GBP, EUR, GHS, ZAR, UGX, CAD, MXN, AED, AUD, SGD, HKD, KES, TZS, BRL, CNY, XAF, PHP, USDC The verified account name for an external destination, the account name for your own, or the resolved holder for a tag.
The payout route, for external destinations.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
The saved recipient this was built from, when it was.
26^[0-7][0-9A-HJKMNP-TV-Z]{25}$"01K2Z3AB4C5D6E7F8G9HJKMNPQ"
The only part of the destination account number ever returned.
4"2026-08-14T09:00:00.000Z"