Overview

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-typeDescription
portal_disputeDebtor submitted a formal dispute with optional proof files
hardshipDebtor declared financial hardship
payment_planDebtor proposed a payment plan

AI Detections (ai_detection)

Items flagged by AI during automated calls. Collection is not paused. Sub-types:
Sub-typeDescription
ai_disputeAI detected a potential dispute during a call
objectionAI detected a debtor objection during a call

Dispute Statuses

  • suggested - Pending review
  • validated - Dispute has been validated
  • rejected - Dispute was rejected

Endpoints

List Todos

curl -X GET https://api.getbill.io/external-api/v1/todos \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json"

Query Parameters

ParameterTypeDescriptionDefault
pageintegerPage number1
limitintegerItems per page (max 100)20
typestringFilter by type: agreement, debtor_request, ai_detection, or allall
periodstringTime period: today, since_yesterday, since_last_week, since_last_month, since_last_year, since_beginningsince_last_month
debt_idstringFilter 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:
FieldTypeDescription
is_groupbooleantrue for grouped agreements
debt_countintegerNumber of debts in the group
total_amountfloatCombined amount across all debts
currencystringCurrency code (e.g. EUR)
debtsarrayAll debts in the group, each with id, reference, amount, status, status_id
debtobjectFirst 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 on sub_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

Get Single Todo

curl -X GET https://api.getbill.io/external-api/v1/todos/debtor_request/enc_234 \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json"

Path Parameters

ParameterTypeDescription
typestringTodo type: agreement, debtor_request, or ai_detection
idstringEncrypted todo ID (for agreements: debt ID; for disputes: dispute ID)

Response

{
  "todo": {
    "id": "enc_234",
    "type": "debtor_request",
    "status": "suggested",
    "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,
    "source": "portal",
    "admin_notes": null,
    "debt": {
      "id": "enc_456",
      "reference": "INV-2024-001",
      "amount": 1500.00,
      "status": "status.default.on_hold",
      "status_id": "enc_2"
    },
    "followup_id": null,
    "call_id": null,
    "audio_url": null,
    "pdf_url": null,
    "media_urls_expire_at": null,
    "created_at": "2024-01-15T10:30:00+00:00",
    "reviewed_at": null,
    "updated_at": "2024-01-15T11:00:00+00:00",
    "collection_paused": true
  }
}
When audio_url or pdf_url are present, they are pre-signed S3 URLs valid for 1 hour. The media_urls_expire_at field indicates when they expire.

Update Todo Status

# Update agreement
curl -X PUT https://api.getbill.io/external-api/v1/todos/agreement/enc_456/status \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "debt_status_id": "enc_5",
    "plan_months": 12,
    "plan_start_date": "2024-02-01",
    "first_payment_amount": 150.00
  }'

# Update dispute
curl -X PUT https://api.getbill.io/external-api/v1/todos/debtor_request/enc_234/status \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "validated",
    "admin_notes": "Verified payment was received"
  }'

Path Parameters

ParameterTypeDescription
typestringTodo type: agreement, debtor_request, or ai_detection
idstringEncrypted todo ID

Request Body (Agreement)

FieldTypeRequiredDescription
debt_status_idstringNoEncrypted debt status ID
plan_monthsintegerNoDuration of payment plan in months
plan_start_datestringNoStart date of payment plan (ISO 8601)
first_payment_amountfloatNoAmount of first payment

Request Body (Dispute)

FieldTypeRequiredDescription
statusstringNoNew status: suggested, validated, or rejected
admin_notesstringNoAdditional notes about the status change

Get Todo Statistics

curl -X GET https://api.getbill.io/external-api/v1/todos/stats \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json"

Query Parameters

ParameterTypeDescriptionDefault
periodstringTime period filtersince_last_month

Response

{
  "stats": {
    "agreements": {
      "total": 25,
      "active": 15,
      "pending": 7,
      "overdue": 3
    },
    "debtor_requests": {
      "total": 8,
      "by_type": {
        "portal_dispute": 4,
        "hardship": 2,
        "payment_plan": 2
      }
    },
    "ai_detections": {
      "total": 12,
      "by_type": {
        "ai_dispute": 7,
        "objection": 5
      }
    },
    "blocking_disputes": {
      "total": 8,
      "by_status": {
        "suggested": 5,
        "validated": 2,
        "rejected": 1
      }
    },
    "non_blocking_disputes": {
      "total": 12,
      "by_status": {
        "suggested": 8,
        "validated": 3,
        "rejected": 1
      }
    },
    "total": {
      "all": 45
    }
  }
}
The blocking_disputes and non_blocking_disputes keys in the stats response are kept for backward compatibility. Use debtor_requests and ai_detections for new integrations.

Error Responses

Status CodeDescription
400Bad Request - Invalid parameters or type
401Unauthorized - Invalid or missing API token
403Forbidden - Company not authorized
404Not Found - Todo or debt not found
429Too Many Requests - Rate limit exceeded
500Internal Server Error

Example: Processing Todos

import requests

API_TOKEN = "your_api_token"
BASE_URL = "https://api.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
See the Webhooks documentation for more information.

Authentication & Scopes

All endpoints require a valid Bearer token. Required OAuth scopes:
EndpointScope
List Todosdebts:read
Get Single Tododebts:read
Get Todo Statisticsdebts:read
Update Todo Statusdebts:write

Rate Limits

  • Read endpoints (list, get, stats): 1,000 requests per hour
  • Write endpoints (update status): 500 requests per hour

Best Practices

  1. Process debtor requests first - These pause collection and need immediate attention
  2. Use period filters - Focus on recent todos to avoid processing old data
  3. Monitor agreement installments - Check for overdue payments regularly
  4. Include admin notes - Always document status changes for audit trail
  5. Use stats endpoint - Get a quick overview before fetching detailed lists
  6. Handle media URL expiry - Pre-signed URLs for audio/PDF expire after 1 hour; re-fetch if needed