List all calls
curl --request GET \
--url https://api.finosu.com/calls \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.finosu.com/calls"
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/calls', 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/calls",
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/calls"
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/calls")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.finosu.com/calls")
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{
"calls": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"customerId": "<string>",
"fromPhoneNumber": "<string>",
"toPhoneNumber": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"callType": "INBOUND",
"status": "CALL_ENDED",
"callResult": "CONNECTED",
"duration": 123,
"transcript": "<string>"
}
],
"nextCursor": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}{
"detail": "<string>"
}{
"detail": "<string>"
}Calls
List All Calls
Retrieve your company’s call history with cursor pagination.
GET
/
calls
List all calls
curl --request GET \
--url https://api.finosu.com/calls \
--header 'X-API-Key: <api-key>'import requests
url = "https://api.finosu.com/calls"
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/calls', 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/calls",
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/calls"
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/calls")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.finosu.com/calls")
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{
"calls": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"customerId": "<string>",
"fromPhoneNumber": "<string>",
"toPhoneNumber": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"callType": "INBOUND",
"status": "CALL_ENDED",
"callResult": "CONNECTED",
"duration": 123,
"transcript": "<string>"
}
],
"nextCursor": "<string>"
}{
"detail": "<string>"
}{
"detail": "<string>"
}{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}{
"detail": "<string>"
}{
"detail": "<string>"
}Use
Newer calls created during pagination appear in a fresh traversal; they do not shift subsequent pages. This is a live history, not a frozen snapshot: existing call outcomes and artifacts can change as processing finishes, and backfilled or deleted records can change the available history. Deduplicate exports by
Dates must include a timezone (
Errors use FastAPI’s
GET /calls to retrieve call records across all customers in the company associated with your X-API-Key. No customer ID is required, and there is no default date cutoff.
The response includes inbound calls, outbound calls, uploaded recordings, and unsuccessful attempts, including records still being processed. Test-customer calls are included. Deleted calls, calls belonging to deleted customers, and legacy calls without a creation timestamp are excluded. For calls made to borrowers, use callType=OUTBOUND. Future scheduled calls are available through the Scheduled Calls API.
First request
curl --fail-with-body --get 'https://api.finosu.com/calls' \
--header "X-API-Key: $FINOSU_API_KEY" \
--data-urlencode 'limit=50' \
--data-urlencode 'callType=OUTBOUND'
{
"calls": [
{
"id": "c2790fa1-d9aa-4510-b80b-22e0c4b94a4e",
"customerId": "customer-123",
"fromPhoneNumber": "+15550000001",
"toPhoneNumber": "+15550000002",
"timestamp": "2026-09-01T12:00:00Z",
"callType": "OUTBOUND",
"status": "CALL_ENDED",
"callResult": "CONNECTED",
"duration": 60,
"transcript": "Example conversation transcript."
}
],
"nextCursor": null
}
id is the stable Finosu call UUID, also accepted by Get Call by ID. customerId is your external customer reference, falling back to the Finosu customer UUID when no reference is stored. duration is in seconds. timestamp is the call record’s creation time. status describes its processing stage; callResult describes its outcome. Outcomes, duration, transcripts, and phone numbers can be null when unavailable. Fetch a call by ID for its recording link; recordings are omitted from the bulk listing so exports do not require a storage lookup for every call. Recording links expire after one hour and can be refreshed by fetching the call again.
Retrieve every page
Calls are ordered newest first by creation time, then call UUID to break ties.limit defaults to 50 and accepts 1–100. Pagination happens in the database. 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.
When nextCursor contains a string, pass that exact value as the next request’s cursor. Keep the same API-key company and filters on every page; you may change limit. Treat cursors as opaque and URL-encode them. A null cursor means the export is complete. An empty result is {"calls": [], "nextCursor": null}.
This Python example writes one call per line to a private JSON Lines file without accumulating the entire history in memory or logging call data. It requires the requests package, a FINOSU_API_KEY environment variable, and FINOSU_CALL_EXPORT_PATH set to a new file in an access-controlled directory. On POSIX systems, the file is readable and writable only by its owner; on other systems, configure equivalent directory permissions. The example refuses to overwrite an existing file. Treat the export as sensitive data: it contains customer identifiers, phone numbers, and transcripts. The example retries 429 and 503 responses up to four times per page, respecting Retry-After. A failed request can leave a partial export.
import json
import os
import requests
from requests.adapters import HTTPAdapter
from urllib3.util import Retry
params = {"limit": 100} # Add "callType": "OUTBOUND" for outbound calls only.
export_fd = os.open(
os.environ["FINOSU_CALL_EXPORT_PATH"],
os.O_WRONLY | os.O_CREAT | os.O_EXCL,
0o600,
)
with os.fdopen(export_fd, "w", encoding="utf-8") as output, requests.Session() as session:
session.headers["X-API-Key"] = os.environ["FINOSU_API_KEY"]
session.mount(
"https://api.finosu.com/",
HTTPAdapter(max_retries=Retry(
total=4,
allowed_methods={"GET"},
status_forcelist={429, 503},
backoff_factor=1,
respect_retry_after_header=True,
)),
)
while True:
response = session.get(
"https://api.finosu.com/calls", params=params, timeout=60
)
response.raise_for_status()
page = response.json()
for call in page["calls"]:
output.write(json.dumps(call) + "\n")
if page["nextCursor"] is None:
break
params["cursor"] = page["nextCursor"]
id. Use call webhooks for completed-call updates, or reread an overlapping creation-time window to refresh recent records.
Filter the history
All filters are optional and combined:| Parameter | Meaning |
|---|---|
customerId | Exact external customer reference (the same identifier used by the customer-specific route). |
callType | INBOUND, OUTBOUND, or UPLOAD. Omit for all types. |
createdAfter | Inclusive creation-time lower bound, such as 2026-09-01T00:00:00Z. |
createdBefore | Exclusive creation-time upper bound, such as 2026-10-01T00:00:00Z. |
Z or an explicit offset). createdAfter must precede createdBefore. These filters use creation time, not last-updated time. Unknown customer references return an empty list.
curl --fail-with-body --get 'https://api.finosu.com/calls' \
--header "X-API-Key: $FINOSU_API_KEY" \
--data-urlencode 'callType=OUTBOUND' \
--data-urlencode 'createdAfter=2026-09-01T00:00:00Z' \
--data-urlencode 'createdBefore=2026-10-01T00:00:00Z'
Errors
| Status | Meaning |
|---|---|
400 | Invalid cursor, cursor from a different company/filter set, or invalid date range. |
401 | Missing, invalid, inactive, deleted, or empty API key. |
422 | Invalid query value, including limits outside 1–100 or dates without timezones. |
429 | Company request quota exhausted. Retry the same page after the Retry-After interval (60 seconds). |
503 | Request quota enforcement is temporarily unavailable. Retry the same page after the Retry-After interval (60 seconds). |
detail field. Validation errors contain an array of field errors; authentication, cursor/range, and quota errors contain a string. Existing call-by-ID and customer-specific routes retain their current response shapes.Authorizations
Query Parameters
Maximum records per page. Default 50; range 1–100.
Required range:
1 <= x <= 100Opaque nextCursor from the preceding response. Keep the same company and filters.
Required string length:
1 - 2048Exact external customer reference. Unknown references return an empty list.
Required string length:
1 - 255Filter by call type; omit for all types.
Available options:
INBOUND, OUTBOUND, UPLOAD Inclusive creation-time lower bound. Must include a timezone and precede createdBefore.
Exclusive creation-time upper bound. Must include a timezone.