IntegrationsQuickBooks
QB
Accounting

Automate QuickBooks Invoice Entry — Parse PDFs Directly into QuickBooks Online

Every supplier invoice that arrives as a PDF can go directly into QuickBooks Online as a Bill — without anyone touching a keyboard.

Trusted by developers worldwide to automate document workflows.

Get Your Free API KeyView API Docs
Free DocuParseAPI accountQuickBooks Online APIPython or Node

The guide

1
Get your credentials
You need three things: a DocuParseAPI key (from your dashboard), a QuickBooks Online OAuth 2.0 access token, and your QBO Company ID. Create a QBO app at developer.intuit.com to get OAuth credentials.
2
Extract the invoice
POST the PDF to DocuParseAPI. The response gives you every field QuickBooks needs:
merchant  →  VendorRef.name
date      →  TxnDate
due_date  →  DueDate
invoice_id → DocNumber
total     →  TotalAmt
line_items → Line[] array
3
Find or create the vendor in QBO
Query QBO for a Vendor matching the extracted merchant name. If none exists, create one automatically:
def find_or_create_vendor(name, qbo_base, headers):
    r = requests.get(f"{qbo_base}/query", headers=headers,
        params={"query": f"SELECT * FROM Vendor WHERE DisplayName LIKE '%{name}%'",
                "minorversion": "65"})
    vendors = r.json().get("QueryResponse", {}).get("Vendor", [])
    if vendors:
        return vendors[0]["Id"]
    r = requests.post(f"{qbo_base}/vendor", headers=headers,
        params={"minorversion": "65"},
        json={"DisplayName": name, "PrintOnCheckName": name})
    return r.json()["Vendor"]["Id"]
Need your API key first?
20 free documents/month — no credit card
Get Free API Key →
4
Check for duplicate invoices
Before creating, query QBO for an existing Bill with the same DocNumber and VendorRef. If found, skip:
def is_duplicate(invoice_number, qbo_base, headers):
    r = requests.get(f"{qbo_base}/query", headers=headers,
        params={"query": f"SELECT * FROM Bill WHERE DocNumber = '{invoice_number}'",
                "minorversion": "65"})
    return len(r.json().get("QueryResponse", {}).get("Bill", [])) > 0
5
Create the Bill in QuickBooks
POST to the QBO /bill endpoint with the extracted fields mapped:
bill = {
    "VendorRef": {"value": vendor_id},
    "TxnDate": invoice["date"],
    "DueDate": invoice["due_date"],
    "DocNumber": invoice["invoice_id"],
    "TotalAmt": float(invoice["total"]),
    "Line": [{
        "Amount": float(item["total"]),
        "DetailType": "AccountBasedExpenseLineDetail",
        "Description": item["description"],
        "AccountBasedExpenseLineDetail": {
            "AccountRef": {"value": "7"}
        }
    } for item in invoice["line_items"]]
}

Full working example

python
import os, requests

QBO_BASE = f"https://quickbooks.api.intuit.com/v3/company/{os.environ['QBO_COMPANY_ID']}"
HEADERS = {"Authorization": f"Bearer {os.environ['QBO_ACCESS_TOKEN']}",
           "Content-Type": "application/json", "Accept": "application/json"}

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 find_or_create_vendor(name):
    r = requests.get(f"{QBO_BASE}/query", headers=HEADERS,
        params={"query": f"SELECT * FROM Vendor WHERE DisplayName LIKE '%{name}%'",
                "minorversion": "65"})
    vendors = r.json().get("QueryResponse", {}).get("Vendor", [])
    if vendors: return vendors[0]["Id"]
    r = requests.post(f"{QBO_BASE}/vendor", headers=HEADERS, params={"minorversion":"65"},
        json={"DisplayName": name})
    return r.json()["Vendor"]["Id"]

def process(file_path):
    inv = extract_invoice(file_path)
    vendor_id = find_or_create_vendor(inv.get("merchant") or "Unknown")
    r = requests.post(f"{QBO_BASE}/bill", headers=HEADERS, params={"minorversion":"65"},
        json={"VendorRef":{"value":vendor_id}, "TxnDate":inv.get("date"),
              "DocNumber":inv.get("invoice_id"), "TotalAmt":float(inv.get("total") or 0),
              "Line":[{"Amount":float(i.get("total") or 0), "DetailType":"AccountBasedExpenseLineDetail",
                "Description":i.get("description",""), "AccountBasedExpenseLineDetail":{"AccountRef":{"value":"7"}}}
                for i in inv.get("line_items",[])]})
    print(f"Bill created: {r.json()['Bill']['Id']}")

process("invoice.pdf")

What gets extracted

FieldDescriptionDestination field
merchantThe vendor or store nameVendorRef.name
totalThe final charged amount including taxTotalAmt
subtotalThe pre-tax amountLine[].Amount
taxThe tax amount chargedTaxCodeRef
dateInvoice or transaction date (ISO 8601)TxnDate
due_datePayment due date, or null if not presentDueDate
invoice_idInvoice number or receipt ID from the documentDocNumber
currencyISO 4217 currency code (USD, EUR, GBP, etc.)CurrencyRef
payment_methodCard type, cash, or other payment methodNot mapped
line_itemsArray of items with description, quantity, unit_price, totalLine[] array

Common errors and fixes

MISSING_API_KEYAuthorization header missingAdd header: Authorization: Bearer YOUR_KEY
INVALID_API_KEYKey not recognisedCheck key in dashboard — regenerate if needed
LIMIT_EXCEEDEDMonthly document limit reachedUpgrade at docuparseapi.com/pricing
UNSUPPORTED_FILE_TYPEFile format not supportedUse PDF, JPG, or PNG only
FILE_TOO_LARGE_AUTHENTICATEDFile exceeds 10 MBCompress the file before sending
EXTRACTION_FAILEDDocument could not be parsedTry a cleaner scan or different file

Ready to start?

20 documents free every month. No credit card. Set up in 5 minutes.

Get Your Free API Key