curl --request POST \
--url https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents \
--header 'API-KEY: <api-key>' \
--header 'Api-Version: <api-version>' \
--header 'Content-Type: multipart/form-data' \
--form document=@example-file \
--form 'idDocType=<string>' \
--form 'country=<string>' \
--form 'side=<string>'import requests
url = "https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents"
files = { "document": ("example-file", open("example-file", "rb")) }
payload = {
"idDocType": "<string>",
"country": "<string>",
"side": "<string>"
}
headers = {
"Api-Version": "<api-version>",
"API-KEY": "<api-key>"
}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('document', '<string>');
form.append('idDocType', '<string>');
form.append('country', '<string>');
form.append('side', '<string>');
const options = {
method: 'POST',
headers: {'Api-Version': '<api-version>', 'API-KEY': '<api-key>'}
};
options.body = form;
fetch('https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"idDocType\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"side\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"API-KEY: <api-key>",
"Api-Version: <api-version>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"idDocType\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"side\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Api-Version", "<api-version>")
req.Header.Add("API-KEY", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents")
.header("Api-Version", "<api-version>")
.header("API-KEY", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"idDocType\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"side\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Api-Version"] = '<api-version>'
request["API-KEY"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"idDocType\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"side\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"accepted": true,
"status": "INITIATED",
"missing": [
"SELFIE",
"PROOF_OF_RESIDENCE"
]
}{
"code": 400,
"message": "<string>",
"status": "ERROR",
"data": null,
"errors": [
{
"code": "<string>",
"message": "<string>",
"field": "<string>"
}
]
}{
"code": 401,
"message": "Invalid API key",
"status": "ERROR",
"data": null,
"errors": [
{
"code": "ERRCORE_1004",
"message": "Invalid API key",
"field": null
}
]
}{
"code": 400,
"message": "<string>",
"status": "ERROR",
"data": null,
"errors": [
{
"code": "<string>",
"message": "<string>",
"field": "<string>"
}
]
}{
"code": 413,
"message": "File exceeds the maximum upload size of 5 MB",
"status": "ERROR",
"data": null,
"errors": [
{
"code": "ERRCORE_1001",
"message": "File too large",
"field": null
}
]
}Upload owner document
Uploads one beneficial owner’s document. Business customers only.
Endl submits the business after its first company document, once every owner’s documents are complete. Upload both company groups before the owners’ last document, so the company isn’t submitted with one group missing. A business keeps its current status (normally INITIATED) until the company and every owner are in, then PENDING. A business never returns SUBMITTED.
curl --request POST \
--url https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents \
--header 'API-KEY: <api-key>' \
--header 'Api-Version: <api-version>' \
--header 'Content-Type: multipart/form-data' \
--form document=@example-file \
--form 'idDocType=<string>' \
--form 'country=<string>' \
--form 'side=<string>'import requests
url = "https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents"
files = { "document": ("example-file", open("example-file", "rb")) }
payload = {
"idDocType": "<string>",
"country": "<string>",
"side": "<string>"
}
headers = {
"Api-Version": "<api-version>",
"API-KEY": "<api-key>"
}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('document', '<string>');
form.append('idDocType', '<string>');
form.append('country', '<string>');
form.append('side', '<string>');
const options = {
method: 'POST',
headers: {'Api-Version': '<api-version>', 'API-KEY': '<api-key>'}
};
options.body = form;
fetch('https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"idDocType\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"side\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"API-KEY: <api-key>",
"Api-Version: <api-version>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"idDocType\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"side\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Api-Version", "<api-version>")
req.Header.Add("API-KEY", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents")
.header("Api-Version", "<api-version>")
.header("API-KEY", "<api-key>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"idDocType\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"side\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-sandbox.endl.io/api/v0/customer/{userId}/ubo/{uboRef}/documents")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Api-Version"] = '<api-version>'
request["API-KEY"] = '<api-key>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"document\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"idDocType\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"country\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"side\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"accepted": true,
"status": "INITIATED",
"missing": [
"SELFIE",
"PROOF_OF_RESIDENCE"
]
}{
"code": 400,
"message": "<string>",
"status": "ERROR",
"data": null,
"errors": [
{
"code": "<string>",
"message": "<string>",
"field": "<string>"
}
]
}{
"code": 401,
"message": "Invalid API key",
"status": "ERROR",
"data": null,
"errors": [
{
"code": "ERRCORE_1004",
"message": "Invalid API key",
"field": null
}
]
}{
"code": 400,
"message": "<string>",
"status": "ERROR",
"data": null,
"errors": [
{
"code": "<string>",
"message": "<string>",
"field": "<string>"
}
]
}{
"code": 413,
"message": "File exceeds the maximum upload size of 5 MB",
"status": "ERROR",
"data": null,
"errors": [
{
"code": "ERRCORE_1001",
"message": "File too large",
"field": null
}
]
}Authorizations
Your partner API key. The key must carry the customers permission.
Headers
The API version this request targets. Required on every /api/v0/* call. Format YYYY-MM.<release>; 2026-09.1 is the latest. The version that served the request is echoed back on the response. Missing, malformed or unsupported values are rejected with 400.
^\d{4}-\d{2}\.\d+$Path Parameters
The customer reference id returned by create — a cus_-prefixed string, not a UUID.
The owner's applicantId, returned per beneficiary by create. Owners have no userId of their own.
Body
JPEG, PNG or PDF. Maximum 5 MB; the whole request is capped at 10 MB.
PASSPORT, ID_CARD, DRIVERS, RESIDENCE_PERMIT, SELFIE, or UTILITY_BILL for proof of address. DRIVERS_LICENSE is still accepted as an alias for DRIVERS. These are the recommended values: Endl does not validate the value, and any non-blank idDocType is passed to the verification provider. ERRCUS_1406 is not returned here. Omitted entirely → 400 ERRCORE_1001 (idDocType is required); sent but blank → ERRCUS_1405.
ISO 3166-1 alpha-3. Omitted entirely → 400 ERRCORE_1001 (country is required); sent but blank → ERRCUS_1012; invalid → ERRCUS_1013.
FRONT or BACK, for two-sided documents (ID_CARD, DRIVERS). Not validated.
Response
Accepted. A business keeps its current status (normally INITIATED) until the company and every owner are in, then PENDING. A business never returns SUBMITTED.