> ## Documentation Index
> Fetch the complete documentation index at: https://apidocs.hopnow.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload Document

> Upload evidence that satisfies one outstanding document requirement on a virtual account group

This endpoint works on any account you own. [Create Virtual Account Group](/fx/api-reference/virtual-account-groups/create-group) covers who can open a group and what the account needs first.

The onboarding opened by [Create Virtual Account Group](/fx/api-reference/virtual-account-groups/create-group) asks for evidence one requirement at a time. [Get Virtual Account Group](/fx/api-reference/virtual-account-groups/get-group) lists those outstanding requirements, each with a `key` and the document types it accepts; this endpoint uploads a file against exactly one of them, using `requirement_key` to say which. A `requirement_key` that is not currently open on this group is rejected.

The file is carried inside the JSON body as a base64 data URI, so there is no multipart upload. Hop stores it as account evidence, links it to the requirement, and sends it to the provider in the same request. A successful response always reports `submission_status` as `submitted`.

Accept every agreement via [Submit Agreement Actions](/fx/api-reference/virtual-account-groups/submit-agreement-actions) and clear every document requirement to enable the group, then [create virtual accounts](/fx/api-reference/virtual-accounts/create-virtual-account) under it.

Requires an API key with `write` access on the `virtual_accounts` scope.

## Path Parameters

<ParamField path="account_id" type="string" required>
  The account's external ID (starts with `acct_`)
</ParamField>

<ParamField path="group_id" type="string" required>
  The virtual account group's external ID (starts with `vag_`). It must belong to the account in the path.
</ParamField>

## Headers

<ParamField header="Idempotency-Key" type="string" required>
  Canonical lowercase 8-4-4-4-12 UUID. Scoped to the group, with the payload fingerprinted including the file's bytes: replaying the same key with the same payload returns the stored document with `200`, and with a different payload returns `409`. See [Idempotency](/fx/api-reference/idempotency).
</ParamField>

## Request Body

Unknown fields are rejected.

<ParamField body="requirement_key" type="string" required>
  The `key` of the open document requirement this evidence satisfies, copied from [Get Virtual Account Group](/fx/api-reference/virtual-account-groups/get-group) (1-200 characters). A Hop-normalised key, never a provider code.
</ParamField>

<ParamField body="type" type="string" required>
  Document type for this upload. Do not treat the document types below as a checklist. Required documents are returned in `requirements` by the [Get Virtual Account Group](/fx/api-reference/virtual-account-groups/get-group) endpoint. For each requirement where `type` is `document`, copy its `key` to `requirement_key` and choose one of its `accepted_document_types`. Types listed for the same requirement are alternatives or substitutes, not all are required. If multiple document requirements are returned, each must be satisfied separately. The list below shows all document types supported by the endpoint.

  Identity: `passport`, `national_id`, `drivers_license`, `residence_permit`, `selfie`.

  Company: `certificate_of_incorporation`, `articles_of_association`, `memorandum_of_association`, `constitution`, `by_laws`, `operating_agreement`, `regulatory_license`, `certificate_of_good_standing`, `register_of_directors`, `register_of_shareholders`, `organization_chart`, `corporate_structure`.

  Other evidence: `proof_of_address`, `bank_statement`, `board_resolution`, `source_of_funds`, `source_of_wealth`, `aml_policy`, `proof_of_directorship`, `other`.
</ParamField>

<ParamField body="file" type="string" required>
  The file as a base64 data URI: `data:<content-type>;base64,<base64 payload>`, with a valid, non-empty base64 payload. The content type inside the URI must be `application/pdf`, `image/jpeg` or `image/png`.
</ParamField>

<ParamField body="side" type="string">
  `front` or `back`. Required for a two-sided identity document, `national_id`, `drivers_license` and `residence_permit`, and rejected for every other type. Upload each side as its own request.
</ParamField>

<ParamField body="file_name" type="string">
  Original file name to store with the evidence (1-255 characters), or omit it
</ParamField>

<ParamField body="country_code" type="string">
  Country the evidence was issued in, as an ISO 3166-1 alpha-2 code, for example `GB`
</ParamField>

This route accepts a request body of up to **21 MB** (22,020,096 bytes), instead of the 1 MB that applies everywhere else on this API. Base64 encoding adds about a third, so the practical ceiling is a file of roughly 15 MB. An oversized body is rejected with `413` `REQUEST_TOO_LARGE` and `details.max_bytes` carries the limit.

## Response

Returns `201` with the stored document on first upload, or `200` with the same document when the `Idempotency-Key` was already used with the same payload.

<ResponseField name="id" type="string">
  Document identifier (starts with `doc_`). Evidence is stored at account level, so the same document id can appear under more than one group.
</ResponseField>

<ResponseField name="object" type="string">
  Always returns `"virtual_account_group_document"`
</ResponseField>

<ResponseField name="account_id" type="string">
  Account the evidence belongs to (starts with `acct_`)
</ResponseField>

<ResponseField name="virtual_account_group_id" type="string">
  Virtual account group the evidence was linked to (starts with `vag_`)
</ResponseField>

<ResponseField name="requirement_key" type="string">
  The requirement this evidence satisfies, echoing the request
</ResponseField>

<ResponseField name="type" type="string">
  Document type, echoing the request
</ResponseField>

<ResponseField name="side" type="string">
  `front` or `back` for a two-sided identity document, otherwise `null`
</ResponseField>

<ResponseField name="file_name" type="string">
  Stored file name, or `null` when none was sent
</ResponseField>

<ResponseField name="content_type" type="string">
  Media type taken from the data URI: `application/pdf`, `image/jpeg` or `image/png`
</ResponseField>

<ResponseField name="size_bytes" type="integer">
  Size of the decoded file in bytes
</ResponseField>

<ResponseField name="submission_status" type="string">
  Delivery state towards the provider. Always `submitted` on a successful response, since delivery happens before the response is returned. The other values, `pending`, `failed` and `unknown`, are visible in [List Documents](/fx/api-reference/virtual-account-groups/list-documents).
</ResponseField>

<ResponseField name="country_code" type="string">
  ISO 3166-1 alpha-2 country of issue, or `null`
</ResponseField>

<ResponseField name="created" type="string">
  ISO 8601 timestamp when created
</ResponseField>

<ResponseField name="updated" type="string">
  ISO 8601 timestamp when last updated
</ResponseField>

<ResponseField name="created_by" type="string">
  ID of the API key that uploaded the document (starts with `ak_`)
</ResponseField>

<ResponseField name="updated_by" type="string">
  Actor that last updated the document, or `null`
</ResponseField>

### When delivery to the provider fails

The file is stored and linked before it is sent on. If the provider then rejects the delivery, the request fails with `502` and no document is returned, but the evidence is kept and its link is marked `failed`. Retry with the **same** `Idempotency-Key` and the same payload: Hop finds the stored evidence and re-attempts delivery instead of creating a second document. A fresh key on the same file uploads it again.

## Errors

| Status | `code` | When |
| - | - | - |
| `404` | `RESOURCE_NOT_FOUND` | `group_id` is unknown or belongs to another account |
| `409` | `BUSINESS_RULE_VIOLATION` | The `Idempotency-Key` was already used on this group with a different document payload |
| `413` | `REQUEST_TOO_LARGE` | The body is over 21 MB; `details.max_bytes` carries the limit |
| `422` | `VALIDATION_ERROR` | `Idempotency-Key` is missing or not a canonical 8-4-4-4-12 UUID |
| `422` | `VALIDATION_ERROR` | `side` is missing on a two-sided identity document, or sent on a type that does not take one |
| `422` | `VALIDATION_ERROR` | The body carries an unknown field |
| `422` | `VALIDATION_ERROR` | The data URI's content type is not `application/pdf`, `image/jpeg` or `image/png`; `details.content_type` carries it |
| `422` | `DOCUMENT_SUBJECT_MISMATCH` | The document type does not fit its subject: a company document on an individual account or with a person attached, or an identity document on a business account with no person to attach it to. `details` carry `document_type` and `required_subject` (`account` or `person`) |
| `422` | `INVALID_FIELD_FORMAT` | `file` is not a base64 data URI, or its payload is empty or not valid base64 |
| `422` | `INVALID_FIELD_VALUE` | `requirement_key` is not an open document requirement on this group: `details` carry `requirement_key` |
| `422` | `INVALID_FIELD_VALUE` | `type` is not one the requirement accepts: `details` carry `requirement_key`, `document_type` and `accepted_document_types` |
| `502` | `GATEWAY_ERROR` | The provider rejected the delivery. The evidence is stored; retry with the same key. |

## Request Example

```bash theme={null}
curl -X POST "https://api.hopnow.io/v1/accounts/acct_ka44qsvpo8q3wtzuwfqf0h6u/virtual-account-groups/vag_7m2k9x4c1b6v3n8q5z0j2p7t/documents" \
  -H "X-API-Key: your_api_key" \
  -H "X-Signature: hmac_signature" \
  -H "X-Timestamp: 1234567890" \
  -H "X-Nonce: abc123" \
  -H "Idempotency-Key: 3f8c1b24-6d5e-4a7f-9c10-2b4e8d6a1f37" \
  -H "Content-Type: application/json" \
  -d '{
    "requirement_key": "req_9c1f4a7b2e06d38a5f4c1b72",
    "type": "passport",
    "file_name": "passport.pdf",
    "country_code": "GB",
    "file": "data:application/pdf;base64,JVBERi0xLjcKJcTl8uXrp..."
  }'
```

## Response Example

```json theme={null}
{
  "id": "doc_xw9rlpdt0bcr5u4f9n095lf4",
  "object": "virtual_account_group_document",
  "account_id": "acct_ka44qsvpo8q3wtzuwfqf0h6u",
  "virtual_account_group_id": "vag_7m2k9x4c1b6v3n8q5z0j2p7t",
  "requirement_key": "req_9c1f4a7b2e06d38a5f4c1b72",
  "type": "passport",
  "side": null,
  "file_name": "passport.pdf",
  "content_type": "application/pdf",
  "size_bytes": 248312,
  "submission_status": "submitted",
  "country_code": "GB",
  "created": "2026-01-16T09:20:00Z",
  "updated": "2026-01-16T09:20:00Z",
  "created_by": "ak_3f7k9m2p5r8t1v4x6z0b3n5q",
  "updated_by": null
}
```
