Backend Integration
Learn how to integrate the Thredfi API into your backend application with best practices and common patterns.Architecture Overview
Copy
Your Application
↓
OAuth2 Authentication
↓
Thredfi API (dev-backend.thredfi.com)
↓
[Business Layer]
├─ Invoices
├─ Bills
├─ Customers
├─ Vendors
└─ General Ledger
Environment URLs
| Environment | Base URL | Use Case |
|---|---|---|
| Development | https://dev-backend.thredfi.com | Testing and development |
| Staging | https://staging-backend.thredfi.com | Pre-production testing |
| Production | https://backend.thredfi.com | Live 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:Copy
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:Copy
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:Copy
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 provideexternal_id when creating resources:
Copy
{
"external_id": "your-system-id-123",
"legal_name": "Example Ltd",
...
}
- Prevents duplicate records
- Safe to retry failed requests
- Easy to find records later
Handle Errors Gracefully
Copy
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:Copy
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:Copy
# 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)
- Quick lookups without API calls
- Bi-directional sync
- Error recovery
Complete Example: Invoice Workflow
Copy
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()