Skip to main content

Backend Integration

Learn how to integrate the Thredfi API into your backend application with best practices and common patterns.

Architecture Overview

Your Application

  OAuth2 Authentication

Thredfi API (dev-backend.thredfi.com)

  [Business Layer]
      ├─ Invoices
      ├─ Bills
      ├─ Customers
      ├─ Vendors
      └─ General Ledger

Environment URLs

EnvironmentBase URLUse Case
Developmenthttps://dev-backend.thredfi.comTesting and development
Staginghttps://staging-backend.thredfi.comPre-production testing
Productionhttps://backend.thredfi.comLive partner integrations
This documentation connects to the development environment. Contact Thredfi for production credentials.

Common Integration Patterns

1. Onboarding a New Business

When a new customer signs up in your system, create a corresponding business in Thredfi:
def onboard_business(customer_data):
    """Create a Thredfi business for a new customer."""
    # Get unscoped token
    unscoped_token = get_unscoped_token()

    # Create business with idempotency
    business_data = {
        "external_id": f"customer-{customer_data['id']}",  # Your customer ID
        "legal_name": customer_data['company_name'],
        "entity_type": "limited",  # or sole_trader, llp, etc.
        "email": customer_data['email'],
        "country": "GB",
        "currency": "GBP"
    }

    response = requests.post(
        "https://dev-backend.thredfi.com/v1/platform/businesses/",
        headers={
            "Authorization": f"Bearer {unscoped_token}",
            "Content-Type": "application/json"
        },
        json=business_data
    )

    business = response.json()

    # Store business_id in your database
    customer_data['thredfi_business_id'] = business['id']
    customer_data.save()

    return business
Use external_id to ensure idempotency. If you retry with the same external_id, Thredfi returns the existing business instead of creating a duplicate.

2. Recording an Invoice

When a customer creates an invoice in your system, sync it to Thredfi:
def sync_invoice_to_thredfi(invoice):
    """Sync invoice from your system to Thredfi."""
    business_id = invoice.customer.thredfi_business_id
    business_token = get_business_token(business_id)

    invoice_data = {
        "external_id": f"invoice-{invoice.id}",
        "customer_id": invoice.customer.thredfi_customer_id,
        "invoice_number": invoice.number,
        "sent_at": invoice.sent_date.isoformat(),
        "due_at": invoice.due_date.isoformat(),
        "currency": invoice.currency,
        "line_items": [
            {
                "description": item.description,
                "quantity": item.quantity,
                "unit_price_cents": int(item.unit_price * 100),
                "account_code": item.account_code
            }
            for item in invoice.line_items
        ]
    }

    response = requests.post(
        f"https://dev-backend.thredfi.com/v1/platform/businesses/{business_id}/invoices/",
        headers={
            "Authorization": f"Bearer {business_token}",
            "Content-Type": "application/json"
        },
        json=invoice_data
    )

    if response.status_code == 201:
        thredfi_invoice = response.json()
        invoice.thredfi_invoice_id = thredfi_invoice['id']
        invoice.save()
        return thredfi_invoice
    else:
        handle_error(response)

3. Recording a Payment

When a customer pays an invoice:
def record_invoice_payment(payment):
    """Record invoice payment in Thredfi."""
    business_id = payment.invoice.customer.thredfi_business_id
    business_token = get_business_token(business_id)

    payment_data = {
        "external_id": f"payment-{payment.id}",
        "amount_cents": int(payment.amount * 100),
        "payment_date": payment.date.isoformat(),
        "payment_method": "bank_transfer",
        "allocations": [
            {
                "invoice_id": payment.invoice.thredfi_invoice_id,
                "amount_cents": int(payment.amount * 100)
            }
        ]
    }

    response = requests.post(
        f"https://dev-backend.thredfi.com/v1/platform/businesses/{business_id}/invoices/payments/",
        headers={
            "Authorization": f"Bearer {business_token}",
            "Content-Type": "application/json",
            "Idempotency-Key": f"payment-{payment.id}"  # Prevent duplicate processing
        },
        json=payment_data
    )

    return response.json()

Best Practices

Use external_id for Idempotency

Always provide external_id when creating resources:
{
  "external_id": "your-system-id-123",
  "legal_name": "Example Ltd",
  ...
}
Benefits:
  • Prevents duplicate records
  • Safe to retry failed requests
  • Easy to find records later

Handle Errors Gracefully

def handle_api_response(response):
    """Handle Thredfi API responses with proper error handling."""
    if response.status_code == 200 or response.status_code == 201:
        return response.json()

    error_data = response.json()

    if response.status_code == 400:
        # Validation error - fix your request
        raise ValidationError(error_data.get('error'))

    elif response.status_code == 401:
        # Auth error - refresh token
        refresh_tokens()
        # Retry request

    elif response.status_code == 403:
        # Permission error - check business_id matches token
        raise PermissionError(error_data.get('error'))

    elif response.status_code == 404:
        # Resource not found
        raise NotFoundError(error_data.get('error'))

    elif response.status_code >= 500:
        # Server error - retry with exponential backoff
        retry_with_backoff(response)

    else:
        raise APIError(f"Unexpected error: {error_data}")

Implement Pagination

For list endpoints, handle pagination correctly:
def get_all_invoices(business_id):
    """Fetch all invoices with pagination."""
    business_token = get_business_token(business_id)
    all_invoices = []
    page = 1

    while True:
        response = requests.get(
            f"https://dev-backend.thredfi.com/v1/platform/businesses/{business_id}/invoices/",
            headers={"Authorization": f"Bearer {business_token}"},
            params={"page": page, "page_size": 100}  # Max page size
        )

        data = response.json()
        all_invoices.extend(data['results'])

        if not data['next']:
            break

        page += 1

    return all_invoices

Store Thredfi IDs

Always store Thredfi’s UUIDs in your database:
# Your database model
class Customer(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=255)
    email = models.EmailField()

    # Store Thredfi references
    thredfi_business_id = models.UUIDField(null=True)
    thredfi_customer_id = models.UUIDField(null=True)
This enables:
  • Quick lookups without API calls
  • Bi-directional sync
  • Error recovery

Complete Example: Invoice Workflow

import requests
import base64
from decimal import Decimal

class ThredfiIntegration:
    def __init__(self, partner_uuid, api_key):
        self.partner_uuid = partner_uuid
        self.api_key = api_key
        self.base_url = "https://dev-backend.thredfi.com"

    def create_and_pay_invoice(self, business_id, customer_id):
        """Complete workflow: create invoice and record payment."""

        # 1. Get business token
        token = self.get_business_token(business_id)

        # 2. Create invoice
        invoice = self.create_invoice(
            business_id=business_id,
            token=token,
            customer_id=customer_id,
            amount_cents=100000
        )

        # 3. Record payment
        payment = self.record_payment(
            business_id=business_id,
            token=token,
            invoice_id=invoice['id'],
            amount_cents=100000
        )

        return {
            "invoice": invoice,
            "payment": payment
        }

    def get_unscoped_token(self):
        credentials = f"{self.partner_uuid}:{self.api_key}"
        encoded = base64.b64encode(credentials.encode()).decode()

        response = requests.post(
            f"{self.base_url}/v1/platform/oauth2/token/",
            headers={"Authorization": f"Basic {encoded}"}
        )

        return response.json()["access_token"]

    def get_business_token(self, business_id):
        unscoped_token = self.get_unscoped_token()

        response = requests.post(
            f"{self.base_url}/v1/platform/{business_id}/oauth2/token/",
            headers={"Authorization": f"Bearer {unscoped_token}"}
        )

        return response.json()["access_token"]

    def create_invoice(self, business_id, token, customer_id, amount_cents):
        invoice_data = {
            "customer_id": customer_id,
            "invoice_number": "INV-001",
            "sent_at": "2024-01-15",
            "due_at": "2024-02-15",
            "currency": "GBP",
            "line_items": [
                {
                    "description": "Professional Services",
                    "quantity": 1,
                    "unit_price_cents": amount_cents,
                    "account_code": "4000"
                }
            ]
        }

        response = requests.post(
            f"{self.base_url}/v1/platform/businesses/{business_id}/invoices/",
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json"
            },
            json=invoice_data
        )

        return response.json()

    def record_payment(self, business_id, token, invoice_id, amount_cents):
        payment_data = {
            "amount_cents": amount_cents,
            "payment_date": "2024-01-20",
            "payment_method": "bank_transfer",
            "allocations": [
                {
                    "invoice_id": invoice_id,
                    "amount_cents": amount_cents
                }
            ]
        }

        response = requests.post(
            f"{self.base_url}/v1/platform/businesses/{business_id}/invoices/payments/",
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json"
            },
            json=payment_data
        )

        return response.json()

Next Steps