List daily billing costs
curl --request GET \
--url https://api.finosu.com/billing/daily-costs \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.finosu.com/billing/daily-costs"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://api.finosu.com/billing/daily-costs', 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/billing/daily-costs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"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"
"net/http"
"io"
)
func main() {
url := "https://api.finosu.com/billing/daily-costs"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-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.get("https://api.finosu.com/billing/daily-costs")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.finosu.com/billing/daily-costs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"date": "2023-12-25",
"currency": "USD",
"rows": [
{
"loanId": "5800001",
"customerId": "6600001",
"applicationStatus": "PENDING",
"callCount": "3.00",
"callMinutes": "4.00",
"callSeconds": "179.00",
"telephonyCost": "0.1200",
"virtualAgentCost": "0.3282",
"smsCount": "1.00",
"smsSegments": "1.00",
"smsCost": "0.0090",
"emailCount": "1.00",
"emailCost": "0.0010",
"portalCollected": "0.00",
"externalCollected": "0.00",
"paymentProcessingFee": "0.0000",
"totalCost": "0.4582"
}
],
"nextCursor": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}Billing
List Daily Billing Costs
Retrieve each loan’s charges for one day, for reporting cost per loan alongside your own loan data.
GET
/
billing
/
daily-costs
List daily billing costs
curl --request GET \
--url https://api.finosu.com/billing/daily-costs \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.finosu.com/billing/daily-costs"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://api.finosu.com/billing/daily-costs', 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/billing/daily-costs",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"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"
"net/http"
"io"
)
func main() {
url := "https://api.finosu.com/billing/daily-costs"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-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.get("https://api.finosu.com/billing/daily-costs")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.finosu.com/billing/daily-costs")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"date": "2023-12-25",
"currency": "USD",
"rows": [
{
"loanId": "5800001",
"customerId": "6600001",
"applicationStatus": "PENDING",
"callCount": "3.00",
"callMinutes": "4.00",
"callSeconds": "179.00",
"telephonyCost": "0.1200",
"virtualAgentCost": "0.3282",
"smsCount": "1.00",
"smsSegments": "1.00",
"smsCost": "0.0090",
"emailCount": "1.00",
"emailCost": "0.0010",
"portalCollected": "0.00",
"externalCollected": "0.00",
"paymentProcessingFee": "0.0000",
"totalCost": "0.4582"
}
],
"nextCursor": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}Use
In this example the first loan had three calls (47 seconds, 132 seconds, and one that did not connect), one text, and one email. The second and third loans are two open applications from the same customer. That customer’s 95-second scheduled call was placed for the second loan, so it carries the whole call. The customer also called in for 60 seconds, which names no loan, so that call is shared evenly: half on each application. The second loan also had a
GET /billing/daily-costs to retrieve one row per loan that had billable activity on a given day: the same charges as the Billing Summary in the client dashboard, broken out by day instead of by month. Loans with no calls, texts, emails, or payments that day are omitted.
The response is scoped to the company associated with your X-API-Key. A key belongs to exactly one company, so a lender with several brands makes one request per brand, using that brand’s key. Only first-party loans on usage billing are included; a company that is not billed per use receives 400.
loanId is the loan identifier you supplied at enrollment or in your file upload, so rows join directly to your loan records. customerId is your customer reference, or the Finosu customer UUID for a customer boarded without one. A typical use is to sum a brand’s rows over a week and divide by that week’s transfers or funded loans.
First request
curl --fail-with-body --get 'https://api.finosu.com/billing/daily-costs' \
--header "X-API-Key: $FINOSU_API_KEY" \
--data-urlencode 'date=2026-10-27'
{
"date": "2026-10-27",
"currency": "USD",
"rows": [
{
"loanId": "5800001",
"customerId": "6600001",
"applicationStatus": "PENDING",
"callCount": "3.00",
"callMinutes": "4.00",
"callSeconds": "179.00",
"telephonyCost": "0.1200",
"virtualAgentCost": "0.3282",
"smsCount": "1.00",
"smsSegments": "1.00",
"smsCost": "0.0090",
"emailCount": "1.00",
"emailCost": "0.0010",
"portalCollected": "0.00",
"externalCollected": "0.00",
"paymentProcessingFee": "0.0000",
"totalCost": "0.4582"
},
{
"loanId": "5800002",
"customerId": "6600002",
"applicationStatus": "FUNDED",
"callCount": "1.50",
"callMinutes": "2.50",
"callSeconds": "125.00",
"telephonyCost": "0.0750",
"virtualAgentCost": "0.2292",
"smsCount": "0.00",
"smsSegments": "0.00",
"smsCost": "0.0000",
"emailCount": "0.00",
"emailCost": "0.0000",
"portalCollected": "150.00",
"externalCollected": "0.00",
"paymentProcessingFee": "6.0000",
"totalCost": "6.3042"
},
{
"loanId": "5800003",
"customerId": "6600002",
"applicationStatus": "PENDING",
"callCount": "0.50",
"callMinutes": "0.50",
"callSeconds": "30.00",
"telephonyCost": "0.0150",
"virtualAgentCost": "0.0550",
"smsCount": "0.00",
"smsSegments": "0.00",
"smsCost": "0.0000",
"emailCount": "0.00",
"emailCost": "0.0000",
"portalCollected": "0.00",
"externalCollected": "0.00",
"paymentProcessingFee": "0.0000",
"totalCost": "0.0700"
}
],
"nextCursor": null
}
$150.00 payment collected through Finosu.
All numeric values are decimal strings, so you can add them without floating-point error. Each row is rounded on its own: counts, minutes, seconds, and collected amounts to 2 decimal places, charges to 4. A shared communication can therefore add up to slightly more or less than its whole; one call shared across three loans shows callCount 0.33 on each row, 0.99 in total. totalCost is exactly the sum of the row’s five charges.
How charges are calculated
Every call minute carries two charges: telephony and virtual agent. Rates are the ones in your Finosu agreement and are not repeated in the response.| Charge | Calculation |
|---|---|
| Telephony | Each call with a duration is rounded up to the next whole minute, then priced per minute. Calls that did not connect have no duration and no telephony charge, but are counted in callCount. |
| Virtual agent | Exact call seconds, priced per minute at the company’s tiered virtual-agent rate. See Daily rows and the monthly bill. |
| SMS | Sent outbound messages only, priced per segment, plus the carrier’s per-message fee passed through at cost. |
| Sent emails, priced per email. | |
| Payment processing | A percentage of payments collected through Finosu (portalCollected). Payments recorded as collected outside Finosu (externalCollected) are reported but not charged. |
Customers with more than one loan
Each scheduled call, text, and email is charged to the loan it was scheduled for, as long as that loan belongs to the same customer and is open on the day. When a customer has more than one loan open, for example two applications, each loan carries only its own outreach. Any other communication, such as an inbound call, one sent outside a loan’s schedule, or one scheduled for a loan that has since closed, is shared evenly across the customer’s loans open that day. That is the only reason counts such ascallCount can be fractional. Payments are recorded against a specific loan and are never shared.
Sharing never changes totals beyond that per-row rounding: the sum of a brand’s rows for a day is the brand’s full cost for that day, to within a fraction of a cent per shared communication.
Daily rows and the monthly bill
Summing a month of daily rows reproduces the month’s Billing Summary charges, within the limits below.- Telephony, SMS, email, and payment processing are priced per item, so daily amounts add up to the monthly amount.
- Virtual agent pricing is tiered on the company’s cumulative minutes in the month. Each day is priced at the rate that applied to the minutes used that day, so a day’s charge never changes when a later day crosses into a lower tier, and a month of days adds up to the company’s monthly tiered total. Within a month that crosses a tier, the Billing Summary applies the month’s average rate to every loan, so an individual loan’s month can differ slightly from the sum of its days.
- Communications that name no loan: a day shares them across the loans open that day, while the monthly Billing Summary shares them across the loans open at any point in the month. If such a customer gains or loses a loan mid-month, that customer’s daily rows can differ from the monthly figure by a few cents.
- Rounding: each row’s charges are rounded to 4 decimal places, and the monthly Billing Summary rounds once per loan. A month of daily rows can differ from the monthly figure by up to
$0.00005per row for each charge.
When a day is final
-
Only completed days can be requested. Today or a future date returns
422. -
Calls, texts, and emails for a day are final once the day has ended in America/New_York. The carrier fee on a text can arrive a few minutes after the message is sent, so request day
Dafter 06:00 America/New_York on dayD + 1. -
Payment fields can change after the day ends. A bank payment that is later returned no longer counts as collected, which lowers
portalCollectedandpaymentProcessingFeeon the day it was originally collected. Most returns arrive within 2 banking days, but a return the borrower reports as unauthorized can arrive up to 60 days after the payment. To stay current:- on each daily pull, re-request the previous 7 days, and
- once a week, re-request each of the previous 70 days, which covers the 60-day return window plus the gap between weekly pulls,
-
applicationStatusis current, not historical. It is the loan’s status when you make the request, so re-requesting an old day can show a loan that has since funded asFUNDED.
Retrieve every page
Rows are ordered byloanId, then by Finosu’s internal loan identifier to break ties. limit defaults to 1000 and accepts 1–1000. When nextCursor contains a string, pass that exact value as the next request’s cursor with the same date; a null cursor means the day is complete. Treat cursors as opaque and URL-encode them. A day with no activity returns {"date": "...", "currency": "USD", "rows": [], "nextCursor": null}.
The endpoint allows 60 requests per 60-second window per company, shared across its API keys. If the quota is exhausted (429) or its enforcement is temporarily unavailable (503), wait for the Retry-After interval before retrying the same page. Successful responses are sent with Cache-Control: no-store.
import os
import requests
def daily_costs(day: str):
params = {"date": day}
while True:
response = requests.get(
"https://api.finosu.com/billing/daily-costs",
params=params,
headers={"X-API-Key": os.environ["FINOSU_API_KEY"]},
timeout=60,
)
response.raise_for_status()
body = response.json()
yield from body["rows"]
if body["nextCursor"] is None:
return
params["cursor"] = body["nextCursor"]
Authorizations
Query Parameters
Calendar day in America/New_York, YYYY-MM-DD. Must be a completed day: today or a future date returns 422.
Maximum rows per page. Default 1000; range 1–1000.
Required range:
1 <= x <= 1000Opaque nextCursor from the preceding response. Keep the same company and date.
Required string length:
1 - 2048Response
A page of per-loan daily costs. nextCursor is null on the last page.