---
title: "Open a wallet-backed USD account for a person"
description: "Open a USD account with US bank details for an individual, whose deposits arrive as USDC in the account's wallet."
---
Open a USD account with US bank details for an individual. Deposits to the account arrive as USDC in the account's wallet.

**API version:** v2.

**Who it is for.** This recipe is for a freelancer marketplace that pays UK and EU freelancers in dollars. It wants each freelancer's USD income to arrive as USDC that it can move at once.

**Status.** Available for individuals in the countries each option serves. `polygon` works on every option. `stellar`, `ethereum`, `avalanche_c_chain` and `solana` work where the option's `token_options` lists them. Gravv supports no other network.

## Money example

Ada, a UK freelancer, invoices a US client 1,200.00 USD.

1. The client sends 1,200.00 USD by ACH to Ada's account number and routing number.
2. The bank converts the payment to USDC and delivers it to the account's wallet on polygon.
3. Gravv books the full deposit and sends `transfer.inflow.deposit_settled`. If your plan has a deposit fee for that rail, Gravv bills it to your business separately.

## Before you start

- Ada's `basic` KYC is `completed`.
- Capabilities for her list a USD option with a non-empty `token_wallets`. See [Find what a customer can open](/recipes/accounts-v2/find-what-a-customer-can-open).
- She has not reached `max_accounts_per_customer`. Count her accounts with `GET /v2/accounts?customer_id=…&currency=USD`.

## Find out what is required

Read the requirements for the option's `bank_code`. The forms differ by option:

| | The default USD option | The USD, EUR and GBP option | The EUR and USD option |
| :-- | :-- | :-- | :-- |
| Root fields | `first_name`, `last_name`, `date_of_birth`, `email`, `phone_daytime`, `gender`, `citizenship_country`, `tax_residence_country`, `permanent_address` | Names, `date_of_birth`, `email`, `citizenship_country`, `permanent_address`, and `expected_activity` (`expected_avg_daily_balance`, `ach_in.monthly_count`, `ach_in.monthly_amount`) | Names, `date_of_birth`, `email`, `phone_daytime`, `citizenship_country`, `tax_residence_country`, `permanent_address`, `source_of_income`, `employment_status`, `work_industry`, `annual_deposit_range`, `transaction_frequency` |
| US citizens only | `tax_id_type` (`ssn`) and `tax_id_number` | `tax_id_type` (`ssn`) once a `tax_id_number` is sent | None |
| Non-US citizens only | None | `immigration_status`, `tax_residence_country`, and `international_supplement` (four yes/no questions, with details when one is true) | None |
| Documents | One identity document and `proof_of_address` | The same | The same |
| Networks that work | `polygon`, `ethereum`, `solana` | `polygon`, `ethereum`, `solana`, `stellar` | `polygon` |

`permanent_address` holds `address_line`, `city`, `state` (ISO 3166-2, such as `GB-LND`), `postal_code` and `country`. It must not be a P.O. Box. An identity document is one of `national_id`, `passport`, `drivers_license`, `permanent_resident_card`, `employment_authorization` or `other`, with `number`, `issued_date`, `expiration_date` and `issuing_country`. If the requirements response differs from this table, trust the requirements response.

## Steps

### 1. Open the account

<CodeGroup>
```bash title="cURL"
curl --request POST \
     --url https://api.gravv.xyz/v2/accounts \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: b6fb7a32-2a3e-4043-8255-ab239c331f1d' \
     --header 'Content-Type: application/json' \
     --data '
{
  "customer_id": "0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33",
  "customer_type": "individual",
  "currency": "USD",
  "account_type": "operating",
  "category": "sub_account",
  "bank_code": "<bank_code>",
  "network": "polygon",
  "label": "Ada USD",
  "data": {
    "root": {
      "first_name": "Ada",
      "last_name": "Okafor",
      "date_of_birth": "1991-03-14",
      "email": "ada.okafor@example.com",
      "phone_daytime": "+447911123456",
      "gender": "female",
      "citizenship_country": "GB",
      "tax_residence_country": "GB",
      "permanent_address": {
        "address_line": "12 Crown Street",
        "city": "London",
        "state": "GB-LND",
        "postal_code": "EC1A 1BB",
        "country": "GB"
      }
    },
    "documents": [
      {
        "document_type": "passport",
        "issuing_country": "GB",
        "number": "533401372",
        "issued_date": "2022-01-31",
        "expiration_date": "2032-01-30",
        "filename": "passport.jpg",
        "content": "<base64 of the passport photo page>"
      },
      {
        "document_type": "proof_of_address",
        "issuing_country": "GB",
        "filename": "utility-bill.pdf",
        "content": "<base64 of a recent utility bill>"
      }
    ]
  }
}
'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v2/accounts", {
  method: "POST",
  headers: {
    "Api-Key": process.env.GRAVV_API_KEY,
    "Idempotency-Key": "b6fb7a32-2a3e-4043-8255-ab239c331f1d",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "customer_id": "0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33",
    "customer_type": "individual",
    "currency": "USD",
    "account_type": "operating",
    "category": "sub_account",
    "bank_code": "<bank_code>",
    "network": "polygon",
    "label": "Ada USD",
    "data": {
      "root": {
        "first_name": "Ada",
        "last_name": "Okafor",
        "date_of_birth": "1991-03-14",
        "email": "ada.okafor@example.com",
        "phone_daytime": "+447911123456",
        "gender": "female",
        "citizenship_country": "GB",
        "tax_residence_country": "GB",
        "permanent_address": {
          "address_line": "12 Crown Street",
          "city": "London",
          "state": "GB-LND",
          "postal_code": "EC1A 1BB",
          "country": "GB"
        }
      },
      "documents": [
        {
          "document_type": "passport",
          "issuing_country": "GB",
          "number": "533401372",
          "issued_date": "2022-01-31",
          "expiration_date": "2032-01-30",
          "filename": "passport.jpg",
          "content": "<base64 of the passport photo page>"
        },
        {
          "document_type": "proof_of_address",
          "issuing_country": "GB",
          "filename": "utility-bill.pdf",
          "content": "<base64 of a recent utility bill>"
        }
      ]
    }
  }),
});
console.log(response.status, await response.json());
```
```python title="Python"
import os

import requests

response = requests.post(
    "https://api.gravv.xyz/v2/accounts",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
        "Idempotency-Key": "b6fb7a32-2a3e-4043-8255-ab239c331f1d",
        "Content-Type": "application/json",
    },
    json={
        "customer_id": "0b6f3c1e-2a4d-4c8e-9f10-7d2a6b5e4c33",
        "customer_type": "individual",
        "currency": "USD",
        "account_type": "operating",
        "category": "sub_account",
        "bank_code": "<bank_code>",
        "network": "polygon",
        "label": "Ada USD",
        "data": {
            "root": {
                "first_name": "Ada",
                "last_name": "Okafor",
                "date_of_birth": "1991-03-14",
                "email": "ada.okafor@example.com",
                "phone_daytime": "+447911123456",
                "gender": "female",
                "citizenship_country": "GB",
                "tax_residence_country": "GB",
                "permanent_address": {
                    "address_line": "12 Crown Street",
                    "city": "London",
                    "state": "GB-LND",
                    "postal_code": "EC1A 1BB",
                    "country": "GB",
                },
            },
            "documents": [
                {
                    "document_type": "passport",
                    "issuing_country": "GB",
                    "number": "533401372",
                    "issued_date": "2022-01-31",
                    "expiration_date": "2032-01-30",
                    "filename": "passport.jpg",
                    "content": "<base64 of the passport photo page>",
                },
                {
                    "document_type": "proof_of_address",
                    "issuing_country": "GB",
                    "filename": "utility-bill.pdf",
                    "content": "<base64 of a recent utility bill>",
                },
            ],
        },
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "account_id": "6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11",
    "status": "pending"
  },
  "error": null
}
```

Save `account_id`. The response has no `application_id`.

To back the account with a wallet you already hold, send `wallet_id` **instead of** `network`. The wallet's network must be one the option offers.

### 2. Follow it until it is active

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v2/accounts/6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11 \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v2/accounts/6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11", {
  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/v2/accounts/6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response while pending"
{
  "data": {
    "id": "6f1c2a9e-3b1d-4f7a-9c62-0d8e4b7a5c11",
    "status": "pending",
    "status_reason": "provider_review",
    "status_message": "The bank is reviewing this application. Nothing is outstanding from you or the customer.",
    "requires_user_action": false,
    "currency": "USD",
    "category": "sub_account",
    "blockchain_network": "polygon",
    "application_id": "5e7a9c1b-2d4f-4a6b-8c0d-e1f2a3b4c5d6"
  },
  "error": null
}
```

- If `requires_user_action` is `true`, go to [Unblock an application](/recipes/accounts-v2/unblock-an-application).
- If `status_reason` is `provider_review` or `provisioning_retrying`, you have nothing to do. Wait.
- If `status` is `failed` or `rejected`, read `status_reason`. See [what each one means](/platform/accounts/track-account-activation#what-each-status_reason-means).

Or wait for `applications.accounts.completed`. Poll every few seconds at first, then less often, because a bank review can take minutes or days.

### 3. Give the details to the payer

When `status` is `active`, read the account again and show the `virtual_account_bank_*` fields. See [Share account details and receive a deposit](/recipes/accounts-v2/share-account-details-and-receive-a-deposit).

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `the customer for the customer_id supplied has not completed KYC; …` (`CUSTOMER_KYC_INCOMPLETE`) | KYC is not completed. The API checks KYC first and reports nothing else until it passes | Finish KYC, then retry with a new key |
| 400 `no customer record exists for the customer_id supplied; …` (`CUSTOMER_NOT_FOUND`) | The id is wrong, or it is a sandbox id sent with a live key | Send the id from the same environment |
| 400 `network is required for account options that support wallet integration` | You sent no `network` or `wallet_id` | Send `network` |
| 400 `this account does not provide a token wallet on bitcoin; choose one of its supported networks (primary: polygon)` | The option does not offer that network | Send one from `token_options` |
| 400 `Network must be sent as a top-level request field; data.root.network is no longer accepted` | You sent `network` in `data.root` | Move it to the top level |
| 400 `invalid request payload: unknown field "blockchain_network"` | You used an old field name | Send `network` |
| 400 `wallet_id and network are mutually exclusive; send only one` | You sent both | Send only one |
| 400 `wallet_id does not reference a known wallet` | The wallet is not one of yours | Check with `GET /v1/wallets` |
| 400 `Date of Birth is required (and 1 other field; see details)` | Fields are missing. `details` lists all of them | Fill every key in `details`, then retry with a new key |
| 400 `details.documents`: `required: requires document proof_of_address` | A document is missing | Add it |
| 400 `Expiration Date is required for this document type` | An ID is missing its details | Add `number`, `issued_date`, `expiration_date`, `issuing_country` |
| 400 `document content too large: maximum size is 2MB; …` | A file is over 2 MB | Compress it |
| 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 | Keep the files of one request under 500 KB together |
| 400 `content must be base64 (standard or URL-safe)` | The content has a `data:` prefix or bad base64 | Send raw base64 |
| 400 `details.phone_daytime`: `invalid_format: must be a valid phone number for its country, …` | The number is not valid for its country | Send it in E.164 format |
| 400 `details.permanent_address.address_line`: `must be a physical address, not a P.O. Box` | The address is a P.O. Box | Send a street address |
| 400 `state "LND" is not an ISO-3166-2 subdivision; …` | The state has no country prefix | Send the full code, such as `GB-LND` |
| 400 `details.gender`: `invalid_enum: … expected one of: female, male` | These forms do not accept `other` | Ask the customer, or choose another option |
| 400 `parties are not accepted for an individual customer; …` | You sent `parties` for a person | Remove `parties` |
| 409 `this customer already holds the maximum number of USD accounts permitted for this account type (5)` | The customer reached the per-customer limit | [Open a second account](/recipes/accounts-v2/open-a-second-account) |
| 409 `customer already has an application in progress for this bank` | An earlier application at this bank is open | [Unblock that application](/recipes/accounts-v2/unblock-an-application) |
| 503 `Service temporarily unavailable. …`, 500, 504 | A temporary fault | Retry with the **same** key |
| Account read: `status_reason` `settlement_wallet_unavailable` | Gravv could not create the wallet on that network | Open a new account on another network |

## Webhooks you will receive

Gravv sends these events in order:

1. `applications.accounts.pending`. Gravv can skip this event.
2. Sometimes `applications.accounts.blocked`, with `metadata.block_reason`.
3. `applications.accounts.completed` with `account_id`, or `applications.accounts.failed` with `metadata.fail_reason`.

Gravv does not send `account.activated`. See [Account application events](/platform/webhooks/event-catalogue#account-applications).

## Testing in sandbox

Sandbox calls the bank's own test system, so activation is neither instant nor certain. Keep the documents of one request under 500 KB together, because sandbox refuses requests of about 600 KB or more. Sandbox KYC does not complete today, so the create call returns `CUSTOMER_KYC_INCOMPLETE` for now. Once an account is `active`, [simulate a deposit](/recipes/accounts-v2/test-accounts-with-a-simulated-deposit).

## Reference

- [Create an account](/api-reference/accounts-v2/post-v2-accounts)
- [Get an account](/api-reference/accounts-v2/get-v2-accounts-id)
- [Open an account](/platform/accounts/open-an-account)
- [Track activation](/platform/accounts/track-account-activation)
