x
Accounting
Automate Xero Invoice Entry — Extract PDF Invoice Data Directly into Xero
Import supplier invoices into Xero without manual data entry. One script, any invoice format.
Trusted by developers worldwide to automate document workflows.
✓ Free DocuParseAPI account✓ Xero API credentials✓ Python or Node
The guide
1
Get your credentials
You need: DocuParseAPI key, Xero OAuth 2.0 access token, and your Xero Tenant ID. Create a Xero app at developer.xero.com. Use the Xero OAuth 2.0 Playground to get an initial access token for testing.
2
Extract the invoice
POST the PDF to DocuParseAPI. The response maps directly to Xero fields:
merchant → Contact.Name date → Date due_date → DueDate invoice_id → InvoiceNumber currency → CurrencyCode line_items → LineItems[] array
3
Find or create the Xero Contact
Search Xero Contacts for the merchant name. Create if not found:
def get_or_create_contact(name, base, headers):
r = requests.get(f"{base}/Contacts", headers=headers,
params={"searchTerm": name})
contacts = r.json().get("Contacts", [])
if contacts: return contacts[0]["ContactID"]
r = requests.post(f"{base}/Contacts", headers=headers,
json={"Contacts": [{"Name": name}]})
return r.json()["Contacts"][0]["ContactID"]Need your API key first?
20 free documents/month — no credit card
4
Create a Draft Bill (ACCPAY)
POST to Xero /Invoices with Type: ACCPAY and Status: DRAFT so a human can approve before payment:
bill = {
"Type": "ACCPAY",
"Contact": {"ContactID": contact_id},
"Date": invoice["date"],
"DueDate": invoice["due_date"],
"InvoiceNumber": invoice["invoice_id"],
"CurrencyCode": invoice.get("currency", "USD"),
"Status": "DRAFT",
"LineItems": [{
"Description": item["description"],
"Quantity": item.get("quantity", 1),
"UnitAmount": float(item.get("unit_price") or item.get("amount") or 0),
"AccountCode": "429"
} for item in invoice.get("line_items", [])]
}5
Check for validation errors
Xero returns HasValidationErrors: true if the bill is invalid. Always check this before proceeding:
result = r.json()["Invoices"][0]
if result.get("HasValidationErrors"):
raise RuntimeError(f"Xero error: {result['ValidationErrors']}")
print(f"Bill created: {result['InvoiceID']}")Full working example
python
import os, requests
XERO_BASE = "https://api.xero.com/api.xro/2.0"
HEADERS = {"Authorization": f"Bearer {os.environ['XERO_ACCESS_TOKEN']}",
"Xero-tenant-id": os.environ['XERO_TENANT_ID'],
"Content-Type": "application/json", "Accept": "application/json"}
ACCOUNT_CODE = os.environ.get("XERO_EXPENSE_ACCOUNT_CODE", "429")
def extract_invoice(path):
with open(path, "rb") as f:
r = requests.post("https://docuparseapi.com/api/v1/extract",
headers={"Authorization": f"Bearer {os.environ['DOCUPARSE_API_KEY']}"},
files={"file": f}, timeout=30)
data = r.json()
if not data["success"]: raise RuntimeError(data["error"]["code"])
return data
def get_or_create_contact(name):
r = requests.get(f"{XERO_BASE}/Contacts", headers=HEADERS, params={"searchTerm": name})
contacts = r.json().get("Contacts", [])
if contacts: return contacts[0]["ContactID"]
r = requests.post(f"{XERO_BASE}/Contacts", headers=HEADERS,
json={"Contacts": [{"Name": name}]})
return r.json()["Contacts"][0]["ContactID"]
def process(file_path):
inv = extract_invoice(file_path)
contact_id = get_or_create_contact(inv.get("merchant") or "Unknown")
r = requests.post(f"{XERO_BASE}/Invoices", headers=HEADERS,
json={"Invoices": [{
"Type": "ACCPAY", "Contact": {"ContactID": contact_id},
"Date": inv.get("date"), "DueDate": inv.get("due_date"),
"InvoiceNumber": inv.get("invoice_id"),
"CurrencyCode": inv.get("currency", "USD"), "Status": "DRAFT",
"LineItems": [{"Description": i.get("description",""),
"Quantity": i.get("quantity",1),
"UnitAmount": float(i.get("unit_price") or i.get("amount") or 0),
"AccountCode": ACCOUNT_CODE}
for i in inv.get("line_items",[])]}
]})
result = r.json()["Invoices"][0]
if result.get("HasValidationErrors"):
raise RuntimeError(result["ValidationErrors"])
print(f"Xero Bill created: {result['InvoiceID']}")
process("invoice.pdf")What gets extracted
Common errors and fixes
Ready to start?
20 documents free every month. No credit card. Set up in 5 minutes.
Get Your Free API Key