---
title: "Top up a card"
description: "Move money onto a card from a wallet-backed account or a USDC wallet on the card's network, and confirm that it arrived."
---
This recipe moves money onto a card from a wallet-backed account or a USDC wallet on the card's network, and confirms that the money arrived.

**Who it is for.** Any card issuer that moves money onto a card. For example, a consumer app lets Ada move part of her USD balance to her card.

**Status.** Available.

## Money example

Ada's USD account holds 400 USD. Her card holds 0.

1. You send 150 USD from her account to her card. Her account now holds 400 USD minus 150 USD minus any fee, and her card holds 150 USD.
2. Separately, a business tops up an employee's card from its USDC wallet. The wallet sends 100 USDC, and the card gains 100 USD.

## Before you start

| Prerequisite | How to check |
| :-- | :-- |
| The card is not frozen or blocked | `GET /v1/cards/{card_id}` returns `status` `active` |
| The source is an account **with a wallet**, or a wallet holding USDC | `GET /v2/accounts/{id}` has `wallet_address` and `blockchain_network`; `GET /v1/wallets/{wallet_id}` has `network` and a USDC balance |
| **The source and the card are on the same network** | `GET /v1/cards?card_id=…` returns the card's `blockchain_network` |
| The source covers the amount and any fee | The account's `balance`, or the wallet's USDC `balance` |

A bank-held account, with no wallet, cannot fund cards.

## Find out what is required

Use the reads in the table above. Gravv has no separate funding endpoint. To fund a card, send a [transfer](/api-reference/transfer/post-v1-transfer) whose destination is the card.

## Steps

### 1. Send the transfer

From an account:

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: f802ce3c-4736-4ccb-9e51-965b36952c96' \
     --header 'Content-Type: application/json' \
     --data '
{
  "source": {
    "source_type": "internal_account",
    "id": "6f1c2b8e-9a43-4f0e-8d35-2b7f0f6c1a90"
  },
  "destination": {
    "destination_type": "card",
    "id": "a14ec356-f33d-4384-ba68-4d9bfa19765b"
  },
  "amount": "150",
  "customer_id": "5b0f6c1e-2a8f-4c39-9d0e-0c7e3b9a1f42",
  "client_reference": "ada-card-topup-0001",
  "description": "Card top-up"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "f802ce3c-4736-4ccb-9e51-965b36952c96",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "source": {
      "source_type": "internal_account",
      "id": "6f1c2b8e-9a43-4f0e-8d35-2b7f0f6c1a90"
    },
    "destination": {
      "destination_type": "card",
      "id": "a14ec356-f33d-4384-ba68-4d9bfa19765b"
    },
    "amount": "150",
    "customer_id": "5b0f6c1e-2a8f-4c39-9d0e-0c7e3b9a1f42",
    "client_reference": "ada-card-topup-0001",
    "description": "Card top-up"
  }),
});
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"],
        "Idempotency-Key": "f802ce3c-4736-4ccb-9e51-965b36952c96",
        "Content-Type": "application/json",
    },
    json={
        "source": {
            "source_type": "internal_account",
            "id": "6f1c2b8e-9a43-4f0e-8d35-2b7f0f6c1a90",
        },
        "destination": {
            "destination_type": "card",
            "id": "a14ec356-f33d-4384-ba68-4d9bfa19765b",
        },
        "amount": "150",
        "customer_id": "5b0f6c1e-2a8f-4c39-9d0e-0c7e3b9a1f42",
        "client_reference": "ada-card-topup-0001",
        "description": "Card top-up",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

From a USDC wallet:

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v1/transfer \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: 7911f412-3ddf-4b08-bcbb-07f68ce313ad' \
     --header 'Content-Type: application/json' \
     --data '
{
  "source": {
    "source_type": "internal_crypto_wallet",
    "id": "67e169c0-547f-410d-8e18-f616b6822d70",
    "crypto_asset": "USDC"
  },
  "destination": {
    "destination_type": "card",
    "id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e"
  },
  "amount": "100",
  "customer_id": "0d6c2f4e-6a1b-4f0e-9b8f-5c2a7d3e1f00",
  "client_reference": "tunde-topup-0001"
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "7911f412-3ddf-4b08-bcbb-07f68ce313ad",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "source": {
      "source_type": "internal_crypto_wallet",
      "id": "67e169c0-547f-410d-8e18-f616b6822d70",
      "crypto_asset": "USDC"
    },
    "destination": {
      "destination_type": "card",
      "id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e"
    },
    "amount": "100",
    "customer_id": "0d6c2f4e-6a1b-4f0e-9b8f-5c2a7d3e1f00",
    "client_reference": "tunde-topup-0001"
  }),
});
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"],
        "Idempotency-Key": "7911f412-3ddf-4b08-bcbb-07f68ce313ad",
        "Content-Type": "application/json",
    },
    json={
        "source": {
            "source_type": "internal_crypto_wallet",
            "id": "67e169c0-547f-410d-8e18-f616b6822d70",
            "crypto_asset": "USDC",
        },
        "destination": {
            "destination_type": "card",
            "id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
        },
        "amount": "100",
        "customer_id": "0d6c2f4e-6a1b-4f0e-9b8f-5c2a7d3e1f00",
        "client_reference": "tunde-topup-0001",
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "amount": "150",
    "amount_currency": "USD",
    "fee": "0",
    "fee_currency": "USD",
    "reference": "3f0c6a8e-6b1d-4c1e-9a55-0d2f7b9e4a11",
    "requires_approval": false,
    "transaction_id": "8d4e2b6c-1f3a-4e7b-b0c9-5a6d7e8f9a01",
    "transfer_status": "pending"
  },
  "error": null
}
```

- `amount` is a **string** in dollars, with at most 2 decimals (`"150"`, `"12.50"`).
- `transfer_status` `pending` is normal. `paused` means your limits held the transfer. See [Stay within transfer limits](/recipes/bank-payouts/stay-within-transfer-limits).

### 2. Wait for the transfer to complete

Poll the transaction until `status` is `completed` or `failed`. In sandbox, the transfer takes about 75 seconds to complete.

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/transactions/8d4e2b6c-1f3a-4e7b-b0c9-5a6d7e8f9a01 \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/transactions/8d4e2b6c-1f3a-4e7b-b0c9-5a6d7e8f9a01", {
  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/transactions/8d4e2b6c-1f3a-4e7b-b0c9-5a6d7e8f9a01",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

A completed funding has `transaction_type` `card_funding`, `receiving_account_id` set to the card id, and a `tx_hash`. Poll rather than wait for webhooks, because Gravv does not guarantee the completion events for a funding.

### 3. Confirm the balance

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance", {
  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/cards/a14ec356-f33d-4384-ba68-4d9bfa19765b/balance",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "balance": 150,
    "card_group_id": "3f1d0c5e-2a8b-4b7e-9f3a-6c1e2d4b5a70",
    "currency": "USD",
    "group_balance": 150,
    "shared": false
  },
  "error": null
}
```

## What can go wrong

| Response (HTTP 400) | Why | Fix |
| :-- | :-- | :-- |
| `this card is freeze and cannot be funded` / `this card is blocked and cannot be funded` | The card is frozen or blocked | [Unfreeze it](/recipes/cards/freeze-or-block-a-lost-card). You cannot use a blocked card again |
| `no card account found for id <card> (customer <id>)` | The card id is wrong | Check it with `GET /v1/cards?card_id=…` |
| `the destination card has no wallet provisioned yet` | The card is not issued yet | Issue the card first |
| `the source account (ethereum network) and destination (polygon network) must be on the same blockchain network` | The source is on another network | Use a source on the card's network |
| `this account can only send to a bank account or to another account held at the same bank` | The source is a bank-held account | Use a wallet-backed account or a USDC wallet |
| `insufficient balance` | The amount plus the fee is above the source balance | Lower the amount or fund the source |
| `the source account could not be verified for this transfer — please try again shortly` | A temporary check failed | Retry once with a new key. If it repeats, contact support |
| `amount must have at most 2 decimal places` | The amount has too many decimals | Use at most 2 decimals |
| `source_type must be one of: internal_account, internal_crypto_wallet, card` | You sent `crypto_wallet` as a source | Use `internal_crypto_wallet` |
| 200 with `transfer_status` `paused` | A limit paused the transfer | Respond to the pause as in [Stay within transfer limits](/recipes/bank-payouts/stay-within-transfer-limits) |

Never retry a 400 with the same key. Use a new one.

## Webhooks you will receive

Gravv does not guarantee these events, and they do not arrive in a fixed order.

1. `transfer.status.pending`, one or more, with `workflow_type` `card_funding`.
2. `transfer.status.completed`, with `amount`, `tx_hash` and `destination_id`.
3. `cards.transaction.funding`, with `transaction.amount` (a string) and `transaction.status`. Its `client_reference` can be empty, so match on `tx_hash`.
4. On failure, `transfer.status.failed`.

## Testing in sandbox

- Sandbox cards are on `polygon`, so use a source on `polygon`.
- To put money in a source, [simulate a deposit](/recipes/accounts-v2/test-accounts-with-a-simulated-deposit) into a wallet-backed v2 account. Today only NGN accounts open in sandbox.
- In sandbox the card balance follows its wallet, so the balance shows the money once the transfer is `completed`.
- In sandbox, a card funding transfer sends only `transfer.status.pending` events, so poll the transaction.

## Reference

- [Fund cards](/platform/cards/fund-cards)
- [Create a transfer](/api-reference/transfer/post-v1-transfer)
- [Get a transaction](/api-reference/transactions/get-v1-transactions-transaction-id)
