Create a Customer Invoice via API
Use this endpoint when you want Pennylane to generate the invoice from structured data, rather than importing an existing PDF.
Goal
This tutorial shows how to generate customer invoices directly from your system — for example, from a CRM, billing app, or ERP — without importing an existing PDF.
You will learn how to:
- Create a new customer invoice from structured data
- Add products, VAT, and accounting mappings
- (Optional) Send or categorize the invoice for reporting
End Result:
Your customer invoice is created and appears in your Pennylane workspace, linked to the right customer and ready for accounting and reconciliation.
Who is this for?
Developers and partners building integrations that generate and sync invoices directly in Pennylane.
Authentication
Partner integrations must use OAuth 2.0 for authentication.
In sandbox environments, you can test this flow using a Company API token, but OAuth 2.0 is required for production integrations published on the Marketplace.
Before You Get Started
Required scopes (Company API v2)
| Scope | Features |
|---|---|
customer_invoices:all | Create and send customer invoices |
(optional)* transactions:readonly` | Retrieve payment or reconciliation data |
(optional) products:readonly | Retrieve product details |
(optional) ledger_accounts:readonly | Fetch accounting mappings |
(optional) categories:all | Categorize invoices |
(optional) file_attachments:all | Attach files to invoices |
You will need:
- A Pennylane company selected through OAuth consent
- Your OAuth access token (or Company token in sandbox)
- A valid
customer_id(existing or newly created) - (Optional) product IDs if your integration uses a product catalog
- (Optional) ledger account IDs — e.g., 706 or 707 for revenue, 4457 for VAT
IDs are company-specific
Customer, product, and ledger account IDs differ for each company.
Store these IDs per tenant in your integration to avoid mismatches.
See also:
- Authentication Overview
- Understand Scopes
Step 1 | (Optional) Verifying Authentication
Before creating invoices, confirm that your token and environment are correctly set up.
curl https://app.pennylane.com/api/external/v2/me \
-H "Authorization: Bearer <ACCESS_TOKEN>"✅ Expected Response: 200 OK - authentication successful.
Tip: Run this check when setting up your integration for the first time, or when switching between sandbox and production environments.
Step 2 | (Optional) Preparing References
Before creating the invoice, make sure your integration can identify or create the necessary entities.
Find or Create a Customer
Check if the customer exists:
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/customers?filter=[{"field":"external_reference","operator":"eq","value":"ACME_001"}]' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'If not, create a company customer:
curl --request POST \
--url 'https://app.pennylane.com/api/external/v2/company_customers' \
-H 'Authorization: Bearer <ACCESS_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"name": "ACME Corp",
"emails": ["[email protected]"],
"external_reference": "ACME_001",
"billing_address": {
"address": "123 Business St",
"postal_code": "75001",
"city": "Paris",
"country": "FR"
}
}'✅ Result
Keep the returned customer_id — you’ll need it to create the invoice.
(Optional) Retrieve Products
If your invoice references products:
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/products' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'Tip: Using products ensures consistent pricing and VAT handling across invoices.
(Optional) Retrieve Ledger Accounts
For precise accounting, retrieve available accounts:
curl --request GET \
--url 'https://app.pennylane.com/api/external/v2/ledger_accounts?filter=[{"field":"number","operator":"start_with","value":"706"}]' \
-H 'Authorization: Bearer <ACCESS_TOKEN>'Common ledger accounts
706xxx→ Service revenue707xxx→ Product sales
Step 3 | Creating the Customer Invoice
Once references are ready, create the invoice directly in Pennylane.
Minimal Example
curl --request POST \
--url "https://app.pennylane.com/api/external/v2/customer_invoices" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"customer_id": 123,
"date": "2025-10-01",
"deadline": "2025-10-31",
"invoice_lines": [
{
"label": "Consulting services",
"quantity": 2,
"unit": "hour",
"raw_currency_unit_price": "100.00",
"vat_rate": "FR_200"
}
],
"external_reference": "INV-ACME-2025-001"
}'✅ Result
This creates a finalized invoice with one line item.
Tip: Amount field types
All numeric amounts (e.g.
currency_amount,currency_tax,currency_amount_before_tax) must be provided as strings, not numbers.
Invoice with Product and Template
curl --request POST \
--url "https://app.pennylane.com/api/external/v2/customer_invoices" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"customer_id": 123,
"date": "2025-10-01",
"deadline": "2025-10-31",
"customer_invoice_template_id": 456,
"invoice_lines": [
{
"product_id": 789,
"quantity": 3,
"unit": "piece",
"raw_currency_unit_price": "50.00",
"vat_rate": "FR_200"
}
],
"draft": true
}'Tip: Use "
draft": trueto create a draft invoice; omit it (or setfalse) to finalize immediately.
Example Response
{
"id": 9876,
"invoice_number": "INV-2025-001",
"status": "draft",
"currency_amount_before_tax": "150.00",
"currency_tax": "30.00",
"currency_amount": "180.00",
"customer": { "id": 123 },
}Ledger Account Mapping
If you omit the
ledger_account_id, Pennylane automatically applies the company’s default mapping (chart of accounts & VAT settings).You can override this by specifying a
ledger_account_idexplicitly.
VAT Rate Codes Reference
vat_ratemust use a supported code.
Code Description Rate FR_200Standard VAT France 20% FR_100Reduced VAT France 10% FR_055Reduced VAT France 5.5% exemptExempt (0%) 0% anyNo specific VAT code — For the full list of supported VAT codes, see the API Reference for Create a Customer Invoice.
Step 4 | Verifying the Invoice
Check the created invoice:
curl --request GET \
--url "https://app.pennylane.com/api/external/v2/customer_invoices/9876" \
-H "Authorization: Bearer <ACCESS_TOKEN>"✅ Result
{
"id": 9876,
"status": "draft",
"currency_amount": "180.00",
"customer": "ACME Corp"
}Common errors
400 Bad Request→ Missing or invalid field401 Unauthorized→ Invalid token403 Forbidden→ Missingcustomer_invoices:allscope422 Unprocessable Entity→ Totals mismatch or invalid VAT code
Step 5 | Optional Actions
1. Send the Invoice by Email
curl --request POST \
--url "https://app.pennylane.com/api/external/v2/customer_invoices/9876/send_by_email" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{ "recipients": ["[email protected]"] }'2. Categorize the Invoice
curl --request PUT \
--url "https://app.pennylane.com/api/external/v2/customer_invoices/9876/categories" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '[{ "id": 321, "weight": 1.0 }]'
Note
weightrepresents the allocation percentage (1.0= 100%,0.5= 50%). Multiple categories are supported.
3. Track Reconciliation
curl --request GET \
--url "https://app.pennylane.com/api/external/v2/customer_invoices/9876/matched_transactions" \
-H "Authorization: Bearer <ACCESS_TOKEN>"Test in Sandbox or Postman
You can test this flow inyour Pennylane Sandbox or with your own Postman setup.
- Collection:
Pennylane API v2 → Customer Invoices → Create - Replace
<ACCESS_TOKEN>with your sandbox token - Use real
customer_id(and optionalproduct_id) from your sandbox company
✅ Result
Testing in sandbox ensures your flow works end-to-end before deploying to production.
Common Pitfalls
| Error | Likely Cause | How to Fix |
|---|---|---|
400 Bad Request | Invalid payload or missing required property | Check field names and formats |
401 Unauthorized | Missing or expired token | Refresh or verify header |
403 Forbidden | Missing scope | Add customer_invoices:all |
404 Not Found | Invalid customer_id or endpoint | Check IDs and URL |
422 Unprocessable Entity | Totals or VAT mismatch | Recalculate amounts |
Best Practices
- Validate before creation - check VAT and totals consistency client-side.
- Use accurate accounts - e.g.,
706or707for sales - Secure tokens - rotate and store them safely
- Test in sandbox first - before running in production
Updated about 1 hour ago
