Close a card and get the money back

Taking money off a new card is Not yet available. See what each route returns today, and the order to follow once it is available.

Use this recipe to take the money off a card before you block it. For new cards, this is Not yet available. The page shows what each route returns today and the order to follow once it is available.

Who it is for. A consumer app whose user closes their account, or an expense platform whose employee leaves.

Status. Not yet available for new cards. Today both routes refuse new cards. Contact support to move money off a card.

Money example

Tunde's card holds 435.80 USD.

  1. You withdraw 435.80 USD from the card to the card account. The card now holds 0 USD.
  2. You block the card.
  3. You send the 435.80 USD onward with a normal transfer.

Today the API refuses step 1 on new cards, so his employer freezes the card and asks support to move the 435.80 USD.

Before you start

Prerequisite How to check
The card is active. You cannot withdraw from a frozen or blocked card GET /v1/cards/{card_id}
The amount on the card GET /v1/cards/{card_id}/balance

Withdraw before you block. If shared is true, the balance belongs to several cards, and withdrawing takes money the other cards can spend.

Find out what is required

The withdraw call tells you which route a card uses. For a card that withdraws by transfer, the call returns this card withdraws by transfer: send a transfer from the card to its account.

Steps

1. Read the balance

curl --request GET \
 --url https://api.gravv.xyz/v1/cards/e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e/balance \
 --header 'Api-Key: <API_KEY>'
const response = await fetch("https://api.gravv.xyz/v1/cards/e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e/balance", {
method: "GET",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
},
});
console.log(response.status, await response.json());
import os

import requests

response = requests.get(
"https://api.gravv.xyz/v1/cards/e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e/balance",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
},
)
print(response.status_code, response.json())

2. Try to withdraw

curl --request POST \
 --url https://api.gravv.xyz/v1/cards/withdraw \
 --header 'Api-Key: <API_KEY>' \
 --header 'Idempotency-Key: 7f2155fe-7a1e-4302-83d0-09464cf8bce9' \
 --header 'Content-Type: application/json' \
 --data '
{
"card_id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"amount": 435.8
}
'
const response = await fetch("https://api.gravv.xyz/v1/cards/withdraw", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Idempotency-Key": "7f2155fe-7a1e-4302-83d0-09464cf8bce9",
"Content-Type": "application/json",
},
body: JSON.stringify({
"card_id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"amount": 435.8
}),
});
console.log(response.status, await response.json());
import os

import requests

response = requests.post(
"https://api.gravv.xyz/v1/cards/withdraw",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
    "Idempotency-Key": "7f2155fe-7a1e-4302-83d0-09464cf8bce9",
    "Content-Type": "application/json",
},
json={
    "card_id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
    "amount": 435.8,
},
)
print(response.status_code, response.json())

amount is a JSON number in dollars with at most 2 decimals. The money can go only to the card's own card account. For a card that supports this route, the call returns {"data":{"tx_hash":"0x…"},"error":null}, and you then receive cards.transaction.withdraw. For new cards, the call returns this card withdraws by transfer: send a transfer from the card to its account.

3. The transfer route

Gravv designed new cards to withdraw with a transfer from the card to its own card account, with the cardholder as customer_id. Read the card account's account_id from GET /v1/cards?card_id=…. Today the API refuses this route too:

curl --request POST \
 --url https://api.gravv.xyz/v1/transfer \
 --header 'Api-Key: <API_KEY>' \
 --header 'Idempotency-Key: 0107108a-0aed-439b-9e28-41368831ad2e' \
 --header 'Content-Type: application/json' \
 --data '
{
"source": {
"source_type": "card",
"id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e"
},
"destination": {
"destination_type": "internal_account",
"id": "9b40be8d-9d74-4d2a-8897-dea3ef80ad23"
},
"amount": "435.80",
"customer_id": "c3a1f2e4-7b6d-4e8a-9c0b-1d2e3f4a5b6c",
"client_reference": "tunde-card-out-0001"
}
'
const response = await fetch("https://api.gravv.xyz/v1/transfer", {
method: "POST",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Idempotency-Key": "0107108a-0aed-439b-9e28-41368831ad2e",
"Content-Type": "application/json",
},
body: JSON.stringify({
"source": {
  "source_type": "card",
  "id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e"
},
"destination": {
  "destination_type": "internal_account",
  "id": "9b40be8d-9d74-4d2a-8897-dea3ef80ad23"
},
"amount": "435.80",
"customer_id": "c3a1f2e4-7b6d-4e8a-9c0b-1d2e3f4a5b6c",
"client_reference": "tunde-card-out-0001"
}),
});
console.log(response.status, await response.json());
import os

import requests

response = requests.post(
"https://api.gravv.xyz/v1/transfer",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
    "Idempotency-Key": "0107108a-0aed-439b-9e28-41368831ad2e",
    "Content-Type": "application/json",
},
json={
    "source": {
        "source_type": "card",
        "id": "e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
    },
    "destination": {
        "destination_type": "internal_account",
        "id": "9b40be8d-9d74-4d2a-8897-dea3ef80ad23",
    },
    "amount": "435.80",
    "customer_id": "c3a1f2e4-7b6d-4e8a-9c0b-1d2e3f4a5b6c",
    "client_reference": "tunde-card-out-0001",
},
)
print(response.status_code, response.json())

The call returns 400 the balance for this account could not be confirmed — please try again shortly. Do not retry it in a loop. Contact support instead.

4. Block the card

Block the card after the money is off it, or after support has moved the money:

curl --request PATCH \
 --url https://api.gravv.xyz/v1/cards/e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e/update \
 --header 'Api-Key: <API_KEY>' \
 --header 'Content-Type: application/json' \
 --data '
{
"status": "blocked",
"limit": 200
}
'
const response = await fetch("https://api.gravv.xyz/v1/cards/e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e/update", {
method: "PATCH",
headers: {
"Api-Key": process.env.GRAVV_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
"status": "blocked",
"limit": 200
}),
});
console.log(response.status, await response.json());
import os

import requests

response = requests.patch(
"https://api.gravv.xyz/v1/cards/e7b1c2d3-4f5a-4b6c-8d7e-9f0a1b2c3d4e/update",
headers={
    "Api-Key": os.environ["GRAVV_API_KEY"],
    "Content-Type": "application/json",
},
json={
    "status": "blocked",
    "limit": 200,
},
)
print(response.status_code, response.json())

What can go wrong

Response (HTTP 400) Why Fix
this card withdraws by transfer: send a transfer from the card to its account The card is a new card Not yet available. Contact support
the balance for this account could not be confirmed — please try again shortly You used the transfer route on a new card Not yet available. Contact support
cannot withdraw from a freezed or blocked card The card is frozen, blocked or inactive Unfreeze first. If blocked, contact support
the source card does not belong to this customer customer_id is not the cardholder Send the cardholder's id
a card can only be withdrawn to its own card account (destination_type internal_account) The destination is not the card's own card account Send the card's own account_id
insufficient balance The amount is above the card balance Read the balance again
amount must be greater than zero The amount is 0 or negative Send a positive amount
a withdrawal for this idempotency key is already in progress; retry shortly You reused the key while the first call was still running Wait, then retry
internal server error or the request could not be completed The call failed, or you sent amount as a string Read the balance and your webhooks before you retry. Then retry with a new key

Webhooks you will receive

When a withdrawal succeeds, you receive cards.transaction.withdraw with transaction.status and transaction.amount. When you block the card, you receive cards.status.update with blocked.

Testing in sandbox

Both routes refuse new sandbox cards today, so you can test the refusal branches only.

Reference

Was this page helpful?