List todos
curl --request GET \
--url https://getbill.io/external-api/v1/todos \
--header 'Authorization: Bearer <token>'import requests
url = "https://getbill.io/external-api/v1/todos"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://getbill.io/external-api/v1/todos', 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://getbill.io/external-api/v1/todos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$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://getbill.io/external-api/v1/todos"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://getbill.io/external-api/v1/todos")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://getbill.io/external-api/v1/todos")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyTodos API
List todos
Manage payment agreements, debtor requests, and AI detections that require attention
GET
/
external-api
/
v1
/
todos
List todos
curl --request GET \
--url https://getbill.io/external-api/v1/todos \
--header 'Authorization: Bearer <token>'import requests
url = "https://getbill.io/external-api/v1/todos"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://getbill.io/external-api/v1/todos', 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://getbill.io/external-api/v1/todos",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$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://getbill.io/external-api/v1/todos"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://getbill.io/external-api/v1/todos")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://getbill.io/external-api/v1/todos")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyOverview
The Todo Lists API allows you to retrieve and manage todo items including:- Agreements - Active payment plans with debtors
- Debtor Requests - Portal submissions requiring human review (disputes, hardship declarations, payment plan proposals)
- AI Detections - Items flagged by AI during calls (disputes, objections)
The legacy type names
blocking_dispute and non_blocking_dispute are still accepted as query parameters and URL path segments for backward compatibility. However, all responses now use the canonical names debtor_request and ai_detection.Todo Types
Agreements (agreement)
Payment plans reached with debtors, including:
- Installment schedules
- Payment tracking
- Plan duration and start dates
- Grouped debts (multiple debts under a single payment plan return extra fields:
is_group,debt_count,total_amount,currency,debts)
Debtor Requests (debtor_request)
Items submitted by debtors through the portal. Collection is paused. Sub-types:
| Sub-type | Description |
|---|---|
portal_dispute | Debtor submitted a formal dispute with optional proof files |
hardship | Debtor declared financial hardship |
payment_plan | Debtor proposed a payment plan |
AI Detections (ai_detection)
Items flagged by AI during automated calls. Collection is not paused. Sub-types:
| Sub-type | Description |
|---|---|
ai_dispute | AI detected a potential dispute during a call |
objection | AI detected a debtor objection during a call |
Dispute Statuses
suggested- Pending reviewvalidated- Dispute has been validatedrejected- Dispute was rejected
List Todos
curl -X GET https://getbill.io/external-api/v1/todos \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json"
Query Parameters
| Parameter | Type | Description | Default |
|---|---|---|---|
page | integer | Page number | 1 |
limit | integer | Items per page (max 100) | 20 |
type | string | Filter by type: agreement, debtor_request, ai_detection, or all | all |
period | string | Time period: today, since_yesterday, since_last_week, since_last_month, since_last_year, since_beginning | since_last_month |
debt_id | string | Filter by encrypted debt ID | - |
Legacy type values
blocking_dispute and non_blocking_dispute are automatically mapped to debtor_request and ai_detection.Response
{
"todos": [
{
"id": "enc_456",
"type": "agreement",
"debt": {
"id": "enc_456",
"reference": "INV-2024-001",
"amount": 1500.00,
"status": "status.default.settled",
"status_id": "enc_3"
},
"plan_months": 6,
"plan_start_date": "2024-01-15T10:00:00+00:00",
"first_payment_amount": 250.00,
"installments": [
{
"id": "enc_789",
"amount": 250.00,
"payment_date": "2024-02-15T00:00:00+00:00",
"paid_at": null,
"status": "pending"
}
],
"created_at": "2024-01-15T10:00:00+00:00",
"followup_id": "enc_123"
},
{
"id": "enc_234",
"type": "debtor_request",
"sub_type": "portal_dispute",
"status": "suggested",
"severity": "urgent",
"title": "Payment Already Made",
"description": "Customer claims payment was already made",
"theme": "Payment Issues",
"detected_content": "I already paid this bill last month",
"ai_confidence": 85,
"proof_files": [
{
"filename": "receipt.pdf",
"url": "https://..."
}
],
"debt": {
"id": "enc_456",
"reference": "INV-2024-001",
"amount": 1500.00,
"status": "status.default.on_hold",
"status_id": "enc_2"
},
"created_at": "2024-01-15T10:30:00+00:00",
"collection_paused": true
},
{
"id": "enc_567",
"type": "ai_detection",
"sub_type": "ai_dispute",
"status": "suggested",
"severity": "review",
"title": "Payment Already Made",
"description": "Customer claims payment was already made",
"theme": "Payment Issues",
"detected_content": "I already paid this bill last month",
"ai_confidence": 78,
"source": "ai_detection",
"audio_url": "https://...",
"followup_id": "enc_125",
"call_id": "call_xyz123",
"debt": {
"id": "enc_789",
"reference": "INV-2024-002",
"amount": 800.00,
"status": "status.default.active",
"status_id": "enc_1"
},
"created_at": "2024-01-16T14:00:00+00:00",
"collection_paused": false
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 45,
"pages": 3
}
}
Grouped agreement fields
When an agreement covers multiple debts, the response includes additional fields:| Field | Type | Description |
|---|---|---|
is_group | boolean | true for grouped agreements |
debt_count | integer | Number of debts in the group |
total_amount | float | Combined amount across all debts |
currency | string | Currency code (e.g. EUR) |
debts | array | All debts in the group, each with id, reference, amount, status, status_id |
debt | object | First debt in the group (for backward compatibility) |
{
"id": "enc_group_1",
"type": "agreement",
"is_group": true,
"debt_count": 3,
"total_amount": 4500.00,
"currency": "EUR",
"debt": {
"id": "enc_456",
"reference": "INV-2024-001",
"amount": 1500.00,
"status": "status.default.settled",
"status_id": "enc_3"
},
"debts": [
{
"id": "enc_456",
"reference": "INV-2024-001",
"amount": 1500.00,
"status": "status.default.settled",
"status_id": "enc_3"
},
{
"id": "enc_457",
"reference": "INV-2024-002",
"amount": 3000.00,
"status": "status.default.settled",
"status_id": "enc_3"
}
],
"plan_months": 12,
"plan_start_date": "2024-01-15T10:00:00+00:00",
"first_payment_amount": 375.00,
"installments": [],
"created_at": "2024-01-15T10:00:00+00:00",
"followup_id": null
}
Debtor Request sub-type fields
Depending onsub_type, debtor requests include different fields:
portal_dispute: status, severity, title, description, theme, detected_content, ai_confidence, proof_files
hardship: employment_status, is_temporary, expected_recovery_period, check_in_date, check_in_due
payment_plan: monthly_amount, start_date, number_of_installments, first_payment_amount, debtor_message
AI Detection sub-type fields
ai_dispute: status, severity, title, description, theme, detected_content, ai_confidence, source, audio_url, followup_id, call_id
objection: objection_type, detected_content, ai_confidence, severity, theme, audio_url, followup_id, call_id
Error Responses
| Status Code | Description |
|---|---|
| 400 | Bad Request - Invalid parameters or type |
| 401 | Unauthorized - Invalid or missing API token |
| 403 | Forbidden - Company not authorized |
| 404 | Not Found - Todo or debt not found |
| 429 | Too Many Requests - Rate limit exceeded |
| 500 | Internal Server Error |
Example: Processing Todos
import requests
API_TOKEN = "your_api_token"
BASE_URL = "https://getbill.io/external-api/v1"
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Content-Type": "application/json"
}
# Get all debtor requests (collection paused, needs attention)
response = requests.get(
f"{BASE_URL}/todos",
headers=headers,
params={
"type": "debtor_request",
"period": "since_last_week"
}
)
todos = response.json()["todos"]
for todo in todos:
sub_type = todo.get("sub_type", "unknown")
if sub_type == "portal_dispute":
print(f"Dispute #{todo['id']}: {todo.get('title', 'N/A')}")
print(f" Debt on hold: {todo['debt']['reference']}")
print(f" Proof files: {len(todo.get('proof_files', []))}")
elif sub_type == "hardship":
print(f"Hardship #{todo['id']}")
print(f" Check-in due: {todo.get('check_in_due', False)}")
elif sub_type == "payment_plan":
print(f"Payment plan proposal #{todo['id']}")
print(f" Monthly amount: {todo.get('monthly_amount')}")
# Get AI detections
ai_response = requests.get(
f"{BASE_URL}/todos",
headers=headers,
params={
"type": "ai_detection",
"period": "since_last_week"
}
)
for todo in ai_response.json()["todos"]:
sub_type = todo.get("sub_type", "unknown")
print(f"AI {sub_type} #{todo['id']}: confidence {todo.get('ai_confidence')}%")
if todo.get("audio_url"):
print(f" Listen: {todo['audio_url']}")
# Auto-validate high-confidence AI disputes
if sub_type == "ai_dispute" and (todo.get("ai_confidence") or 0) > 80:
update_response = requests.put(
f"{BASE_URL}/todos/ai_detection/{todo['id']}/status",
headers=headers,
json={
"status": "validated",
"admin_notes": "Auto-validated due to high AI confidence"
}
)
if update_response.status_code == 200:
print(f" Validated")
# Quick stats overview
stats = requests.get(f"{BASE_URL}/todos/stats", headers=headers).json()["stats"]
print(f"\nOverdue agreements: {stats['agreements']['overdue']}")
print(f"Debtor requests: {stats['debtor_requests']['total']}")
print(f"AI detections: {stats['ai_detections']['total']}")
Webhooks
You can configure webhooks to be notified when:- New disputes are detected
- Agreements are created or modified
- Dispute status changes
Authentication
This endpoint requires a valid Bearer token with thedebts:read scope.
Rate Limits
- 1,000 requests per hour
Best Practices
- Process debtor requests first - These pause collection and need immediate attention
- Use period filters - Focus on recent todos to avoid processing old data
- Monitor agreement installments - Check for overdue payments regularly
- Include admin notes - Always document status changes for audit trail
- Use stats endpoint - Get a quick overview before fetching detailed lists
- Handle media URL expiry - Pre-signed URLs for audio/PDF expire after 1 hour; re-fetch if needed