---
title: "Download a KYC document"
description: "List a customer's KYC documents and save one side of one document to a compliance review file."
---
This recipe lists a customer's KYC documents and saves one side of one document to a compliance review file.

**Who it is for.** The compliance officer of a payroll platform, who must attach a contractor's uploaded passport to a periodic review file.

**Status.** Available, for documents uploaded through the API or taken on the hosted page. Not supported for documents uploaded through the card KYC flow.

## Money example

No money moves. Before approving a contractor's first 3,000.00 USD monthly payout, the compliance officer re-checks the passport on file. You list the documents, download the passport front and save it to the review file.

## Before you start

- A key with `customer:update`. The download needs it, and the list does not.
- The customer has uploaded documents, server to server or on the hosted page.
- Agree who may see identity documents. The list holds the document number in plain text.

## Find out what is required

The documents list returns each document's `id` and which sides have an image (`has_front_image`, `has_back_image`). For documents taken on the hosted page, the list also fetches the images first, so call it **before** downloading.

## Steps

### 1. List the documents

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url https://api.gravv.xyz/v1/customers/3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71/kyc/documents \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/customers/3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71/kyc/documents", {
  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/v1/customers/3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71/kyc/documents",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response"
{
  "data": {
    "customer_id": "3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71",
    "documents": [
      {
        "id": "a7b8c9d0-e1f2-4a3b-8c4d-5e6f7a8b9c0d",
        "IDType": "PASSPORT",
        "IDSubType": "FRONT_SIDE",
        "IDNumber": "B04512398",
        "IssuingCountry": "NGA",
        "IssuedDate": "2020-02-11",
        "ExpiryDate": "2030-02-10",
        "has_front_image": true,
        "has_back_image": false,
        "OriginalFilename": "passport.jpg"
      }
    ],
    "count": 1
  },
  "error": null
}
```

Pick the document `id` and a side that has an image. Do not log `IDNumber`.

### 2. Download one side

<CodeGroup>
```bash title="cURL"
curl --request GET \
     --url 'https://api.gravv.xyz/v1/customers/3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71/kyc/documents/a7b8c9d0-e1f2-4a3b-8c4d-5e6f7a8b9c0d/download?side=front' \
     --header 'Api-Key: <API_KEY>'
```
```javascript title="Node"
const response = await fetch("https://api.gravv.xyz/v1/customers/3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71/kyc/documents/a7b8c9d0-e1f2-4a3b-8c4d-5e6f7a8b9c0d/download?side=front", {
  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/v1/customers/3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71/kyc/documents/a7b8c9d0-e1f2-4a3b-8c4d-5e6f7a8b9c0d/download?side=front",
    headers={
        "Api-Key": os.environ["GRAVV_API_KEY"],
    },
)
print(response.status_code, response.json())
```
</CodeGroup>

```json title="Response, content trimmed"
{
  "data": {
    "content": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBD...",
    "content_type": "image/jpeg",
    "document_id": "a7b8c9d0-e1f2-4a3b-8c4d-5e6f7a8b9c0d",
    "filename": "passport_front-side_60287353-bc08-482b-871c-98305b9a42dd.jpg",
    "side": "front",
    "size_bytes": 57134
  },
  "error": null
}
```

### 3. Save the file

- `content` is a **data URI**. Strip the `data:image/jpeg;base64,` prefix, decode the rest and write the bytes to a file. `size_bytes` is the file's size.
- `filename` is a storage name. Use `OriginalFilename` from the list for the customer's own name.
- A two-sided ID is **two documents**. Read the back image with `side=back` on the `BACK_SIDE` row's own id.

## What can go wrong

| Response | Why | Fix |
| :-- | :-- | :-- |
| 400 `side is required (front or back)` or `side must be "front" or "back"` | `side` is missing or wrong | Send `?side=front` or `?side=back` |
| 400 `invalid document_id format` | The id is not a UUID | Use `documents[].id` |
| 404 `document not found` | The id belongs to another customer, or does not exist | List again for this customer |
| 404 `no back image available for this document` | That side was never uploaded on this row | Use the `BACK_SIDE` row, or the other side |
| 404 `no KYC document data found for customer` (on the list) | Nothing was uploaded. With no documents, the list returns a 404, not an empty list | Treat it as "no documents" |
| 404 `CUSTOMER_NOT_FOUND: …` | The customer id or the environment is wrong | Check the id |
| 403 `{"reason": "forbidden", "required_any": ["customer:update"]}` | The key lacks `customer:update` | Use a key with that permission |
| 500 `failed to download document image`, or 503 `storage service not configured` | A storage problem | Retry later, and contact support if it persists |
| The download fails, or returns odd content | The document was uploaded through the card KYC flow (`/v1/risk/upload-document`) | Not supported. Keep one upload flow per customer |

## Webhooks you will receive

This recipe sends no webhooks.

## Testing in sandbox

This recipe works with a `grvSec_sandbox_` key. [Upload a small JPEG](/recipes/customers-and-kyc/verify-a-person-with-documents-you-upload#1-upload-the-id-front) for a test customer, list the documents, then download one. The decoded `content` is the file you uploaded.

## Reference

- [Get KYC document data](/api-reference/kyc/get-v1-customers-customer-id-kyc-documents)
- [Download a KYC document](/api-reference/kyc/get-v1-customers-customer-id-kyc-documents-document-id-download)
- [Download KYC documents](/getting-started/download-kyc-documents)
