Gravv DOCS
Docs Agent Get Started
API Reference Upload document

Upload document

Upload a KYC document for individual and business customers. This is the first step of the server-to-server verification flow.

Documents are uploaded as base64-encoded strings along with metadata about the document type, customer information, and document details. The document is validated and stored in preparation for verification.

POST/v1/customers/kyc/upload-document

Supported document types

Individual customers must upload a SELFIE and one identification document: PASSPORT, ID_CARD, or DRIVERS.

Business customers upload a single REGISTRATION_CERTIFICATE, which may be a formation document or an ownership document. The idDocType value is always REGISTRATION_CERTIFICATE.

All idDocType values are uppercase.

Selfie documents must be an image (JPEG or PNG); PDF selfies are rejected.

Document subtypes

Identification documents use idDocSubType to indicate which side is uploaded (case-sensitive, uppercase):

  • FRONT_SIDE: Front side of document
  • BACK_SIDE: Back side of document (if applicable)

Omit idDocSubType for SELFIE uploads.

File format

The filename extension and the decoded content MIME type must agree and must be one of: image/jpeg, image/png, application/pdf.

Size limits

The maximum decoded document size is 10 MB.

Idempotency

The Idempotency-Key header is required on this endpoint. If you send the same key again with an identical request body, the API returns the original response instead of uploading the document a second time. If you send the same key with a different request body, then the API rejects the request with a 422.

After uploading all required documents, call the Initiate KYC S2S endpoint to start the verification process.

Headers

Unique key to dedupe retries. Required on this endpoint; 1–255 ASCII printable characters. Missing or malformed keys return 400.

Request Body

ID of the customer this document belongs to

Type of document being uploaded. Individual customers upload a SELFIE plus one identification document: PASSPORT, ID_CARD, or DRIVERS. Business customers upload REGISTRATION_CERTIFICATE only.

Possible values: PASSPORT, ID_CARD, DRIVERS, SELFIE, REGISTRATION_CERTIFICATE

Subtype indicating which side of the document. Must be uppercase; lowercase values are passed through to the upstream verifier and may be silently rejected.

Possible values: FRONT_SIDE, BACK_SIDE

Document issuing country (ISO 3166-1 alpha-3 code, uppercase)

First name as shown on document

Middle name as shown on document (optional)

Last name as shown on document

Document issue date (YYYY-MM-DD)

Document expiration date (YYYY-MM-DD)

Document identification number

Date of birth (YYYY-MM-DD)

Place of birth as shown on document

Base64-encoded document content. The decoded bytes must be a JPEG, PNG, or PDF; the service sniffs the leading bytes and rejects other MIME types. Maximum decoded size is 10 MB.

Original filename of the document. Extension must be one of .jpg, .jpeg, .png, .pdf (case-insensitive); the file extension is validated before the content MIME sniff.

Request Examples

{
"customer_id": "b84d9a52-2978-43e5-9b86-eac9839e1146",
"metadata": {
"idDocType": "PASSPORT",
"idDocSubType": "FRONT_SIDE",
"country": "USA",
"firstName": "John",
"middleName": "Michael",
"lastName": "Doe",
"issuedDate": "2020-01-15",
"validUntil": "2030-01-15",
"number": "A12345678",
"dob": "1990-05-20",
"placeOfBirth": "New York, NY"
},
"content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"filename": "passport_front.jpg"
}
{
"customer_id": "b84d9a52-2978-43e5-9b86-eac9839e1146",
"metadata": {
"idDocType": "SELFIE",
"country": "NGA",
"firstName": "John",
"lastName": "Doe",
"dob": "1990-05-20"
},
"content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"filename": "selfie.jpg"
}
{
"customer_id": "c84d9a52-2978-43e5-9b86-eac9839e1147",
"metadata": {
"idDocType": "REGISTRATION_CERTIFICATE",
"idDocSubType": "FRONT_SIDE",
"country": "NGA",
"firstName": "Acme",
"lastName": "Corporation",
"number": "RC123456",
"issuedDate": "2020-01-15"
},
"content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"filename": "registration_certificate.pdf"
}
{
"customer_id": "b84d9a52-2978-43e5-9b86-eac9839e1146",
"metadata": {
"idDocType": "ID_CARD",
"idDocSubType": "FRONT_SIDE",
"country": "NGA",
"firstName": "Jane",
"lastName": "Smith",
"number": "ID987654321",
"issuedDate": "2019-06-10",
"validUntil": "2029-06-10",
"dob": "1992-03-15",
"placeOfBirth": "Lagos"
},
"content": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==",
"filename": "id_card_front.jpg"
}

Responses

Status Description
200 Document uploaded successfully
400 Invalid request
404 Customer not found
409 Concurrent in-flight request with the same Idempotency-Key (gateway-level)
413 Payload too large. The decoded document exceeds the 10 MB service limit, or the request body exceeded the ingress 15 MB limit. The ingress-layer 413 is returned as HTML by nginx and does not match the schema below. (gateway-level)
422 Idempotency-Key reused with a different request payload (gateway-level)
500 Internal processing error during document upload

200 response

Always "success" on a successful upload

Possible values: success

ID of the customer the document was saved for

Normalised document type derived from metadata.idDocType

ISO 3166-1 alpha-3 country code as supplied in metadata.country

Human-readable confirmation message

Error information if the request failed

{
"data": {
"status": "success",
"customer_id": "b84d9a52-2978-43e5-9b86-eac9839e1146",
"document_type": "PASSPORT",
"country": "NGA",
"message": "Document saved. Use /start-s2s to submit for verification."
},
"error": null
}
{
"data": {
"status": "success",
"customer_id": "b84d9a52-2978-43e5-9b86-eac9839e1146",
"document_type": "SELFIE",
"country": "NGA",
"message": "Document saved. Use /start-s2s to submit for verification."
},
"error": null
}

400 response

Always null for error responses

Human-readable error message

{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "customer_id is required"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "content is required"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "filename is required"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "id_doc_type is required"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "country is required"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "number is required"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "valid_until is required"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "id_doc_sub_type is required"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "The customer ID provided is not valid"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "invalid document type for customer type"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "invalid document sub type"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "document exceeds maximum allowed size of 10 MB"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "document content is not valid base64"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "unsupported file type \"application/octet-stream\"; allowed types are JPEG, PNG, and PDF"
}
}
{
"data": null,
"error": {
"code": "INVALID_REQUEST",
"message": "missing idempotency key in request headers"
}
}

404 response

Always null for error responses

Human-readable error message

{
"data": null,
"error": {
"code": "CUSTOMER_NOT_FOUND",
"message": "Customer not found. Please verify the customer exists in our system."
}
}

409 response

Always null for error responses

Human-readable error message

{
"data": null,
"error": {
"code": "CONFLICT",
"message": "A request with the same Idempotency-Key is currently being processed"
}
}

413 response

Always null for error responses

Human-readable error message

{
"data": null,
"error": {
"code": "PAYLOAD_TOO_LARGE",
"message": "file too large: content exceeds maximum allowed size of 10MB"
}
}

422 response

Always null for error responses

Human-readable error message

{
"data": null,
"error": {
"code": "UNPROCESSABLE_ENTITY",
"message": "Idempotency-Key was previously used with a different request payload"
}
}

500 response

Always null for error responses

Human-readable error message

{
"data": null,
"error": {
"code": "KYC_PROCESSING_ERROR",
"message": "Document processing failed. Please verify your documents and try again."
}
}

Authorization

  • ApiKeyAuth
Was this page helpful?
Assistant
Responses are generated using AI and may contain mistakes.

Ask me anything about the documentation.

ESC