curl --request POST \
--url https://api.finosu.com/external-payments \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"loanId": "ACCOUNT-1001",
"transactionId": "lms-payment-001",
"sourceSystem": "example-lms",
"amount": "125.25",
"currency": "USD",
"paymentDate": "2026-10-16",
"status": "settled",
"settlesLoan": false
}
'import requests
url = "https://api.finosu.com/external-payments"
payload = {
"loanId": "ACCOUNT-1001",
"transactionId": "lms-payment-001",
"sourceSystem": "example-lms",
"amount": "125.25",
"currency": "USD",
"paymentDate": "2026-10-16",
"status": "settled",
"settlesLoan": False
}
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({
loanId: 'ACCOUNT-1001',
transactionId: 'lms-payment-001',
sourceSystem: 'example-lms',
amount: '125.25',
currency: 'USD',
paymentDate: '2026-10-16',
status: 'settled',
settlesLoan: false
})
};
fetch('https://api.finosu.com/external-payments', 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/external-payments",
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([
'loanId' => 'ACCOUNT-1001',
'transactionId' => 'lms-payment-001',
'sourceSystem' => 'example-lms',
'amount' => '125.25',
'currency' => 'USD',
'paymentDate' => '2026-10-16',
'status' => 'settled',
'settlesLoan' => false
]),
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/external-payments"
payload := strings.NewReader("{\n \"loanId\": \"ACCOUNT-1001\",\n \"transactionId\": \"lms-payment-001\",\n \"sourceSystem\": \"example-lms\",\n \"amount\": \"125.25\",\n \"currency\": \"USD\",\n \"paymentDate\": \"2026-10-16\",\n \"status\": \"settled\",\n \"settlesLoan\": false\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/external-payments")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"loanId\": \"ACCOUNT-1001\",\n \"transactionId\": \"lms-payment-001\",\n \"sourceSystem\": \"example-lms\",\n \"amount\": \"125.25\",\n \"currency\": \"USD\",\n \"paymentDate\": \"2026-10-16\",\n \"status\": \"settled\",\n \"settlesLoan\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.finosu.com/external-payments")
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 \"loanId\": \"ACCOUNT-1001\",\n \"transactionId\": \"lms-payment-001\",\n \"sourceSystem\": \"example-lms\",\n \"amount\": \"125.25\",\n \"currency\": \"USD\",\n \"paymentDate\": \"2026-10-16\",\n \"status\": \"settled\",\n \"settlesLoan\": false\n}"
response = http.request(request)
puts response.read_body{
"paymentTransactionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"transactionId": "<string>",
"loanId": "<string>",
"amount": "<string>",
"currency": "USD",
"paymentDate": "2023-12-25",
"status": "recorded",
"replayed": true,
"overpayment": true,
"settledLoan": true
}{
"paymentTransactionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"transactionId": "<string>",
"loanId": "<string>",
"amount": "<string>",
"currency": "USD",
"paymentDate": "2023-12-25",
"status": "recorded",
"replayed": true,
"overpayment": true,
"settledLoan": true
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}Record External Payment
Record money already collected and settled in a lender/LMS. Requires the exact company-owned loan/account identifier. This creates no Finosu processor charge. The company-scoped transactionId is the atomic idempotency identity: exact retries return 200 and the original transaction; changed payloads return 409. Only settled USD payments are supported; pending, returns, reversals, refunds, negative adjustments, and corrections are rejected.
curl --request POST \
--url https://api.finosu.com/external-payments \
--header 'Content-Type: application/json' \
--header 'X-API-Key: <api-key>' \
--data '
{
"loanId": "ACCOUNT-1001",
"transactionId": "lms-payment-001",
"sourceSystem": "example-lms",
"amount": "125.25",
"currency": "USD",
"paymentDate": "2026-10-16",
"status": "settled",
"settlesLoan": false
}
'import requests
url = "https://api.finosu.com/external-payments"
payload = {
"loanId": "ACCOUNT-1001",
"transactionId": "lms-payment-001",
"sourceSystem": "example-lms",
"amount": "125.25",
"currency": "USD",
"paymentDate": "2026-10-16",
"status": "settled",
"settlesLoan": False
}
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({
loanId: 'ACCOUNT-1001',
transactionId: 'lms-payment-001',
sourceSystem: 'example-lms',
amount: '125.25',
currency: 'USD',
paymentDate: '2026-10-16',
status: 'settled',
settlesLoan: false
})
};
fetch('https://api.finosu.com/external-payments', 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/external-payments",
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([
'loanId' => 'ACCOUNT-1001',
'transactionId' => 'lms-payment-001',
'sourceSystem' => 'example-lms',
'amount' => '125.25',
'currency' => 'USD',
'paymentDate' => '2026-10-16',
'status' => 'settled',
'settlesLoan' => false
]),
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/external-payments"
payload := strings.NewReader("{\n \"loanId\": \"ACCOUNT-1001\",\n \"transactionId\": \"lms-payment-001\",\n \"sourceSystem\": \"example-lms\",\n \"amount\": \"125.25\",\n \"currency\": \"USD\",\n \"paymentDate\": \"2026-10-16\",\n \"status\": \"settled\",\n \"settlesLoan\": false\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/external-payments")
.header("X-API-Key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"loanId\": \"ACCOUNT-1001\",\n \"transactionId\": \"lms-payment-001\",\n \"sourceSystem\": \"example-lms\",\n \"amount\": \"125.25\",\n \"currency\": \"USD\",\n \"paymentDate\": \"2026-10-16\",\n \"status\": \"settled\",\n \"settlesLoan\": false\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.finosu.com/external-payments")
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 \"loanId\": \"ACCOUNT-1001\",\n \"transactionId\": \"lms-payment-001\",\n \"sourceSystem\": \"example-lms\",\n \"amount\": \"125.25\",\n \"currency\": \"USD\",\n \"paymentDate\": \"2026-10-16\",\n \"status\": \"settled\",\n \"settlesLoan\": false\n}"
response = http.request(request)
puts response.read_body{
"paymentTransactionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"transactionId": "<string>",
"loanId": "<string>",
"amount": "<string>",
"currency": "USD",
"paymentDate": "2023-12-25",
"status": "recorded",
"replayed": true,
"overpayment": true,
"settledLoan": true
}{
"paymentTransactionId": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"transactionId": "<string>",
"loanId": "<string>",
"amount": "<string>",
"currency": "USD",
"paymentDate": "2023-12-25",
"status": "recorded",
"replayed": true,
"overpayment": true,
"settledLoan": true
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}Authentication and account identity
Send the company API key inX-API-Key. The key determines the company scope;
company or customer IDs in the request cannot widen it.
loanId is required and must be the exact company-owned external loan/account
identifier. A customer identifier is not accepted because one customer can have
multiple loans. Unknown or other-company identifiers return 404. If legacy data
makes an identifier ambiguous within the authenticated company, Finosu returns
409 and records nothing.
Request
{
"loanId": "ACCOUNT-1001",
"transactionId": "lms-payment-001",
"sourceSystem": "example-lms",
"amount": "125.25",
"currency": "USD",
"paymentDate": "2026-10-16",
"status": "settled",
"settlesLoan": false
}
transactionIdis the lender’s stable transaction identity. It must be unique across every feed used by the authenticated company, not merely within one loan.sourceSystemis a stable lowercase LMS identifier using letters, digits,.,_, or-. Finosu stores it as payment provenance.amountis a positive USD decimal no greater than21474836.47, with at most two fractional digits. JSON strings are recommended to preserve decimal precision.paymentDateis the settlement date in ISOYYYY-MM-DDformat.statusmust besettled. Pending, submitted, failed, returned, reversed, or refunded events are not accepted by this contract.- Set
settlesLoantotrueonly after the lender has finalized this payment as full payoff or negotiated settlement. It invokes the existing external settlement flow, including stopping servicing and pending communications.
Idempotency and conflicts
No separate idempotency header is needed. Finosu atomically claims(authenticated company, transactionId) in the payment transaction itself.
- The first accepted request returns
201andstatus: "recorded". - An exact retry returns
200, the originalpaymentTransactionId, andstatus: "duplicate". It does not apply money or settlement effects again. - Reusing the same
transactionIdwith a changed loan, source, amount, date, currency, status, orsettlesLoanvalue returns409. The original payment remains unchanged.
Accounting and integration behavior
The payment is stored as a successfulEXTERNAL_IMPORT transaction and uses
the same scheduled-payment allocation and external-settlement services as the
canonical CSV flow. Rollover rows may be created when one payment covers
multiple installments, but their total equals the request amount. Finosu does
not create an AchPayment, payment method, gateway transaction, or processor
charge.
Imports dated before boarding remain pre-boarding history and are not allocated
to a later Finosu payment plan. Existing balance calculations exclude that
history where the transferred balance already includes it, preventing double
counting. Post-boarding imports participate in normal balance, reporting, and
payoff calculations.
EXTERNAL_IMPORT payments are ineligible for LMS payment writeback. The stored
source provenance also identifies the originating LMS, preventing the import
from being echoed back as a new Finosu-originated payment.
Response
{
"paymentTransactionId": "17f82780-3c2d-4e71-bbad-03ca7f713e4f",
"transactionId": "lms-payment-001",
"loanId": "ACCOUNT-1001",
"amount": "125.25",
"currency": "USD",
"paymentDate": "2026-10-16",
"status": "recorded",
"replayed": false,
"overpayment": false,
"settledLoan": false
}
Returns, reversals, and corrections
Do not represent a return, reversal, or correction by sending a negative amount or by reusingtransactionId with changed data; both are rejected. The existing
/ach-returns API is only for ACH payments Finosu originated and is not a
general reversal API.
Until a separate immutable external-adjustment contract is published, contact
Finosu support for a reviewed correction. This endpoint never deletes or edits
an existing financial record.
Errors
401— missing, invalid, or inactiveX-API-Key404—loanIdis not owned by the authenticated company409— ambiguousloanId, or conflicting reuse oftransactionId422— invalid identifier, amount, currency, date, status, or request shape500— the atomic payment/accounting operation failed and was rolled back
Authorizations
Body
A successful payment already settled outside Finosu. CamelCase is preferred; matching snake_case aliases are accepted. Extra fields are rejected.
Exact company-owned external loan/account identifier.
1 - 255Stable lender transaction identity, unique across the authenticated company.
1 - 200Stable lowercase identifier of the originating LMS.
1 - 50^[a-z0-9][a-z0-9._-]*$Positive USD amount no greater than 21474836.47, with at most two decimal places.
^(?!0+(?:\.0{1,2})?$)\d{1,8}(?:\.\d{1,2})?$USD Settlement date in YYYY-MM-DD format.
settled True only for a lender-finalized full payoff or negotiated settlement.
Response
Exact idempotent replay; no accounting or settlement effects were repeated.
USD recorded, duplicate