Receivables API
The Receivables API enables platforms to submit unpaid invoices for recovery. Support both structured data submission and PDF processing with OCR.
Prerequisites: Users must complete onboarding before submitting receivables. Check user.capabilities.send_receivables before allowing submissions.
Submission Methods
Method 1: Structured Data
Submit receivables with complete structured data:
const receivable = await cleavr.receivables.create({ // User identification user_id: 'usr_clv_a1b2c3d4e5f6',
// Invoice details invoice: { numbers: ['INV-2025-001', 'INV-2025-002'], // Support multiple invoices issue_date: '2025-08-01', due_date: '2025-08-31', service_description: 'Consulting services for Q3 2025',
// Amounts amount: 5000.00, // Total including tax amount_before_tax: 4166.67, tax_amount: 833.33, currency: 'EUR',
// Optional payment_terms: 'Net 30', late_penalty_rate: 10.0, // Annual percentage },
// Debtor information debtor: { // Company details company_name: 'Debtor Corp', company_type: 'company', // 'company' or 'individual' siret: '98765432100001', vat_number: 'FR98765432101',
// Address address: { line1: '456 Avenue des Champs', line2: 'Building B', city: 'Lyon', postal_code: '69000', country: 'FR' },
// Contact information (CRITICAL for success) contacts: [ { first_name: 'Marie', last_name: 'Martin', email: 'marie.martin@debtorcorp.com', // REQUIRED phone: '+33612345678', // HIGHLY RECOMMENDED job_title: 'CFO', level: 0 // Primary contact }, { first_name: 'Pierre', last_name: 'Dubois', email: 'pierre.dubois@debtorcorp.com', phone: '+33698765432', job_title: 'Accounting Manager', level: 1 // Secondary contact for escalation } ] },
// Platform metadata metadata: { platform_invoice_id: 'plat_inv_123', customer_id: 'cust_456', notes: 'Customer disputed item 3, but agreed to pay rest' }});Method 2: PDF with OCR
Submit PDF invoices for automatic extraction:
// First, upload the PDFconst formData = new FormData();formData.append('file', pdfFile);formData.append('user_id', 'usr_clv_a1b2c3d4e5f6');
const extraction = await cleavr.receivables.extractFromPDF(formData);
// Review and complete extractionif (extraction.confidence < 0.8 || extraction.missing_fields.length > 0) { // Complete missing information const receivable = await cleavr.receivables.create({ ...extraction.data, // Add missing fields debtor: { ...extraction.data.debtor, contacts: [{ email: 'contact@debtor.com', // Add if missing phone: '+33612345678' }] } });}OCR Extraction Response:
{ "extraction_id": "ext_a1b2c3d4", "confidence": 0.85, "data": { "invoice": { "numbers": ["INV-2025-001"], "amount": 5000.00, "due_date": "2025-08-31" // ... extracted fields }, "debtor": { "company_name": "Debtor Corp" // ... extracted fields } }, "missing_fields": [ "debtor.contacts[0].email", "debtor.contacts[0].phone" ], "warnings": [ { "field": "debtor.siret", "message": "SIRET format appears invalid", "extracted_value": "987654321" } ]}Contact Requirements & Success Rates
Email is MANDATORY: Receivables without debtor email will be rejected. Phone is CRUCIAL: Success rate is highly increased.
Contact Priority Levels
Contacts are processed in priority order during escalation:
contacts: [ { level: 0, ... }, // Primary - contacted first { level: 1, ... }, // Secondary - if primary doesn't respond { level: 2, ... }, // Tertiary - final escalation]Handling Missing Contacts
try { const receivable = await cleavr.receivables.create(data);} catch (error) { if (error.code === 'MISSING_EMAIL_CONTACT') { // Must add email before proceeding const updatedData = { ...data, debtor: { ...data.debtor, contacts: [{ email: await promptUserForEmail(), phone: await promptUserForPhone(), first_name: 'Contact', last_name: 'Person' }] } };
const receivable = await cleavr.receivables.create(updatedData);
} else if (error.code === 'MISSING_PHONE_WARNING') { // Warning about lower success rate const proceed = await confirmWithUser( 'Missing phone number reduces success rate from 82% to 45%. Continue?' );
if (proceed) { const receivable = await cleavr.receivables.create({ ...data, force_proceed: true }); } }}Receivable Lifecycle
stateDiagram-v2 [*] --> pending: Create Receivable pending --> in_process: Send to Recovery in_process --> payment_received: Debtor Pays in_process --> disputed: Debtor Disputes in_process --> cancelled: Platform Cancels payment_received --> recovered: Payment Confirmed disputed --> evidence_requested: Request Evidence evidence_requested --> in_process: Evidence Provided evidence_requested --> cancelled: No EvidenceStatus Definitions
| Status | Description | Next Actions |
|---|---|---|
pending | Created but not sent to recovery | Send to recovery or cancel |
in_process | Active recovery in progress | Monitor, update, or cancel |
payment_received | Payment received, pending confirmation | Wait for confirmation |
recovered | Successfully recovered | None (terminal state) |
disputed | Debtor disputes the debt | Provide evidence |
evidence_requested | Waiting for supporting documents | Submit evidence |
cancelled | Recovery cancelled | None (terminal state) |
Managing Receivables
Get Receivable Status
const receivable = await cleavr.receivables.get('rec_a1b2c3d4e5f6');
console.log(receivable);// {// id: 'rec_a1b2c3d4e5f6',// status: 'in_process',// amount: 5000.00,// recovery_progress: {// emails_sent: 3,// calls_made: 1,// last_contact: '2025-09-15T10:30:00Z',// next_action: 'phone_call',// next_action_date: '2025-09-17T09:00:00Z'// },// commission: {// rate: 15.90, // Based on 60-day old debt// amount: 795.00// },// estimated_recovery_date: '2025-09-30'// }Update Receivable
Add or update debtor information:
// Add new contactawait cleavr.receivables.addContact('rec_a1b2c3d4e5f6', { first_name: 'New', last_name: 'Contact', email: 'new.contact@debtor.com', phone: '+33611111111', level: 2 // Tertiary contact});
// Update metadataawait cleavr.receivables.update('rec_a1b2c3d4e5f6', { metadata: { ...existingMetadata, internal_note: 'Customer promised payment by end of month' }});Cancel Recovery
Stop recovery process:
await cleavr.receivables.cancel('rec_a1b2c3d4e5f6', { reason: 'paid_directly', // Required notes: 'Customer paid directly to our bank account'});
// Cancellation reasons:// - 'paid_directly': Debtor paid outside of Cleavr// - 'disputed_valid': Valid dispute from debtor// - 'business_decision': Strategic decision to stop// - 'incorrect_submission': Error in receivable dataHandling Disputes
When Disputes Occur
Disputes automatically pause recovery:
// Webhook notification{ "event": "receivable.disputed", "data": { "receivable_id": "rec_a1b2c3d4e5f6", "dispute": { "reason": "service_not_delivered", "debtor_message": "We never received the products mentioned in invoice", "evidence_requested": [ "delivery_confirmation", "signed_contract" ] } }}Submit Evidence
Provide supporting documents:
const evidence = await cleavr.receivables.submitEvidence('rec_a1b2c3d4e5f6', { documents: [ { type: 'delivery_confirmation', file: deliveryProofFile, // File or base64 description: 'Signed delivery receipt from 2025-08-15' }, { type: 'contract', file: contractFile, description: 'Signed service agreement' } ], statement: 'Products were delivered on Jan 15, 2024, as confirmed by the attached signed receipt.'});Partial Payments
Handle partial payment scenarios:
// Webhook notification for partial payment{ "event": "receivable.partial_payment", "data": { "receivable_id": "rec_a1b2c3d4e5f6", "original_amount": 5000.00, "paid_amount": 3000.00, "remaining_amount": 2000.00, "action_required": "platform_decision" }}
// Platform decides how to proceedawait cleavr.receivables.handlePartialPayment('rec_a1b2c3d4e5f6', { action: 'continue_recovery', // or 'mark_complete' notes: 'Continue recovering remaining €2000'});Commission Calculation
Get Commission Estimate
Calculate commission before submission:
const commission = await cleavr.commission.calculate({ amount: 5000.00, invoice_date: '2025-08-01', // Used to calculate age platform_id: 'plat_live_xxx' // For custom rates});
// Response{ "rate_percentage": 15.90, "rate_tier": "45-89 days", "commission_amount": 795.00, "net_recovery": 4205.00, "custom_rate": false // true if platform has special rate}Commission Rate Table
const rates = await cleavr.commission.getRates();
// Response{ "default_rates": [ { "days_from": 0, "days_to": 44, "rate": 10.80 }, { "days_from": 45, "days_to": 89, "rate": 15.90 }, // ... ], "platform_custom_rate": { "enabled": true, "flat_rate": 12.50, // If negotiated "volume_tiers": [...] // If volume-based }}Bulk Operations
Submit Multiple Receivables
const results = await cleavr.receivables.createBatch([ { /* receivable 1 */ }, { /* receivable 2 */ }, // ... up to 100 per batch]);
// Response{ "successful": [ { "index": 0, "id": "rec_xxx", "status": "created" }, { "index": 2, "id": "rec_yyy", "status": "created" } ], "failed": [ { "index": 1, "error": "MISSING_EMAIL_CONTACT", "message": "Debtor email is required" } ]}List Receivables
const receivables = await cleavr.receivables.list({ user_id: 'usr_clv_xxx', status: 'in_process', created_after: '2025-01-01', created_before: '2025-12-31', limit: 50, starting_after: 'rec_xxx' // For pagination});Idempotency
Prevent duplicate submissions:
const receivable = await cleavr.receivables.create(data, { idempotency_key: 'platform-inv-12345'});
// Subsequent calls with same key return existing receivableconst same = await cleavr.receivables.create(data, { idempotency_key: 'platform-inv-12345'});
console.log(receivable.id === same.id); // trueError Handling
Common Errors
| Error Code | Description | Solution |
|---|---|---|
USER_NOT_ONBOARDED | User hasn't completed KYB | Redirect to onboarding |
MISSING_EMAIL_CONTACT | No email provided | Add email before submission |
INVALID_SIRET | Invalid French company ID | Verify and correct SIRET |
DUPLICATE_RECEIVABLE | Already submitted | Check with idempotency key |
INVALID_CURRENCY | Unsupported currency | Convert to EUR |
Error Response Format
{ "error": { "code": "MISSING_EMAIL_CONTACT", "message": "At least one debtor contact with email is required", "field": "debtor.contacts[0].email", "details": { "debtor_name": "Debtor Corp", "has_phone": true, "has_email": false } }}Next Steps
After submitting receivables:
- Configure Webhooks for real-time updates
- Monitor Recovery Progress
- Handle Disputes
