---
title: "Pay a supplier abroad by SWIFT"
description: "Look up the supplier's bank, save a SWIFT payee with an optional intermediary bank, upload the invoice and send a USD payout."
---
Use this recipe to look up a supplier's bank, save a SWIFT payee with an optional intermediary bank, upload the invoice and send a USD payout.

**Who it is for.** A US importer that pays a textile supplier in Lagos for an invoiced shipment.

**Status.** Available to Nigeria (NG) from bank-held USD accounts. Not yet available to other countries, or from wallet-backed accounts. SWIFT payouts are in USD only.

## Money example

1. Acme Imports' bank-held USD account holds 20,000.00 USD.
2. Acme uploads invoice INV-7731 (a 180 KB PDF), then sends 12,000.00 USD to Adebayo Textiles Ltd's USD account at a Nigerian bank. In this example, the fee on its plan is 25.00 USD.
3. Gravv debits the account 12,025.00 USD. Banks along the way, such as an intermediary bank or the receiving bank, may take their own charges out of the 12,000.00 USD. Gravv's fee is the `fee` in the response.

## Before you start

- A **bank-held USD account** that is `active` and funded. Check it with [`GET /v2/accounts/{id}`](/api-reference/accounts-v2/get-v2-accounts-id). For a country other than Nigeria, contact support before you build the flow, because no API lists the countries open for SWIFT.
- The invoice or contract as a PDF, PNG or JPEG, **at most 700 KB** (in sandbox, under 500 KB).
- These details from the supplier:
  - The legal name on the account.
  - The account number, or the IBAN.
  - The SWIFT/BIC code.
  - The supplier's address.
  - The bank's address.
  - The intermediary (correspondent) bank's details, only if the supplier's bank gave you them.

## Find out what is required

### The bank and its address

Look the bank up by BIC. SWIFT banks appear only when you pass `swift_code`, `search` (3 characters or more) or `institution_type=swift`.

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/external-accounts/institutions?swift_code=ZEIBNGLA' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/external-accounts/institutions?swift_code=ZEIBNGLA", {
  method: "GET",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
  },
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.get(
    "https://api.gravv.xyz/v1/external-accounts/institutions?swift_code=ZEIBNGLA",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "items": [
      {
        "account_number_type": "bank_account_number",
        "address": "ZENITH HEIGHTS, PLOT 84 AJOSE ADEOGUN STREET, VICTORIA ISLAND, LAGOS",
        "country_iso_code": "NG",
        "currency": "USD",
        "id": "fc7b9fc8-d85c-5473-9b62-f88b9eb5815b",
        "max_amount": 0,
        "min_amount": 0,
        "name": "ZENITH BANK PLC",
        "prefill": {
          "bank_address_line_1": "87 AJOSE ADEOGUN STREET, ZENITH HEIGHTS",
          "bank_city": "VICTORIA ISLAND",
          "bank_country": "NG",
          "bank_name": "ZENITH BANK PLC",
          "bank_postal_code": "101241",
          "bank_state": "LAGOS",
          "bic": "ZEIBNGLA",
          "institution_id": "fc7b9fc8-d85c-5473-9b62-f88b9eb5815b"
        },
        "swift_code": "ZEIBNGLA"
      }
    ],
    "items_per_page": 10,
    "page": 1,
    "total_items": 1,
    "total_pages": 1
  },
  "error": null
}
```

Copy the fields in `prefill` into the payee. `address` is one display line. `min_amount` and `max_amount` are reference values, not limits. For a BIC that is not in the directory, the API returns HTTP 200 with an empty `items` list.

### Address forms

[`GET /v1/transfer/supported-countries-for-address?iso_code=NG`](/api-reference/transfer/get-v1-transfer-supported-countries-for-address) lists the country's states for `address.state` and `bank_state`. [`GET /v1/transfer/supported-countries`](/api-reference/transfer/get-v1-transfer-supported-countries) lists the countries a payee may be in. A listed country does not mean payouts to it are open.

### The SWIFT payee rules

- Set `payee_type` to `swift` and `currency` to `USD`, the only currency allowed.
- Send `bic`, which must be in the SWIFT directory, and `account_number`. The account number is **required even when you also send `iban`**.
- Set `account_owner_type` to `business` for a company or `individual` for a person.
- Send the beneficiary's `address` with `address_line1`, `city`, `state` and `country`.
- Send the bank's `bank_address_line_1`, `bank_city`, `bank_state`, `bank_postal_code` and `bank_country`. Gravv fills `bank_name` and `institution_id` from the BIC.
- An intermediary bank is optional. If you add one, send **all or none** of `intermediary_financial_institution`, `intermediary_bank_routing_number`, `intermediary_bank_address1`, `intermediary_bank_city`, `intermediary_bank_state`, `intermediary_bank_zip_code` and `intermediary_bank_country`. `intermediary_bank_address2` is optional.

### Whether a document is needed

Whether a SWIFT payout needs a document depends on a setting of your account at the bank, not on the payee. If one SWIFT payout from an account needed a document, expect all of them to need one. For business payments, always attach the invoice. If you send no document and one is needed, the transfer comes back `paused`. See [Pay a person abroad by SWIFT](/recipes/bank-payouts/pay-a-person-abroad-by-swift).

## Steps

### 1. Save the SWIFT payee

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/external-accounts \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: 53fe6ebb-1103-4731-915d-ea44ae648a0b' \
     --data '
{
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "payee_type": "swift",
  "currency": "USD",
  "account_name": "Adebayo Textiles Ltd",
  "account_number": "1012345678",
  "bic": "ZEIBNGLA",
  "account_owner_type": "business",
  "internal_account_id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
  "bank_address_line_1": "Plot 84 Ajose Adeogun Street",
  "bank_city": "Lagos",
  "bank_state": "Lagos",
  "bank_postal_code": "101241",
  "bank_country": "NG",
  "address": {
    "address_line1": "12 Broad Street",
    "city": "Lagos",
    "state": "Lagos",
    "postal_code": "102273",
    "country": "NG"
  },
  "intermediary_financial_institution": "Citibank N.A.",
  "intermediary_bank_routing_number": "021000089",
  "intermediary_bank_address1": "388 Greenwich Street",
  "intermediary_bank_city": "New York",
  "intermediary_bank_state": "NY",
  "intermediary_bank_zip_code": "10013",
  "intermediary_bank_country": "US"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/external-accounts", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "53fe6ebb-1103-4731-915d-ea44ae648a0b",
  },
  body: JSON.stringify({
    "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "payee_type": "swift",
    "currency": "USD",
    "account_name": "Adebayo Textiles Ltd",
    "account_number": "1012345678",
    "bic": "ZEIBNGLA",
    "account_owner_type": "business",
    "internal_account_id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
    "bank_address_line_1": "Plot 84 Ajose Adeogun Street",
    "bank_city": "Lagos",
    "bank_state": "Lagos",
    "bank_postal_code": "101241",
    "bank_country": "NG",
    "address": {
      "address_line1": "12 Broad Street",
      "city": "Lagos",
      "state": "Lagos",
      "postal_code": "102273",
      "country": "NG"
    },
    "intermediary_financial_institution": "Citibank N.A.",
    "intermediary_bank_routing_number": "021000089",
    "intermediary_bank_address1": "388 Greenwich Street",
    "intermediary_bank_city": "New York",
    "intermediary_bank_state": "NY",
    "intermediary_bank_zip_code": "10013",
    "intermediary_bank_country": "US"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/external-accounts",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "53fe6ebb-1103-4731-915d-ea44ae648a0b",
    },
    json={
        "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "payee_type": "swift",
        "currency": "USD",
        "account_name": "Adebayo Textiles Ltd",
        "account_number": "1012345678",
        "bic": "ZEIBNGLA",
        "account_owner_type": "business",
        "internal_account_id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
        "bank_address_line_1": "Plot 84 Ajose Adeogun Street",
        "bank_city": "Lagos",
        "bank_state": "Lagos",
        "bank_postal_code": "101241",
        "bank_country": "NG",
        "address": {
            "address_line1": "12 Broad Street",
            "city": "Lagos",
            "state": "Lagos",
            "postal_code": "102273",
            "country": "NG",
        },
        "intermediary_financial_institution": "Citibank N.A.",
        "intermediary_bank_routing_number": "021000089",
        "intermediary_bank_address1": "388 Greenwich Street",
        "intermediary_bank_city": "New York",
        "intermediary_bank_state": "NY",
        "intermediary_bank_zip_code": "10013",
        "intermediary_bank_country": "US",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response (HTTP 202)"
{
  "data": {
    "account_name": "ADEBAYO TEXTILES LTD",
    "account_number": "1012345678",
    "account_owner_type": "business",
    "address": {
      "address_line1": "12 Broad Street",
      "city": "Lagos",
      "country": "NG",
      "postal_code": "102273",
      "state": "Lagos"
    },
    "bic": "ZEIBNGLA",
    "currency": "USD",
    "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "date_created": "2026-10-13T22:39:41.973606Z",
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "institution_country_iso_code": "NG",
    "institution_id": "fc7b9fc8-d85c-5473-9b62-f88b9eb5815b",
    "institution_name": "ZENITH BANK PLC",
    "institution_type": "swift",
    "payee_type": "swift",
    "status": "pending"
  },
  "error": null
}
```

Gravv stores the bank and intermediary fields but does not return them.

### 2. Wait until the payee is active

A SWIFT payee can stay `pending` while the bank approves the receiving details, and there is no fixed time for this. Listen for `external_account.active` or `external_account.setup_failed`.

### 3. Upload the invoice

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer/documents \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: 2251e6f0-754e-4c1b-a3b9-bb02e9afdf82' \
     --data '
{
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "document_type": "invoice",
  "description": "Invoice INV-7731 cotton fabric",
  "file_name": "INV-7731.pdf",
  "content_type": "application/pdf",
  "content": "JVBERi0xLjQKJcfsj6IKMSAwIG9iago8PC9UeXBlL0NhdGFsb2c+PgplbmRvYmoK"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer/documents", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "2251e6f0-754e-4c1b-a3b9-bb02e9afdf82",
  },
  body: JSON.stringify({
    "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "document_type": "invoice",
    "description": "Invoice INV-7731 cotton fabric",
    "file_name": "INV-7731.pdf",
    "content_type": "application/pdf",
    "content": "JVBERi0xLjQKJcfsj6IKMSAwIG9iago8PC9UeXBlL0NhdGFsb2c+PgplbmRvYmoK"
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/transfer/documents",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "2251e6f0-754e-4c1b-a3b9-bb02e9afdf82",
    },
    json={
        "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "document_type": "invoice",
        "description": "Invoice INV-7731 cotton fabric",
        "file_name": "INV-7731.pdf",
        "content_type": "application/pdf",
        "content": "JVBERi0xLjQKJcfsj6IKMSAwIG9iago8PC9UeXBlL0NhdGFsb2c+PgplbmRvYmoK",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "content_type": "application/pdf",
    "created_at": "2026-10-13T22:39:57.665487Z",
    "description": "Invoice INV-7731 cotton fabric",
    "document_type": "invoice",
    "file_name": "INV-7731.pdf",
    "id": "5b0d8f3e-2a71-4c4f-9e0b-7d6c1a2b3c4d",
    "size_bytes": 184233
  },
  "error": null
}
```

Store `data.id`. [Upload a supporting document](/platform/transfers/paused-transfers#upload-a-supporting-document) lists the document types and rules. You can attach up to 5 documents to a transfer. Each document works for one transfer only. Gravv deletes an unused document after 7 days.

### 4. Send the SWIFT payout

Gravv sends `description` to the bank as the **purpose of payment**, so say what the money is for. Set `rail` to `swift`. The `wire` rail is a US domestic wire, and the API refuses it for this payee.

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Content-Type: application/json' \
     --header 'Idempotency-Key: bd2f2a31-ea82-4680-a06c-9e9e949e849b' \
     --data '
{
  "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "source": {
    "source_type": "internal_account",
    "id": "c56a4180-65aa-42ec-a945-5fd21dec0538"
  },
  "destination": {
    "destination_type": "external_account",
    "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "rail": "swift"
  },
  "amount": "12000.00",
  "description": "Payment for invoice INV-7731 cotton fabric",
  "client_reference": "inv-7731",
  "supporting_document_ids": [
    "5b0d8f3e-2a71-4c4f-9e0b-7d6c1a2b3c4d"
  ]
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Content-Type": "application/json",
    "Idempotency-Key": "bd2f2a31-ea82-4680-a06c-9e9e949e849b",
  },
  body: JSON.stringify({
    "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "source": {
      "source_type": "internal_account",
      "id": "c56a4180-65aa-42ec-a945-5fd21dec0538"
    },
    "destination": {
      "destination_type": "external_account",
      "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
      "rail": "swift"
    },
    "amount": "12000.00",
    "description": "Payment for invoice INV-7731 cotton fabric",
    "client_reference": "inv-7731",
    "supporting_document_ids": [
      "5b0d8f3e-2a71-4c4f-9e0b-7d6c1a2b3c4d"
    ]
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v1/transfer",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Content-Type": "application/json",
        "Idempotency-Key": "bd2f2a31-ea82-4680-a06c-9e9e949e849b",
    },
    json={
        "customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
        "source": {
            "source_type": "internal_account",
            "id": "c56a4180-65aa-42ec-a945-5fd21dec0538",
        },
        "destination": {
            "destination_type": "external_account",
            "id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
            "rail": "swift",
        },
        "amount": "12000.00",
        "description": "Payment for invoice INV-7731 cotton fabric",
        "client_reference": "inv-7731",
        "supporting_document_ids": [
            "5b0d8f3e-2a71-4c4f-9e0b-7d6c1a2b3c4d",
        ],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "amount": "12000",
    "amount_currency": "USD",
    "reference": "2d4f6a8c-0e1b-4d3f-a5c7-9e1b3d5f7a92",
    "date_created": "2026-10-13T23:01:12.410Z",
    "transfer_status": "pending",
    "transaction_id": "6f8a0c2e-4b6d-4f8a-9c1e-3b5d7f9a1c03",
    "local_amount": null,
    "local_currency": null,
    "fee": "25",
    "fee_currency": "USD",
    "requires_approval": false
  },
  "error": null
}
```

### 5. Follow it to the end

[`GET /v1/transactions/6f8a0c2e-4b6d-4f8a-9c1e-3b5d7f9a1c03`](/api-reference/transactions/get-v1-transactions-transaction-id) shows `transaction_type` `global_remittance` and `channel` `wire`. The webhooks carry `workflow_type` `swift_transfer`.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `bic ZEIBNGXX is not in the swift directory, so it cannot receive swift payouts: invalid input` | The BIC is unknown or mistyped | Look it up with `institutions?swift_code=` |
| 400 `BIC must be 8 or 11 characters` or `BIC country code must be 2 letters` | The BIC has the wrong format | Use the 8- or 11-character BIC |
| 400 `bic … is listed under …; name the bank_country or the institution_id: invalid input` | The BIC is listed in several countries | Send `bank_country` or `institution_id` |
| 400 `required but missing for swift payee: account_number, bank_postal_code: invalid input` | Fields are missing. The message lists all of them at once | Add them. `account_number` is needed even with `iban` |
| 400 `intermediary bank needs all of its fields; missing: intermediary_bank_zip_code: invalid input` | The intermediary bank fields are incomplete | Send all of the intermediary fields, or none |
| 400 `EUR not supported for swift external accounts: invalid input` | SWIFT payees are USD only | To pay euros, use [SEPA](/recipes/bank-payouts/pay-a-euro-account-by-sepa) |
| 400 `a minimum of $1 USD is required for SWIFT transfers` | The amount is below 1 USD | Send at least 1.00 USD |
| 400 `payee type "swift" does not accept ACH/Wire transfers` | The request sent `"rail": "wire"` | Use `"rail": "swift"` |
| 400 `transfers from this account to DE/USD are not supported at this time` | Payouts to that country are not open for your account | Contact support |
| 200 `pending`, then `transfer.status.failed` with `event_data.transfer_status` `manual_processing_required`, transaction still `pending` | Gravv accepted the transfer but holds it for a person to review, for example a SWIFT payout from a wallet-backed account. No money has left the account | Do not send it again. Contact support with the `reference` |
| 200 with `transfer_status` `paused` and `status` `awaiting_supporting_document` | Your account needs a document and you sent none | [Answer the pause](/recipes/bank-payouts/pay-a-person-abroad-by-swift) |
| 400 `supporting_document_ids: each id must be a document uploaded for this customer and not used by another transfer` | The document belongs to another customer, is already used, or is older than 7 days | Upload it again for this customer |
| 400 `supporting_document_ids: at most 5 documents per transfer` | The request has too many ids, or lists one twice | Send up to 5 different ids |
| 400 `content: the file is 812 KB (831488 bytes); the largest accepted is 700 KB (716800 bytes). …` | The file is over 700 KB | Compress it to 700 KB or less |
| 413 `the uploaded file is too large — the maximum is 10MB` | In sandbox, a request of about 600 KB or more, whatever the message says. In live, a file over about 10 MB | Compress it to under 500 KB in sandbox, or to 700 KB or less in live |
| 400 `content: the file is not a application/pdf` | `content_type` does not match the file | Send the file's real type |
| 400 `supporting documents cannot be checked right now; retry shortly` | The document store is briefly unavailable | Retry shortly with a **new** key. Gravv replays the 400 for 24 hours under the old key |
| `transfer.status.failed` and a refund after the bank refused the payment | The bank refused the payment for good, for example because the payee was saved against another account. Nothing was paid | Save the payee with `internal_account_id` set to the source, then send again |

## Webhooks you will receive

You receive `external_account.created`, then `external_account.active`, then several `transfer.status.pending` events, then `transfer.status.completed`. If a document is missing, you receive `transfer.requires_information`.

## Testing in sandbox

Sandbox simulates SWIFT payee setup. A business payee with an intermediary bank and an individual payee both become `active` within seconds. Uploads work as in live, except for size. Keep each file under 500 KB, because sandbox refuses requests from about 600 KB with a 413. The largest file that passed was 563 KB. You cannot force a document pause with a test value.

## Reference

- [List institutions](/api-reference/external-accounts/get-v1-external-accounts-institutions)
- [Add an external account](/api-reference/external-accounts/post-v1-external-accounts)
- [Upload a supporting document](/api-reference/transfer/post-v1-transfer-documents)
- [Create a transfer](/api-reference/transfer/post-v1-transfer)
