---
title: "Open an account"
description: "Open a v2 account with one POST /v2/accounts, and read the errors it can return."
---
**API version:** v2.

[`POST /v2/accounts`](/api-reference/accounts-v2/post-v2-accounts) opens an account at the option you chose, with the data its [requirements form](/platform/accounts/read-account-requirements) asks for. There is no draft, submit or process step. The one call starts the bank's review.

**A money example.** Ada's account for her 1,200.00 USD invoice is a wallet-backed USD account on polygon. You send one request with her details, her passport and a utility bill. The response has `status` `pending`. A few minutes or days later the bank approves the account, and her client can pay.

## Before you start

- The customer's `basic` KYC is `completed`. The API checks this first. Until it passes, the API reports no other error.
- You have an option from [capabilities](/platform/accounts/find-an-account-option) and its form from [requirements](/platform/accounts/read-account-requirements).
- The customer has not reached `max_accounts_per_customer` at that option. To count the customer's accounts, use `GET /v2/accounts?customer_id=…`.

## Send the request

```bash title="Open a wallet-backed USD account"
curl --request POST \
     --url https://api.gravv.xyz/v2/accounts \
     --header 'Api-Key: <API_KEY>' \
     --header 'Idempotency-Key: 5eedf850-57d4-4d57-ad69-6f0593c45c2b' \
     --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>"
      }
    ]
  }
}
'
```

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

Save `account_id`. The response has no `application_id`, so read it from [`GET /v2/accounts/{id}`](/platform/accounts/track-account-activation).

## What to send

- For a wallet-backed option (non-empty `token_wallets`), send `network` from the option's `token_options`. Instead of `network`, you can send the `wallet_id` of one of your wallets on one of those networks. Never send both.
- For a fiat-only option, send neither `network` nor `wallet_id`.
- For a customer's account, send `category` `sub_account`. For NGN, always send `category`.
- Send `customer_type`. In `data.root`, send `citizenship_country` for a person or `incorporation_country` for a business.
- For a business, send `data.parties[]`. Name each person from the customer record by `id`, and add the facts the record lacks (ownership, control, signer, documents). If you leave `parties` out, the API declares every associated person without those facts, and the request fails validation on them.
- Identity documents carry `number`, `issued_date`, `expiration_date` and `issuing_country`. `content` is raw base64, not a `data:` URI.
- Each document is at most 2 MB. In sandbox, keep the files in one request under 500 KB in total. Sandbox refuses requests from about 600 KB with a 413.
- Gravv does not deduplicate a request body over 1 MiB under its `Idempotency-Key`. Documents of about 770 KB or more make a body that large. A retry of such a request runs again instead of returning the first response. After a timeout, list the customer's accounts before you send it again.

## Errors

Every 400 lists **all** the problems it found in `details`, keyed by field. Fix them all, then send the request again with a **new** `Idempotency-Key`. Gravv stores a 4xx response under its key for 24 hours and replays it if you reuse the key.

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `the customer for the customer_id supplied has not completed KYC; …` (`CUSTOMER_KYC_INCOMPLETE`) | `basic` KYC is not completed | Finish KYC, then send again with a new key |
| 400 `no customer record exists for the customer_id supplied; …` (`CUSTOMER_NOT_FOUND`) | Wrong id, or an id from the other environment | Use the id from `POST /v1/customers` in the same environment |
| 400 `Date of Birth is required (and 1 other field; see details)` | Missing fields, each named in `details` | Fill every key in `details` |
| 400 `network is required for account options that support wallet integration` | Wallet-backed option without `network` or `wallet_id` | Send `network` |
| 400 `network is only accepted for account options that support wallet integration` | `network` sent to a fiat-only option | Remove it |
| 400 `this account does not provide a token wallet on bitcoin; …` (`SELECTION_CONFLICT`) | The option does not offer that network | Use one of its `token_options` |
| 400 `Network must be sent as a top-level request field; data.root.network is no longer accepted` | `network` inside `data.root` | Move it to the top level |
| 400 `invalid request payload: unknown field "blockchain_network"` | An old field name | Send `network` |
| 400 `wallet_id and network are mutually exclusive; send only one` | Both sent | Send one |
| 400 `bank_code=<code> does not open accounts for customers resident in XX. …` (`COUNTRY_NOT_SERVED`) | The option does not serve that country | Choose another option |
| 400 `bank_code=<code> does not offer currency=GBP; …` (`CURRENCY_NOT_SUPPORTED`) | The option does not offer that currency | Choose another option |
| 400 `details.documents`: `required: requires document proof_of_address` | A required document is missing | Add it to `data.documents` |
| 400 `document content too large: maximum size is 2MB; …` | A file over 2 MB | Compress it |
| 400 `unsupported file type "image/heic"; allowed types are JPEG, PNG, and PDF` | Wrong format | Convert it |
| 400 `content must be base64 (standard or URL-safe)` | A `data:` prefix or bad base64 | Send raw base64 |
| 400 `details.gender`: `invalid_enum: … expected one of: female, male` | These forms do not accept `other` | Ask the customer, or choose another option |
| 400 `state "LND" is not an ISO-3166-2 subdivision; …` | A bare state code | Send the full code, such as `GB-LND` or `US-CA` |
| 400 `parties are not accepted for an individual customer; …` | `parties` on an individual | Remove `parties` |
| 400 `customer_type=business does not match this customer's registered profile, which is individual. …` | Wrong `customer_type` | Send the customer's type |
| 400 `this customer already has an existing reference at the resolved bank; root-level fields are no longer accepted for it …` | The bank already knows this customer | Send no `data.root` |
| 409 `this customer already holds the maximum number of USD accounts permitted for this account type (5)` | The per-customer limit | Reuse an account, or ask support to raise the limit |
| 409 `customer already has an application in progress for this bank` | An earlier application at this bank is open | [Fix that application](/platform/accounts/fix-a-blocked-application) |
| 409 `this account provider is currently disabled for this account` | The option is switched off for your business or this customer | Choose another option, or contact support |
| 422 `idempotency_key_reused` | The same key with a different body | Use a new key for each different request |
| 503 `Service temporarily unavailable. …`, 500, 504 | A temporary fault | Retry with the **same** key. Gravv does not open a second account |

## Webhooks

Gravv sends `applications.accounts.pending`, then possibly `applications.accounts.blocked`, then `applications.accounts.completed` or `applications.accounts.failed`. Gravv never sends `account.activated` for a v2 account you open. See [Track activation](/platform/accounts/track-account-activation).
