---
updatedAt: 2026-08-27T22:50:07.000Z
agentTools:
  projectIndex: https://docs.zerohash.com/llms.txt
---

# Create a virtual account

Requests a virtual account for a participant. The account is created asynchronously: this endpoint returns `202 Accepted` with `status: PENDING` and no `bank_details`, and the account becomes `ACTIVE` once the banking partner issues it. Poll `GET /virtual_accounts/{id}` for the transition. Idempotency is keyed solely on the required `Idempotency-Key` header, which also becomes the account `id` — replaying the same key returns `200 OK` with the existing record, while a different key against the same participant creates a second, distinct account. An optional `account_label` routes every deposit into the account, and any auto-convert-and-withdraw payout, to that labeled account; it is set once at creation.

# OpenAPI definition

```json
{
  "openapi": "3.1.0",
  "info": {
    "title": "zerohash API",
    "description": "\n### Authentication\n\nzerohash Uses HMAC SHA-256 verification to ensure the authenticity of API requests, follow instructions by link [https://docs.zerohash.com/reference/api-authentication](https://docs.zerohash.com/reference/api-authentication)\n\n<a href=\"/zh-swagger.json\">Download zerohash OpenAPI Schema as JSON</a>\n",
    "version": "1.7.0"
  },
  "servers": [
    {
      "url": "https://api.cert.zerohash.com",
      "description": "Certification API server"
    }
  ],
  "x-readme": {
    "proxy-enabled": false
  },
  "paths": {
    "/virtual_accounts": {
      "post": {
        "tags": [
          "Virtual Accounts"
        ],
        "summary": "Create a virtual account",
        "description": "Requests a virtual account for a participant. The account is created asynchronously: this endpoint returns `202 Accepted` with `status: PENDING` and no `bank_details`, and the account becomes `ACTIVE` once the banking partner issues it. Poll `GET /virtual_accounts/{id}` for the transition. Idempotency is keyed solely on the required `Idempotency-Key` header, which also becomes the account `id` — replaying the same key returns `200 OK` with the existing record, while a different key against the same participant creates a second, distinct account. An optional `account_label` routes every deposit into the account, and any auto-convert-and-withdraw payout, to that labeled account; it is set once at creation.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Signature"
          },
          {
            "$ref": "#/components/parameters/Timestamp"
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "A [UUID v4](https://www.uuidtools.com/uuid-versions-explained#version-4) chosen by the caller. Becomes the virtual account `id`. Omitting it returns `400 MISSING_IDEMPOTENCY_KEY`; a non-UUIDv4 value returns `400 INVALID_IDEMPOTENCY_KEY`.",
            "schema": {
              "type": "string",
              "format": "uuid",
              "example": "550e8400-e29b-41d4-a716-446655440000"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateVirtualAccountRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The `Idempotency-Key` was already used; the existing record is returned unchanged.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VirtualAccount"
                }
              }
            }
          },
          "202": {
            "description": "Accepted. The virtual account was created and is `PENDING` issuance by the banking partner.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VirtualAccount"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Code400"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Code403"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Code404"
                }
              }
            }
          },
          "409": {
            "description": "The participant cannot be onboarded to the banking partner, for example because the underlying customer record is in a terminal state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Code400"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Code500"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Code503"
                }
              }
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "apiKey": [],
      "apiPassphrase": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "description": "Your API public key. See the [Authentication guide](https://docs.zerohash.com/reference/api-authentication) for how to obtain keys.",
        "in": "header",
        "name": "X-SCX-API-KEY"
      },
      "apiPassphrase": {
        "type": "apiKey",
        "description": "The passphrase associated with your API key, set when the key was created.",
        "in": "header",
        "name": "X-SCX-PASSPHRASE"
      }
    },
    "parameters": {
      "Signature": {
        "name": "X-SCX-SIGNED",
        "in": "header",
        "description": "HMAC-SHA256 signature of the request, base64-encoded. See the [Authentication guide](https://docs.zerohash.com/reference/api-authentication) for the exact signing formula.",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "Timestamp": {
        "name": "X-SCX-TIMESTAMP",
        "in": "header",
        "description": "Current Unix timestamp in seconds. Must be within 60 seconds of server time or the request is rejected.",
        "required": true,
        "schema": {
          "type": "string",
          "example": "1678901234"
        }
      }
    },
    "schemas": {
      "Code400": {
        "type": "object",
        "description": "Request was rejected by validation or a downstream service. `errors` is an array of human-readable messages; each entry describes a single validation failure or business-rule violation.",
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "example": [
              "body/amount must be >= 0",
              "body/asset is required"
            ]
          }
        }
      },
      "Code403": {
        "type": "object",
        "description": "Authentication or authorization failed. `error` is always `true`; `message` explains which check failed (missing API key, bad signature, insufficient permission, etc.).",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "boolean",
            "example": true
          },
          "message": {
            "type": "string",
            "example": "This api key does not have write permission to this endpoint"
          }
        }
      },
      "Code404": {
        "type": "object",
        "description": "The requested resource does not exist or is not visible to the caller.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "example": "Not Found"
          }
        }
      },
      "Code500": {
        "type": "object",
        "description": "An unexpected error occurred on the server. The request can be retried after a short delay, but the same inputs may reproduce the error.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "example": "Internal Server Error"
          }
        }
      },
      "Code503": {
        "type": "object",
        "description": "A downstream dependency was unavailable, timed out, or returned a retryable error. Safe to retry; the response carries a `zh-allow-retry: true` header.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "example": "Service Unavailable"
          }
        }
      },
      "VirtualAccount": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "The virtual account identifier, equal to the `Idempotency-Key` supplied on creation.",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "participant_code": {
            "type": "string",
            "example": "PART01"
          },
          "platform_code": {
            "type": "string",
            "example": "PLAT01"
          },
          "provider_type": {
            "type": "string",
            "enum": [
              "CRB"
            ],
            "nullable": true,
            "description": "Banking partner backing the account.",
            "example": "CRB"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "ACTIVE",
              "CLOSING",
              "CLOSED",
              "BLOCKED",
              "FAILED"
            ],
            "nullable": true,
            "description": "Lifecycle state. `PENDING` until the banking partner issues the account; `FAILED` when the banking partner declined to onboard the customer; `CLOSED`, `BLOCKED` and `FAILED` are terminal.",
            "example": "PENDING"
          },
          "currency": {
            "type": "string",
            "enum": [
              "USD"
            ],
            "example": "USD"
          },
          "virtual_account_name": {
            "type": "string",
            "nullable": true,
            "maxLength": 255,
            "description": "Platform-supplied label for the account, set at creation and immutable afterwards. When supplied it is used verbatim as the title on the banking partner’s own record of the account. `null` when it was not supplied.",
            "example": "Acme Holdings Operating Account"
          },
          "named_account": {
            "type": "boolean",
            "description": "Echoed back exactly as supplied at creation. Reserved for a future named-account feature; it drives no behaviour today, so do not branch on it.",
            "example": false
          },
          "account_label": {
            "type": "string",
            "maxLength": 40,
            "description": "Label of the account deposits into this virtual account are credited to, set at creation and immutable afterwards. `general` when none was supplied.",
            "example": "treasury"
          },
          "bank_details": {
            "$ref": "#/components/schemas/BankDetails"
          },
          "status_detail": {
            "$ref": "#/components/schemas/StatusDetail"
          },
          "settlement_policy": {
            "$ref": "#/components/schemas/SettlementPolicy"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-07-20T14:22:00.000Z"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-07-20T14:22:00.000Z"
          }
        }
      },
      "CreateVirtualAccountRequest": {
        "type": "object",
        "required": [
          "participant_code",
          "settlement_policy"
        ],
        "additionalProperties": false,
        "properties": {
          "participant_code": {
            "type": "string",
            "description": "The participant the virtual account is issued to. The platform is taken from the authenticated API key and must not be supplied.",
            "example": "PART01"
          },
          "settlement_policy": {
            "$ref": "#/components/schemas/SettlementPolicy",
            "description": "Required — choose how money settles once it lands in the account (no default):\n- `HOLD` — the USD you send settles as a USD balance in the virtual account.\n- `AUTO_CONVERT_AND_WITHDRAW` — the incoming USD is converted into the selected `asset` and sent to the `external_account_id` previously linked to the participant."
          },
          "provider_type": {
            "type": "string",
            "enum": [
              "CRB"
            ],
            "description": "Banking partner to back the account. Optional; defaults to `CRB`, the only supported value. Any other value is rejected with `400`.",
            "example": "CRB"
          },
          "virtual_account_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 255,
            "pattern": "^[\\t\\x20]*[\\x21-\\x7E][\\t\\x20-\\x7E]*$",
            "description": "Optional label for the account, used verbatim as the title on the banking partner’s own record. Omit it and the title is derived from the participant’s name as before. Set once at creation: a replay of the same `Idempotency-Key` returns the originally-stored name and does not update it. Must be 1-255 characters, each printable ASCII (U+0020-U+007E) or tab, and not only whitespace; anything else is rejected with 400.",
            "example": "Acme Holdings Operating Account"
          },
          "named_account": {
            "type": "boolean",
            "description": "Optional; defaults to `false`. Persisted and echoed back, but drives no behaviour today — reserved for a future named-account feature.",
            "example": false
          },
          "account_label": {
            "type": "string",
            "maxLength": 40,
            "description": "Optional. Routes deposits and AUTO_CONVERT_AND_WITHDRAW payouts to a named account instead of `general`. No leading/trailing special chars; max 40 chars. Invalid value returns `400`. Immutable.",
            "example": "treasury"
          }
        }
      },
      "BankDetails": {
        "type": "object",
        "nullable": true,
        "description": "Deposit instructions for the virtual account. Present only when `status` is `ACTIVE`, `CLOSING`, `CLOSED` or `BLOCKED`; `null` while the account is `PENDING`, when the banking partner has not yet issued the account, and always for `FAILED`.",
        "properties": {
          "bank_account_number": {
            "type": "string",
            "example": "0000000001"
          },
          "bank_routing_number": {
            "type": "string",
            "example": "123456789"
          },
          "provider_account_id": {
            "type": "string",
            "description": "The banking partner's own identifier for the account.",
            "example": "0000000001"
          },
          "bank_name": {
            "type": "string",
            "example": "Cross River Bank"
          }
        }
      },
      "StatusDetail": {
        "type": "object",
        "nullable": true,
        "description": "Supplementary context for a terminal failure. Populated when `status` is `FAILED` or `BLOCKED`; `null` otherwise. `code` is drawn from a closed, additive set — treat any value not listed in `enum` as a terminal, unclassified failure.",
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "VA_CREATION_DECLINED",
              "VA_CREATION_FAILED",
              "VA_BLOCKED"
            ],
            "example": "VA_CREATION_FAILED"
          },
          "message": {
            "type": "string",
            "example": "zerohash was unable to open this virtual account. Contact zerohash support to request a new account."
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "example": "2026-07-21T09:05:00.000Z"
          }
        }
      },
      "SettlementPolicy": {
        "nullable": true,
        "description": "Determines what happens to money once it lands in the virtual account. Discriminated union on `type` with two variants:\n- **`HOLD`** — dollars in, dollars held. The USD you send settles as a USD balance in the virtual account.\n- **`AUTO_CONVERT_AND_WITHDRAW`** — the incoming USD is converted into the selected `asset` and sent to the `external_account_id` previously linked to the participant.\n\nRequired at creation; there is no implicit default. May be `null` in responses for accounts created before settlement policies were introduced.",
        "oneOf": [
          {
            "title": "HOLD",
            "type": "object",
            "description": "Dollars in, dollars held — the USD you send settles as a USD balance in the virtual account.",
            "required": [
              "type"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "HOLD"
                ],
                "example": "HOLD"
              }
            }
          },
          {
            "title": "AUTO_CONVERT_AND_WITHDRAW",
            "type": "object",
            "description": "Incoming USD is automatically converted into the selected `asset` and sent to the `external_account_id` previously linked to the participant. Both fields are required.",
            "required": [
              "type",
              "asset",
              "external_account_id"
            ],
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "AUTO_CONVERT_AND_WITHDRAW"
                ],
                "example": "AUTO_CONVERT_AND_WITHDRAW"
              },
              "asset": {
                "type": "string",
                "minLength": 1,
                "description": "Crypto asset the USD balance is converted into before withdrawal.",
                "example": "USDC"
              },
              "external_account_id": {
                "type": "string",
                "format": "uuid",
                "description": "External crypto account the converted funds are sent to. Must already be linked to the participant.",
                "example": "550e8400-e29b-41d4-a716-446655440000"
              }
            }
          }
        ]
      }
    }
  }
}
```