Download a KYC document
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
curl --request GET \
--url https://api.gravv.xyz/v1/customers/3f6b2a1e-8c4d-4e7f-9a2b-5c1d0e9f8a71/kyc/documents \
--header 'Api-Key: <API_KEY>'
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());
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())
{
"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
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>'
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());
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())
{
"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
contentis a data URI. Strip thedata:image/jpeg;base64,prefix, decode the rest and write the bytes to a file.size_bytesis the file's size.filenameis a storage name. UseOriginalFilenamefrom the list for the customer's own name.- A two-sided ID is two documents. Read the back image with
side=backon theBACK_SIDErow'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 for a test customer, list the documents, then download one. The decoded content is the file you uploaded.