Customer Batch Intake
curl --request POST \
--url https://api.finosu.com/customers/batch \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"customers": [
{
"id": "<string>",
"firstName": "<string>",
"lastName": "<string>",
"timezone": "<string>",
"phoneNumber": "<string>",
"taskType": "collections",
"email": "[email protected]",
"birthday": "1985-06-15",
"ssnLastFour": "1234",
"streetAddress1": "123 Main St",
"streetAddress2": "Apt 4B",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"doNotCall": true,
"isTest": false,
"externalMetadata": {
"home_phone": "+14155555555",
"cell_phone": "+14155556666",
"employer_name": "Example employer",
"employer_address": "456 Business Ave",
"employer_city": "San Francisco",
"employer_state": "CA",
"employer_zip": "94103",
"employer_department": "Sales",
"employer_phone": "+14155557777",
"employer_direct_line": "+14155558888",
"pay_frequency": "biweekly",
"pay_day": "Friday"
},
"loanIds": [
123,
456,
789
],
"sync": false,
"loanId": "<string>"
}
]
}
'import requests
url = "https://api.finosu.com/customers/batch"
payload = { "customers": [
{
"id": "<string>",
"firstName": "<string>",
"lastName": "<string>",
"timezone": "<string>",
"phoneNumber": "<string>",
"taskType": "collections",
"email": "[email protected]",
"birthday": "1985-06-15",
"ssnLastFour": "1234",
"streetAddress1": "123 Main St",
"streetAddress2": "Apt 4B",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"doNotCall": True,
"isTest": False,
"externalMetadata": {
"home_phone": "+14155555555",
"cell_phone": "+14155556666",
"employer_name": "Example employer",
"employer_address": "456 Business Ave",
"employer_city": "San Francisco",
"employer_state": "CA",
"employer_zip": "94103",
"employer_department": "Sales",
"employer_phone": "+14155557777",
"employer_direct_line": "+14155558888",
"pay_frequency": "biweekly",
"pay_day": "Friday"
},
"loanIds": [123, 456, 789],
"sync": False,
"loanId": "<string>"
}
] }
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
customers: [
{
id: '<string>',
firstName: '<string>',
lastName: '<string>',
timezone: '<string>',
phoneNumber: '<string>',
taskType: 'collections',
email: '[email protected]',
birthday: '1985-06-15',
ssnLastFour: '1234',
streetAddress1: '123 Main St',
streetAddress2: 'Apt 4B',
city: 'San Francisco',
state: 'CA',
zipCode: '94102',
doNotCall: true,
isTest: false,
externalMetadata: {
home_phone: '+14155555555',
cell_phone: '+14155556666',
employer_name: 'Example employer',
employer_address: '456 Business Ave',
employer_city: 'San Francisco',
employer_state: 'CA',
employer_zip: '94103',
employer_department: 'Sales',
employer_phone: '+14155557777',
employer_direct_line: '+14155558888',
pay_frequency: 'biweekly',
pay_day: 'Friday'
},
loanIds: [123, 456, 789],
sync: false,
loanId: '<string>'
}
]
})
};
fetch('https://api.finosu.com/customers/batch', 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.finosu.com/customers/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'customers' => [
[
'id' => '<string>',
'firstName' => '<string>',
'lastName' => '<string>',
'timezone' => '<string>',
'phoneNumber' => '<string>',
'taskType' => 'collections',
'email' => '[email protected]',
'birthday' => '1985-06-15',
'ssnLastFour' => '1234',
'streetAddress1' => '123 Main St',
'streetAddress2' => 'Apt 4B',
'city' => 'San Francisco',
'state' => 'CA',
'zipCode' => '94102',
'doNotCall' => true,
'isTest' => false,
'externalMetadata' => [
'home_phone' => '+14155555555',
'cell_phone' => '+14155556666',
'employer_name' => 'Example employer',
'employer_address' => '456 Business Ave',
'employer_city' => 'San Francisco',
'employer_state' => 'CA',
'employer_zip' => '94103',
'employer_department' => 'Sales',
'employer_phone' => '+14155557777',
'employer_direct_line' => '+14155558888',
'pay_frequency' => 'biweekly',
'pay_day' => 'Friday'
],
'loanIds' => [
123,
456,
789
],
'sync' => false,
'loanId' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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.finosu.com/customers/batch"
payload := strings.NewReader("{\n \"customers\": [\n {\n \"id\": \"<string>\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"timezone\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"taskType\": \"collections\",\n \"email\": \"[email protected]\",\n \"birthday\": \"1985-06-15\",\n \"ssnLastFour\": \"1234\",\n \"streetAddress1\": \"123 Main St\",\n \"streetAddress2\": \"Apt 4B\",\n \"city\": \"San Francisco\",\n \"state\": \"CA\",\n \"zipCode\": \"94102\",\n \"doNotCall\": true,\n \"isTest\": false,\n \"externalMetadata\": {\n \"home_phone\": \"+14155555555\",\n \"cell_phone\": \"+14155556666\",\n \"employer_name\": \"Example employer\",\n \"employer_address\": \"456 Business Ave\",\n \"employer_city\": \"San Francisco\",\n \"employer_state\": \"CA\",\n \"employer_zip\": \"94103\",\n \"employer_department\": \"Sales\",\n \"employer_phone\": \"+14155557777\",\n \"employer_direct_line\": \"+14155558888\",\n \"pay_frequency\": \"biweekly\",\n \"pay_day\": \"Friday\"\n },\n \"loanIds\": [\n 123,\n 456,\n 789\n ],\n \"sync\": false,\n \"loanId\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
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.finosu.com/customers/batch")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"customers\": [\n {\n \"id\": \"<string>\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"timezone\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"taskType\": \"collections\",\n \"email\": \"[email protected]\",\n \"birthday\": \"1985-06-15\",\n \"ssnLastFour\": \"1234\",\n \"streetAddress1\": \"123 Main St\",\n \"streetAddress2\": \"Apt 4B\",\n \"city\": \"San Francisco\",\n \"state\": \"CA\",\n \"zipCode\": \"94102\",\n \"doNotCall\": true,\n \"isTest\": false,\n \"externalMetadata\": {\n \"home_phone\": \"+14155555555\",\n \"cell_phone\": \"+14155556666\",\n \"employer_name\": \"Example employer\",\n \"employer_address\": \"456 Business Ave\",\n \"employer_city\": \"San Francisco\",\n \"employer_state\": \"CA\",\n \"employer_zip\": \"94103\",\n \"employer_department\": \"Sales\",\n \"employer_phone\": \"+14155557777\",\n \"employer_direct_line\": \"+14155558888\",\n \"pay_frequency\": \"biweekly\",\n \"pay_day\": \"Friday\"\n },\n \"loanIds\": [\n 123,\n 456,\n 789\n ],\n \"sync\": false,\n \"loanId\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.finosu.com/customers/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"customers\": [\n {\n \"id\": \"<string>\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"timezone\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"taskType\": \"collections\",\n \"email\": \"[email protected]\",\n \"birthday\": \"1985-06-15\",\n \"ssnLastFour\": \"1234\",\n \"streetAddress1\": \"123 Main St\",\n \"streetAddress2\": \"Apt 4B\",\n \"city\": \"San Francisco\",\n \"state\": \"CA\",\n \"zipCode\": \"94102\",\n \"doNotCall\": true,\n \"isTest\": false,\n \"externalMetadata\": {\n \"home_phone\": \"+14155555555\",\n \"cell_phone\": \"+14155556666\",\n \"employer_name\": \"Example employer\",\n \"employer_address\": \"456 Business Ave\",\n \"employer_city\": \"San Francisco\",\n \"employer_state\": \"CA\",\n \"employer_zip\": \"94103\",\n \"employer_department\": \"Sales\",\n \"employer_phone\": \"+14155557777\",\n \"employer_direct_line\": \"+14155558888\",\n \"pay_frequency\": \"biweekly\",\n \"pay_day\": \"Friday\"\n },\n \"loanIds\": [\n 123,\n 456,\n 789\n ],\n \"sync\": false,\n \"loanId\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"totalCustomers": 100,
"message": "Batch job created with 4 parallel batches. Use GET /customers/batch/550e8400-e29b-41d4-a716-446655440000 to check status."
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": {
"code": "<string>",
"message": "<string>"
}
}{
"detail": {
"code": "<string>",
"message": "<string>"
}
}{
"detail": "<string>"
}{
"detail": {
"code": "<string>",
"message": "<string>"
}
}{
"detail": {
"code": "<string>",
"message": "<string>"
}
}Customers
Batch Create Customers
Submit a list for background processing. Explicit scheduleMode=enroll uses origination enrollment with a batch Idempotency-Key and per-row idempotencyKey. Omit scheduleMode for the existing customer-create workflow. HTTP 202 confirms acceptance, not successful processing of every row.
POST
/
customers
/
batch
Customer Batch Intake
curl --request POST \
--url https://api.finosu.com/customers/batch \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"customers": [
{
"id": "<string>",
"firstName": "<string>",
"lastName": "<string>",
"timezone": "<string>",
"phoneNumber": "<string>",
"taskType": "collections",
"email": "[email protected]",
"birthday": "1985-06-15",
"ssnLastFour": "1234",
"streetAddress1": "123 Main St",
"streetAddress2": "Apt 4B",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"doNotCall": true,
"isTest": false,
"externalMetadata": {
"home_phone": "+14155555555",
"cell_phone": "+14155556666",
"employer_name": "Example employer",
"employer_address": "456 Business Ave",
"employer_city": "San Francisco",
"employer_state": "CA",
"employer_zip": "94103",
"employer_department": "Sales",
"employer_phone": "+14155557777",
"employer_direct_line": "+14155558888",
"pay_frequency": "biweekly",
"pay_day": "Friday"
},
"loanIds": [
123,
456,
789
],
"sync": false,
"loanId": "<string>"
}
]
}
'import requests
url = "https://api.finosu.com/customers/batch"
payload = { "customers": [
{
"id": "<string>",
"firstName": "<string>",
"lastName": "<string>",
"timezone": "<string>",
"phoneNumber": "<string>",
"taskType": "collections",
"email": "[email protected]",
"birthday": "1985-06-15",
"ssnLastFour": "1234",
"streetAddress1": "123 Main St",
"streetAddress2": "Apt 4B",
"city": "San Francisco",
"state": "CA",
"zipCode": "94102",
"doNotCall": True,
"isTest": False,
"externalMetadata": {
"home_phone": "+14155555555",
"cell_phone": "+14155556666",
"employer_name": "Example employer",
"employer_address": "456 Business Ave",
"employer_city": "San Francisco",
"employer_state": "CA",
"employer_zip": "94103",
"employer_department": "Sales",
"employer_phone": "+14155557777",
"employer_direct_line": "+14155558888",
"pay_frequency": "biweekly",
"pay_day": "Friday"
},
"loanIds": [123, 456, 789],
"sync": False,
"loanId": "<string>"
}
] }
headers = {
"X-API-Key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-API-Key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
customers: [
{
id: '<string>',
firstName: '<string>',
lastName: '<string>',
timezone: '<string>',
phoneNumber: '<string>',
taskType: 'collections',
email: '[email protected]',
birthday: '1985-06-15',
ssnLastFour: '1234',
streetAddress1: '123 Main St',
streetAddress2: 'Apt 4B',
city: 'San Francisco',
state: 'CA',
zipCode: '94102',
doNotCall: true,
isTest: false,
externalMetadata: {
home_phone: '+14155555555',
cell_phone: '+14155556666',
employer_name: 'Example employer',
employer_address: '456 Business Ave',
employer_city: 'San Francisco',
employer_state: 'CA',
employer_zip: '94103',
employer_department: 'Sales',
employer_phone: '+14155557777',
employer_direct_line: '+14155558888',
pay_frequency: 'biweekly',
pay_day: 'Friday'
},
loanIds: [123, 456, 789],
sync: false,
loanId: '<string>'
}
]
})
};
fetch('https://api.finosu.com/customers/batch', 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.finosu.com/customers/batch",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'customers' => [
[
'id' => '<string>',
'firstName' => '<string>',
'lastName' => '<string>',
'timezone' => '<string>',
'phoneNumber' => '<string>',
'taskType' => 'collections',
'email' => '[email protected]',
'birthday' => '1985-06-15',
'ssnLastFour' => '1234',
'streetAddress1' => '123 Main St',
'streetAddress2' => 'Apt 4B',
'city' => 'San Francisco',
'state' => 'CA',
'zipCode' => '94102',
'doNotCall' => true,
'isTest' => false,
'externalMetadata' => [
'home_phone' => '+14155555555',
'cell_phone' => '+14155556666',
'employer_name' => 'Example employer',
'employer_address' => '456 Business Ave',
'employer_city' => 'San Francisco',
'employer_state' => 'CA',
'employer_zip' => '94103',
'employer_department' => 'Sales',
'employer_phone' => '+14155557777',
'employer_direct_line' => '+14155558888',
'pay_frequency' => 'biweekly',
'pay_day' => 'Friday'
],
'loanIds' => [
123,
456,
789
],
'sync' => false,
'loanId' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-Key: <api-key>"
],
]);
$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.finosu.com/customers/batch"
payload := strings.NewReader("{\n \"customers\": [\n {\n \"id\": \"<string>\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"timezone\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"taskType\": \"collections\",\n \"email\": \"[email protected]\",\n \"birthday\": \"1985-06-15\",\n \"ssnLastFour\": \"1234\",\n \"streetAddress1\": \"123 Main St\",\n \"streetAddress2\": \"Apt 4B\",\n \"city\": \"San Francisco\",\n \"state\": \"CA\",\n \"zipCode\": \"94102\",\n \"doNotCall\": true,\n \"isTest\": false,\n \"externalMetadata\": {\n \"home_phone\": \"+14155555555\",\n \"cell_phone\": \"+14155556666\",\n \"employer_name\": \"Example employer\",\n \"employer_address\": \"456 Business Ave\",\n \"employer_city\": \"San Francisco\",\n \"employer_state\": \"CA\",\n \"employer_zip\": \"94103\",\n \"employer_department\": \"Sales\",\n \"employer_phone\": \"+14155557777\",\n \"employer_direct_line\": \"+14155558888\",\n \"pay_frequency\": \"biweekly\",\n \"pay_day\": \"Friday\"\n },\n \"loanIds\": [\n 123,\n 456,\n 789\n ],\n \"sync\": false,\n \"loanId\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-API-Key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
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.finosu.com/customers/batch")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"customers\": [\n {\n \"id\": \"<string>\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"timezone\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"taskType\": \"collections\",\n \"email\": \"[email protected]\",\n \"birthday\": \"1985-06-15\",\n \"ssnLastFour\": \"1234\",\n \"streetAddress1\": \"123 Main St\",\n \"streetAddress2\": \"Apt 4B\",\n \"city\": \"San Francisco\",\n \"state\": \"CA\",\n \"zipCode\": \"94102\",\n \"doNotCall\": true,\n \"isTest\": false,\n \"externalMetadata\": {\n \"home_phone\": \"+14155555555\",\n \"cell_phone\": \"+14155556666\",\n \"employer_name\": \"Example employer\",\n \"employer_address\": \"456 Business Ave\",\n \"employer_city\": \"San Francisco\",\n \"employer_state\": \"CA\",\n \"employer_zip\": \"94103\",\n \"employer_department\": \"Sales\",\n \"employer_phone\": \"+14155557777\",\n \"employer_direct_line\": \"+14155558888\",\n \"pay_frequency\": \"biweekly\",\n \"pay_day\": \"Friday\"\n },\n \"loanIds\": [\n 123,\n 456,\n 789\n ],\n \"sync\": false,\n \"loanId\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.finosu.com/customers/batch")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-API-Key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"customers\": [\n {\n \"id\": \"<string>\",\n \"firstName\": \"<string>\",\n \"lastName\": \"<string>\",\n \"timezone\": \"<string>\",\n \"phoneNumber\": \"<string>\",\n \"taskType\": \"collections\",\n \"email\": \"[email protected]\",\n \"birthday\": \"1985-06-15\",\n \"ssnLastFour\": \"1234\",\n \"streetAddress1\": \"123 Main St\",\n \"streetAddress2\": \"Apt 4B\",\n \"city\": \"San Francisco\",\n \"state\": \"CA\",\n \"zipCode\": \"94102\",\n \"doNotCall\": true,\n \"isTest\": false,\n \"externalMetadata\": {\n \"home_phone\": \"+14155555555\",\n \"cell_phone\": \"+14155556666\",\n \"employer_name\": \"Example employer\",\n \"employer_address\": \"456 Business Ave\",\n \"employer_city\": \"San Francisco\",\n \"employer_state\": \"CA\",\n \"employer_zip\": \"94103\",\n \"employer_department\": \"Sales\",\n \"employer_phone\": \"+14155557777\",\n \"employer_direct_line\": \"+14155558888\",\n \"pay_frequency\": \"biweekly\",\n \"pay_day\": \"Friday\"\n },\n \"loanIds\": [\n 123,\n 456,\n 789\n ],\n \"sync\": false,\n \"loanId\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"totalCustomers": 100,
"message": "Batch job created with 4 parallel batches. Use GET /customers/batch/550e8400-e29b-41d4-a716-446655440000 to check status."
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": {
"code": "<string>",
"message": "<string>"
}
}{
"detail": {
"code": "<string>",
"message": "<string>"
}
}{
"detail": "<string>"
}{
"detail": {
"code": "<string>",
"message": "<string>"
}
}{
"detail": {
"code": "<string>",
"message": "<string>"
}
}Choose your workflow
- Origination lists: send outer
scheduleMode: "enroll", a headerIdempotency-Key, and a rowidempotencyKeyfor each application. Follow Enroll a List (Batch) for the full contract, limits, retry behavior and examples. - Existing customer creation: omit
scheduleMode. The remainder of this guide describes that existing workflow only.
Overview
This batch endpoint allows you to create multiple customers in a single API request for improved efficiency. The endpoint processes customers asynchronously - it immediately returns a job ID (HTTP 202 Accepted) and processes customers in the background. UseGET /customers/batch/{jobId} to check the processing status.
Each customer in the batch is processed independently, making it ideal for bulk customer imports from your LMS system or initial data migration.
Upcoming Contract Changes
This endpoint shares its per-customer payload with Create a Customer, so the upcoming required-field changes apply here identically:birthday, ssnLastFour, and state become required;
email/phoneNumber and timezone/zipCode become at-least-one pairs; and loanId is removed in
favor of loanIds.
The fields documented on this page reflect what is enforced today, and we will give partners ample
time to adjust before introducing any breaking changes. See Upcoming API
Changes for the new contract and migration examples.
Request Body Format
The batch endpoint accepts an array of customer records wrapped in acustomers field:
{
"customers": [
{
"id": "CUST123",
"firstName": "John",
"lastName": "Doe",
"email": "[email protected]",
"birthday": "1990-01-15",
"ssnLastFour": "1234",
"phoneNumber": "+15551234567",
"timezone": "America/New_York",
"taskType": "Collections",
"streetAddress1": "123 Main St",
"streetAddress2": "Apt 4B",
"city": "New York",
"state": "NY",
"zipCode": "10001",
"doNotCall": false,
"externalMetadata": {
"employer": "Example employer",
"paySchedule": "biweekly"
}
},
{
"id": "CUST124",
"firstName": "Jane",
"lastName": "Smith",
"email": "[email protected]",
"birthday": "1985-05-20",
"ssnLastFour": "5678",
"phoneNumber": "+15559876543",
"timezone": "America/Los_Angeles",
"taskType": "Collections",
"streetAddress1": "456 Oak Ave",
"city": "Los Angeles",
"state": "CA",
"zipCode": "90001",
"doNotCall": false
}
]
}
Individual Customer Schema
Each object in thecustomers array follows the same schema as the single customer endpoint:
Always required:
id- External customer reference ID (required)firstName- Customer’s first name (required)lastName- Customer’s last name (required)phoneNumber- Customer’s phone number (required) - Used for automated calling and SMS communicationstimezone- Customer’s timezone as an IANA timezone name (e.g., “America/New_York”, “America/Los_Angeles”, “America/Chicago”) (required) - Required for TCPA compliance to ensure calls are made during appropriate hours. Must be a valid IANA timezone identifier.taskType- Task type for customer’s call schedule (required) - Valid task types depend on your company configuration. A company-invalid task type fails the row during processing with a422:message listing the valid types. An accepted batch still returns HTTP202.
sync: true:
email- Customer email address (required)streetAddress1- Street address line 1 (required)city- City name (required)state- State code (e.g., “NY”, “CA”) (required)zipCode- ZIP/postal code (required)
birthday- Customer birthday (YYYY-MM-DD format) - Used for identity verification on calls. Will become required — see Upcoming API Changes.ssnLastFour- Last 4 digits of SSN - Used for identity verification on calls. Will become required — see Upcoming API Changes.streetAddress2- Street address line 2doNotCall- Whether to mark customer for Do Not Call (default: false)externalMetadata- Custom JSON object with additional customer dataloanIds- Array of loan IDs to import for the customer (e.g.,[123, 456, 789]). If provided, only the specified loans will be imported. If omitted, all loans for the customer will be imported. Pass a single-element array to import exactly one loan.sync- Whentrue, auto-fetch missing customer data from your company’s LMS (default:false). See the Create a Customer endpoint for details.isTest- Mark customer as test customer (default: false)
loanId- Does not select loans to import; useloanIdsfor that. On rows with an origination task type for companies configured for origination boarding,loanIdis read as the LMS application reference; on all other rows it is accepted and then ignored.
Response Format
The batch endpoint immediately returns a job ID for tracking:{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"totalCustomers": 100,
"message": "Batch job created with 4 parallel batches. Use GET /customers/batch/550e8400-e29b-41d4-a716-446655440000 to check status."
}
Response Fields
- jobId - Unique identifier for the batch job. Use this to check status via
GET /customers/batch/{jobId} - totalCustomers - Total number of customers queued for processing
- message - Status message with instructions for checking progress
Checking Job Status
UseGET /customers/batch/{jobId} to check the processing status:
{
"jobId": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"totalCustomers": 100,
"processed": 98,
"failed": 2,
"progressPercentage": 100.0,
"createdAt": "2026-08-17T10:30:00+00:00",
"processingStartedAt": "2026-08-17T10:30:05+00:00",
"processingCompletedAt": "2026-08-17T10:32:11+00:00",
"errorMessage": null,
"failures": [
{
"index": 4,
"customerId": "CUST127",
"message": "422: Missing required fields: phoneNumber"
},
{
"index": 31,
"customerId": "CUST154",
"message": "422: Invalid task type. Valid types for this company: ['Collections']"
}
]
}
Status Values
- pending - Job is queued but not yet started
- processing - Job is currently processing customers
- completed - Job has finished processing all customers. Individual customers may still have failed — check
failures - failed - Job encountered a fatal error
status: completed does not mean every customer succeeded — it means the job
finished. Always reconcile against failed and failures rather than treating
completed as success.Failures
failures lists every customer that could not be created — it is never truncated,
however many there are. Successful rows are not enumerated; they are the set you submitted
minus these.
| Field | Description |
|---|---|
index | 0-based position of the row in the customers array you submitted. -1 if the position could not be determined |
customerId | External customer reference ID, exactly as submitted |
message | Why the row was rejected, prefixed with the status code the equivalent single POST /customers call would have returned |
index is the position in your array, not within a server-side batch, so you can walk
failures straight against your source file.
Common failure messages:
422: Missing required fields: ...— a required field was absent for that customer422: Invalid task type. Valid types for this company: [...]—taskTypeisn’t configured for your company422: Invalid phone number. Phone number must contain at least one digit.— the number contained no digits422: State ... is not supported for ...— a state restriction applies to your company502: sync=true but failed to fetch customer data from LMSor502: LMS returned invalid customer data— an LMS dependency failure, not invalid caller input500: Internal error processing this customer— an internal failure. Check the customer’s current state before resubmitting; the error alone does not prove that no writes occurred
failures fills in in bursts and is partial while status is
processing. Poll until status is completed or failed before treating the list as
the final record. The array is always sorted by index regardless of which batch finished
first.
failures was added in August 2026. Jobs that ran before then report an empty array even
if they had failures — the per-row detail was not retained at the time.Partial Success Handling
The batch endpoint uses partial success handling to ensure maximum processing:- Individual failures don’t stop processing - If one customer fails, the remaining customers are still processed
- Detailed tracking - The
failedcount shows how many customers failed, andfailuresnames each one - Nothing is truncated - Every failed row is reported, however many there are
- Transaction isolation - Each customer is processed in its own transaction to prevent one failure from affecting others
What the Endpoint Does
When you submit a batch request, Finosu:- Queues the job - Stores customer data and creates a background job
- Returns immediately - Returns HTTP 202 with job ID for status tracking
- Processes asynchronously - For each customer in parallel batches:
- Validates the customer data (required fields, state restrictions, task type)
- Creates the customer, or updates it in place when a customer with the same
idalready exists for your company — the same upsert behavior as the single endpoint - Creates call schedule for supported task types (e.g., Collections)
id, resubmitting failed rows (or the whole batch)
does not create duplicate customers.
When to Use Batch vs Single
Use the batch endpoint when:- Importing multiple customers from your LMS system
- Performing initial data migration
- You have more than 5-10 customers to create
- You want to minimize API calls for efficiency
- Creating individual customers in real-time
- You only have 1-2 customers to create
- You need simpler error handling
- Testing or debugging individual customer creation
Error Handling
An accepted batch returns HTTP202 Accepted, with a job ID for GET /customers/batch/{jobId}. Invalid request envelopes return HTTP 422 before queuing. Failures discovered during processing appear per row in the status response; a 422: message prefix does not change the batch HTTP status.
Individual customer errors during processing may include:
- 422 UNPROCESSABLE CONTENT - Missing required fields, invalid task type, or state restrictions
- 502 BAD GATEWAY - LMS fetch failure or malformed provider data (malformed optional birthdays are skipped)
- 500 INTERNAL SERVER ERROR - Server error during processing
GET /customers/batch/{jobId} to monitor the processed and failed counts during processing, and read the failures array for the specific rows that were rejected and why.
Authentication
Authentication via X-API-Key header:- API key is company-scoped
- Must be obtained from Finosu
- Same authentication as single customer endpoint
Example Usage
This endpoint is typically used for:- Bulk customer import - Import multiple customers from your loan management system
- Initial setup - Create multiple customers during initial platform setup
- Scheduled jobs - Automated daily/weekly customer sync workflows
- Data migration - Migrate customer data from legacy systems
Headers
Body
application/json
Choose origination list enrollment or legacy customer creation. Enrollment row fields are validated individually by the worker; invalid rows are reported in results.
Response
Batch job accepted for async processing. Use GET /customers/batch/{jobId} to check status.