Batch-generate invoice PDFs from a CSV file with Python
This recipe shows a batch script around the same endpoint documented on the main API page — read that first for the full field reference. It reads a CSV of orders, generates one PDF per row, and skips-but-logs any row that fails instead of stopping the whole batch.
What you'll need
- An InvoiceCraftly API key (see the API reference).
- Python 3.9 or later.
- The
requestspackage:pip install requests. - A CSV file of orders with columns matching the fields below.
Expected CSV columns
invoice_number, seller_name, seller_address, buyer_name, buyer_address, description, quantity, unit_price, tax_rate, currency — one row per invoice, matching the field names in the reference table. An optional issue_date column is used when present; rows without it fall back to today's date.
The batch script
# pip install requests
import csv
import datetime
import os
import requests
API_KEY = os.environ["INVOICECRAFTLY_API_KEY"]
OUTPUT_DIR = "invoices"
os.makedirs(OUTPUT_DIR, exist_ok=True)
def build_invoice(row):
return {
"type": "invoice",
"number": row["invoice_number"],
"issueDate": row.get("issue_date") or datetime.date.today().isoformat(),
"currency": row["currency"].strip().upper(),
"seller": {"name": row["seller_name"], "addressLines": [row["seller_address"]]},
"buyer": {"name": row["buyer_name"], "addressLines": [row["buyer_address"]]},
"items": [{
"description": row["description"],
"quantity": float(row["quantity"]),
"unitPrice": float(row["unit_price"]),
"taxRate": float(row["tax_rate"]),
}],
}
with open("orders.csv", newline="") as csv_file:
reader = csv.DictReader(csv_file)
succeeded, failed = 0, []
for row in reader:
try:
invoice = build_invoice(row)
response = requests.post(
"https://invoicecraftly.com/api/v1/documents/pdf",
headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
json=invoice,
)
except (ValueError, KeyError) as exc:
failed.append((row.get("invoice_number", "?"), "local", str(exc)))
continue
if response.status_code == 200:
with open(f"{OUTPUT_DIR}/{invoice['number']}.pdf", "wb") as pdf_file:
pdf_file.write(response.content)
succeeded += 1
else:
failed.append((invoice["number"], response.status_code, response.json().get("error", {}).get("code")))
print(f"Generated {succeeded} invoice(s).")
if failed:
print(f"Failed {len(failed)} row(s):")
for number, status, code in failed:
print(f" {number}: {status} {code}")
One bad row doesn't stop the batch
Each row's HTTP status and error code (from the same error table as the rest of the API) is collected into failed instead of raising immediately, so a single malformed row doesn't stop the rest of the file from processing. Rows that break before the request is even sent — a blank or missing tax_rate raising ValueError, a missing column raising KeyError — are caught by the same try block and recorded as local failures, so the loop continues either way. Re-run the script against a CSV containing only the failed rows once you've fixed them.
One thing to watch on large files: a batch bigger than your account's remaining monthly quota will start failing partway through once the quota runs out, so if a run succeeds for a while and then fails row after row, check the remaining quota on your account before hunting for a data problem.
Get an API key and try this recipe.
Free during the beta, 100 renders included per month.