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

# Get started with recipients and destinations

> Save a recipient, give them routing, and turn them into a spendable destination.

Four calls: read the route, save who you are paying, save how to reach them, resolve a destination. The last one is what a transfer spends.

```bash theme={null}
export SAKAWAVE_API_KEY='sk_test_replace_with_your_key'
export SAKAWAVE_BASE_URL='https://sandbox-api.sakawave.example'
```

## 1. Find the route

Never guess the fields. Ask which routes serve the destination and what each one needs.

```bash theme={null}
curl --silent \
  --url "$SAKAWAVE_BASE_URL/v1/transfer-corridors?currency=KES&country=KE&paymentMethod=bank-transfer" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
| jq '.[0].route | {id, name, requiredFields, fieldDescriptors}'
```

Keep two things from the response: `route.id`, and the `fieldDescriptors` that tell you what to collect.

## 2. Save the recipient

Who you are paying. More is required here than you might expect — `relationship`, `email`, and a full `address` are all mandatory, because payout providers ask for them.

<CodeGroup>
  ```bash cURL theme={null}
  RECIPIENT=$(curl --silent --show-error --fail-with-body \
    --request POST \
    --url "$SAKAWAVE_BASE_URL/v1/transfer-recipients" \
    --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
    --header "Content-Type: application/json" \
    --data '{
      "type": "individual",
      "displayName": "Ada Okafor",
      "firstName": "Ada",
      "lastName": "Okafor",
      "relationship": "vendor",
      "email": "ada@example.com",
      "phoneNumber": "+2348012345678",
      "address": {
        "line1": "14 Marina Road",
        "city": "Nairobi",
        "stateOrProvince": "Nairobi",
        "postalCode": "00100",
        "country": "KE"
      }
    }')

  RECIPIENT_ID=$(printf '%s' "$RECIPIENT" | jq --raw-output '.id')
  ```

  ```ts Node.js theme={null}
  const response = await fetch(`${process.env.SAKAWAVE_BASE_URL}/v1/transfer-recipients`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SAKAWAVE_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      type: 'individual',
      displayName: 'Ada Okafor',
      firstName: 'Ada',
      lastName: 'Okafor',
      relationship: 'vendor',
      email: 'ada@example.com',
      address: {
        line1: '14 Marina Road',
        city: 'Nairobi',
        stateOrProvince: 'Nairobi',
        country: 'KE',
      },
    }),
  });

  const recipient = await response.json();
  if (!response.ok) throw new Error(`${response.status}: ${recipient.code} — ${recipient.message}`);
  ```
</CodeGroup>

`type` decides the name fields: `individual` needs `firstName` and `lastName`, `business` needs `legalName`.

## 3. Add routing

The keys inside `fields` come from the route's descriptors — not from a list you maintain.

```bash theme={null}
curl --request POST \
  --url "$SAKAWAVE_BASE_URL/v1/transfer-recipients/$RECIPIENT_ID/payment-details" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{
    \"routeId\": \"$ROUTE_ID\",
    \"currency\": \"KES\",
    \"country\": \"KE\",
    \"accountHolderName\": \"Ada Okafor\",
    \"fields\": { \"bankCode\": \"001\", \"accountNumber\": \"0123456789\" },
    \"isDefault\": true
  }"
```

The response masks what you sent:

```json theme={null}
{
  "id": "01K2Z3AB4Q5R6S7T8VWXYZABCD",
  "routeId": "01K2Z3AB4M5N6P7Q8RSTVWXYZA",
  "paymentMethod": "bank-transfer",
  "currency": "KES",
  "country": "KE",
  "accountHolderName": "Ada Okafor",
  "institutionName": "Example Bank",
  "maskedIdentifier": "•••• 6789",
  "isDefault": true,
  "providerLocked": false,
  "createdAt": "2026-08-14T09:06:00.000Z"
}
```

<Warning>
  `maskedIdentifier` is the only form the account number comes back in, ever. No endpoint returns the value you sent. If your UI needs to show it, store it yourself.
</Warning>

## 4. Resolve a destination

```bash theme={null}
curl --request POST \
  --url "$SAKAWAVE_BASE_URL/v1/transfer-destinations/recipients/$RECIPIENT_ID" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{}'
```

An empty body uses the recipient's default detail. The response is what a transfer spends:

```json theme={null}
{
  "id": "01K2Z3AB4F5G6H7J8KMNPQRSTV",
  "type": "external",
  "currency": "KES",
  "displayName": "ADA OKAFOR",
  "routeId": "01K2Z3AB4M5N6P7Q8RSTVWXYZA",
  "recipientId": "01K2Z3AB4G5H6J7K8MNPQRSTVW",
  "institutionName": "Example Bank",
  "accountNumberLast4": "6789",
  "expiresAt": "2026-08-15T09:05:00.000Z"
}
```

Two things to do with it:

1. **Show `displayName` to whoever is approving the payment.** On a route that supports verification, this is the name the bank holds — not the name you typed. A mismatch is the cheapest fraud check you will ever run.
2. **Spend it soon.** It expires in 24 hours and can be used once.

## Doing it in one call instead

For a one-off, skip the recipient entirely:

```bash theme={null}
curl --request POST \
  --url "$SAKAWAVE_BASE_URL/v1/transfer-destinations" \
  --header "Authorization: Bearer $SAKAWAVE_API_KEY" \
  --header "Content-Type: application/json" \
  --data "{
    \"type\": \"external\",
    \"routeId\": \"$ROUTE_ID\",
    \"currency\": \"KES\",
    \"country\": \"KE\",
    \"accountName\": \"Ada Okafor\",
    \"recipientRelationship\": \"vendor\",
    \"recipientEmail\": \"ada@example.com\",
    \"recipientAddressLine1\": \"14 Marina Road\",
    \"recipientCity\": \"Nairobi\",
    \"bankCode\": \"001\",
    \"accountNumber\": \"0123456789\"
  }"
```

Note the four `recipient*` fields. They are optional to the parser and required by the payout rail on international routes — leave them out and you get `422 recipient_profile_incomplete`. Routing fields go at the top level here, not inside `fields`.

## If it failed

| Status | `code` | What to do |
| - | - | - |
| `400` | `validation_failed` | An unknown field, or a name field that does not match `type`. |
| `404` | `transfer_route_not_found` | The `routeId` is not active for that currency. Re-read the corridors. |
| `404` | `destination_account_not_found` | For own-account or tag destinations, the target cannot receive. Check `status`, `canCredit`, `currency`, and `transferReady`. |
| `409` | `destination_field_required` | The route wants a field you did not send. The name is in `field`. |
| `409` | `recipient_payment_detail_required` | The recipient has no payment details yet. |
| `422` | `payment_detail_invalid` | A routing value is missing, malformed, or not accepted by the route. Every problem is in `errors`. |
| `422` | `recipient_profile_incomplete` | `relationship`, `email`, `address.line1`, `address.city`, or `address.country` is missing. |
| `503` | `provider_unavailable` | The provider could not be reached. Retry shortly. |

## Next

* [Routing fields and verification](/services/recipients/routing-fields-and-verification) — building `fields` correctly
* [Use cases](/services/recipients/use-cases) — payroll, one-offs, and rotating a bank account


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