> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.haloinvest.ng/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.haloinvest.ng/_mcp/server.

# Upload KYC documents

POST https://api.haloinvest.ng/api/kyc/upload
Content-Type: application/json

Sends identity documents for a customer of your business to Halo for verification. Find the customer by email or by an account number your business opened. Send all three images and the ID type; the request is refused without them. A customer who also banks with Halo directly or through another business is refused with 403.

Documents cannot be sent again while earlier ones are still under review. Check progress with the KYC status endpoint.

Reference: https://docs.haloinvest.ng/api-reference/identity-and-kyc/upload-kyc-documents

## Authentication

- `Authorization` header (bearer token, required) — Your Halo partner API key, sent as `Authorization: Bearer <key>`.

## Request

### Body (application/json)

This endpoint expects an object.

- `email` (string, optional) — The customer email. Required when account_number is not sent.
- `account_number` (string, optional) — An account number your business opened. Required when email is not sent.
- `id_identity_type` (string, optional) — The type of ID document in the images, passed to the verification provider.
- `id_front_page` (string, optional) — Front of the ID. A JPEG or PNG image as a base64 data URI, for example `data:image/jpeg;base64,...`.
- `id_back_page` (string, optional) — Back of the ID. A JPEG or PNG image as a base64 data URI, for example `data:image/jpeg;base64,...`.
- `proof_of_residency` (string, optional) — Proof of address. A JPEG or PNG image as a base64 data URI, for example `data:image/jpeg;base64,...`.

## Response

### 200

The documents were sent for verification.

- `status` (boolean, required) — True on success, false otherwise.
- `message` (EnvelopeMessage, required) — A short message. On a 422 validation failure it is an object of field names to error lists.
- `status_code` (integer, required) — The HTTP status code.
- `data` (any, optional) — The result. Left out on most errors.

## Errors

### 400 Bad Request Error

The documents could not be sent.

- `status` (boolean, required) — True on success, false otherwise.
- `message` (EnvelopeMessage, required) — A short message. On a 422 validation failure it is an object of field names to error lists.
- `status_code` (integer, required) — The HTTP status code.
- `data` (any, optional) — The result. Left out on most errors.

### 401 Unauthorized Error

The API key is missing or not recognised, or your business is switched off.

- `error` (UnauthorizedErrorError, optional)

### 404 Not Found Error

The customer was not found for your business.

- `status` (boolean, required) — True on success, false otherwise.
- `message` (EnvelopeMessage, required) — A short message. On a 422 validation failure it is an object of field names to error lists.
- `status_code` (integer, required) — The HTTP status code.
- `data` (any, optional) — The result. Left out on most errors.

### 422 Unprocessable Entity Error

The request failed validation. `message` lists the errors by field.

- `status` (boolean, required) — True on success, false otherwise.
- `message` (EnvelopeMessage, required) — A short message. On a 422 validation failure it is an object of field names to error lists.
- `status_code` (integer, required) — The HTTP status code.
- `data` (any, optional) — The result. Left out on most errors.

### 429 Too Many Requests Error

Too many requests. Wait and try again.

- `status` (boolean, required) — True on success, false otherwise.
- `message` (EnvelopeMessage, required) — A short message. On a 422 validation failure it is an object of field names to error lists.
- `status_code` (integer, required) — The HTTP status code.
- `data` (any, optional) — The result. Left out on most errors.

## Types

### EnvelopeMessage

A short message. On a 422 validation failure it is an object of field names to error lists.

- string
- map from string to list of string

### UnauthorizedErrorError

- `message` (string, optional)

## Examples

**Request**

```json
{}
```

**Response**

```json
{
  "status": true,
  "message": "Documents Uploaded successfully",
  "status_code": 200,
  "data": null
}
```

**SDK Code**

```python Identity and KYC_uploadKycDocuments_example
import requests

url = "https://api.haloinvest.ng/api/kyc/upload"

payload = {}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Identity and KYC_uploadKycDocuments_example
const url = 'https://api.haloinvest.ng/api/kyc/upload';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Identity and KYC_uploadKycDocuments_example
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.haloinvest.ng/api/kyc/upload"

	payload := strings.NewReader("{}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Identity and KYC_uploadKycDocuments_example
require 'uri'
require 'net/http'

url = URI("https://api.haloinvest.ng/api/kyc/upload")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"

response = http.request(request)
puts response.read_body
```

```java Identity and KYC_uploadKycDocuments_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.haloinvest.ng/api/kyc/upload")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{}")
  .asString();
```

```php Identity and KYC_uploadKycDocuments_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.haloinvest.ng/api/kyc/upload', [
  'body' => '{}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp Identity and KYC_uploadKycDocuments_example
using RestSharp;

var client = new RestClient("https://api.haloinvest.ng/api/kyc/upload");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Identity and KYC_uploadKycDocuments_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.haloinvest.ng/api/kyc/upload")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```