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

# Uploading Documents

> Attach a customer's identity documents as evidence of your KYC.

You can attach a customer's documents, such as the front and back of their ID, as evidence of the KYC you did. Documents are optional, and they never change the customer's `kycStatus`.

> [!WARNING]
>
> Documents are personal data, so they are only for accounts with
> `identityPolicy: SHARED`. With `REFERENCED`, the document types list is
> empty and every upload is refused with `400 CUSTOMER_PII_NOT_ACCEPTED`.

Uploading takes two calls: list the document types you can send, then upload one file per call.

## List document types

```
GET /api/v3/partners/{shortName}/document-types
```

```bash theme={null}
curl https://be-dev-1.dev.ndovu.co/api/v3/partners/YOUR_SHORT_NAME/document-types \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

```json theme={null}
{
  "message": "Request executed successfully",
  "data": [
    {
      "shortName": "id-card",
      "name": "National ID card",
      "requiredSides": ["front", "back"],
      "allowedMimeTypes": ["image/jpeg", "image/png", "application/pdf"],
      "maxFileSizeBytes": 5242880
    }
  ]
}
```

| Field | Type | Description |
| - | - | - |
| `shortName` | string | The `{documentType}` to upload with. |
| `name` | string | Display name. |
| `requiredSides` | string\[] \| null | The sides this type takes, e.g. `front` and `back`. `null` for a single-sided document. |
| `allowedMimeTypes` | string\[] | File types accepted. |
| `maxFileSizeBytes` | number \| null | Largest file accepted, in bytes. |

The list depends on your country, and can change as we enable types. Read it rather than hard-coding the values above.

## Upload a document

```
PUT /api/v3/partners/{shortName}/customers/{externalUserId}/documents/{documentType}?side={side}
```

Send the file as `multipart/form-data` in a field named `file`. Upload one file per call: for a two-sided ID, call once with `side=front` and once with `side=back`.

```bash cURL theme={null}
curl -X PUT "https://be-dev-1.dev.ndovu.co/api/v3/partners/YOUR_SHORT_NAME/customers/cust_8842/documents/id-card?side=front" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "file=@./id-front.jpg"
```

```javascript Node.js theme={null}
import { readFile } from "node:fs/promises";

const form = new FormData();
form.append(
  "file",
  new Blob([await readFile("./id-front.jpg")], { type: "image/jpeg" }),
  "id-front.jpg"
);

const res = await fetch(
  `https://be-dev-1.dev.ndovu.co/api/v3/partners/${shortName}/customers/cust_8842/documents/id-card?side=front`,
  { method: "PUT", headers: { Authorization: `Bearer ${accessToken}` }, body: form }
);
```

```python Python theme={null}
with open("id-front.jpg", "rb") as f:
    res = requests.put(
        f"https://be-dev-1.dev.ndovu.co/api/v3/partners/{short_name}/customers/cust_8842/documents/id-card",
        params={"side": "front"},
        headers={"Authorization": f"Bearer {access_token}"},
        files={"file": ("id-front.jpg", f, "image/jpeg")},
    )
res.raise_for_status()
```

| Parameter | In | Description |
| - | - | - |
| `externalUserId` | path | Your ID for the customer. |
| `documentType` | path | A `shortName` from the document types list. |
| `side` | query | Required when the type has `requiredSides`, and must be one of them. Not allowed when it doesn't. |
| `file` | form field | The file. Its type must be in `allowedMimeTypes` and its size within `maxFileSizeBytes`. |

Uploading again for the same type and side **replaces** the earlier file.

### Response

`201 Created`:

```json theme={null}
{
  "message": "Document uploaded successfully",
  "data": {
    "type": "id-card",
    "side": "front",
    "uploadedAt": "2026-10-22T09:20:41.102Z",
    "url": "https://ndovu-documents.s3.amazonaws.com/…",
    "expiresAt": "2026-10-22T09:25:41.102Z"
  }
}
```

`url` is a temporary download link, valid for 5 minutes until `expiresAt`. Don't store it: [get the customer](https://docs.ndovu.co/onboarding-customers.md#get-a-customer) again for fresh links to all their documents.

## Errors

| Status | Code | Cause |
| - | - | - |
| `400` | `CUSTOMER_PII_NOT_ACCEPTED` | Your `identityPolicy` is `REFERENCED`. |
| `400` | `UNSUPPORTED_DOCUMENT_TYPE` | `documentType` is not in your document types list. |
| `400` | `DOCUMENT_SIDE_REQUIRED` | The type has sides and `side` is missing. |
| `400` | `INVALID_DOCUMENT_SIDE` | `side` is not one of the type's sides, or the type takes no side. |
| `400` | `BAD_REQUEST` | No `file`, a file type not in `allowedMimeTypes`, or a file over `maxFileSizeBytes`. `message` says which. |
| `401` | `INVALID_PARTNER_TOKEN` | Missing, expired or invalid access token. |
| `403` | `FORBIDDEN` | `shortName` is not yours. |
| `403` | `PARTNER_DISABLED` | Your account is disabled; uploads are refused. |
| `404` | `CUSTOMER_NOT_FOUND` | No customer with this `externalUserId`. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.