Import a Customer Invoice via API
Use this guide to import customer (sales) invoices into Pennylane and automatically register them in your invoicing and accounting workspace.
Goal
This tutorial shows how to automatically import customer invoices into Pennylane from your own system - for example, a billing tool, CRM, or ERP - so they appear in your accounting workspace, linked to the right customer and ready for reconciliation.
You will learn how to:
- Upload a customer invoice file (PDF)
- Import the invoice in Pennylane via API
- (Optional) Categorize the invoice for reporting
End result:
Your customer invoice appears in your Pennylane workspace, linked to the right customer, with correct VAT, amounts, and categories — ready for accounting.
Who is this for?
Developers and partners building integrations that sync customer invoices into Pennylane automatically.
Authentication
Partner integrations must use OAuth 2.0 for authentication.
In sandbox environments, you can test this flow with 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 or import customer invoices |
file_attachments:all | Upload and attach invoice PDFs |
(optional) products:readonly | Retrieve products for invoice lines |
(optional) ledger_accounts:readonly | Retrieve account IDs for proper accounting mapping |
(optional) categories:all | Categorize invoices for reporting |
You will need:
- A Pennylane company selected through OAuth consent
- Your OAuth access token (or a Company token in sandbox)
- A valid
customer_id(existing or newly created) - A valid invoice file (PDF) to attach
- (Optional) ledger account IDs - e.g., 706/707 for Revenue, 4457 for Output VAT, 530 for Cash, or 511… for payment providers
- (Optional) product IDs if you sync invoice lines from your product catalog
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
- Understanding Scopes
Step 1 | (Optional) Verifying Authentication
Before importing 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 | Uploading the Invoice File
Before importing your invoice, upload the PDF file to Pennylane.
curl --request POST \
--url https://app.pennylane.com/api/external/v2/file_attachments \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: multipart/form-data" \
-F [email protected]Result
The API returns a JSON object containing an
id.Use this
file_attachment_idin the next step to import your invoice.
Example response
{
"id": 4321,
"filename": "invoice-october.pdf",
"status": "uploaded"
}Tip: Accepted file format & size Only PDF files are supported for customer invoice imports. The
/file_attachmentsendpoint accepts files up to 100 MB.Uploading another file format for an invoice will return a validation error.
Step 3 | Importing the Customer Invoice
Now that your invoice file is uploaded, use the import endpoint to create the corresponding invoice in Pennylane.
curl --request POST \
--url https://app.pennylane.com/api/external/v2/customer_invoices/import \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '{
"file_attachment_id": 4321,
"customer_id": 1001,
"date": "2025-10-01",
"deadline": "2025-10-31",
"currency_amount_before_tax": "100.00",
"currency_tax": "20.00",
"currency_amount": "120.00",
"invoice_lines": [
{
"ledger_account_id": 706002,
"currency_amount": "120.00",
"currency_tax": "20.00",
"quantity": 2,
"raw_currency_unit_price": "50.00",
"unit": "piece",
"vat_rate": "FR_200"
}
]
}'✅ Result
A successful request creates the invoice in your Pennylane workspace and returns its id and main details.
Example response
{
"id": 5678,
"status": "imported",
"currency_amount": "120.00",
"customer_name": "ABC Consulting"
}Important:
The sum of all invoice line
currency_amountvalues must equal the totalcurrency_amount.Otherwise, the API returns
422 Unprocessable Entity — Entry lines are not balanced.
Ledger Account Mapping
If you omit the
ledger_account_id, Pennylane automatically applies your company’s default mapping, based on its chart of accounts and VAT settings.You can override this by specifying the account ID explicitly in your payload.
VAT Rate Codes Reference
The
vat_ratefield must use one of the supported VAT rate codes.
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 the Create a customer invoice endpoint.
Step 4 | Validating the Import
After importing the invoice, verify that it was successfully created in Pennylane.
curl --request GET \
--url https://app.pennylane.com/api/external/v2/customer_invoices/5678 \
-H "Authorization: Bearer <ACCESS_TOKEN>"✅ Result
A successful response confirms the invoice exists and was imported correctly.
Example response
{
"id": 5678,
"status": "imported",
"currency_amount": "120.00",
"customer_name": "ABC Consulting"
}Common errors
401 Unauthorized→ Invalid or expired OAuth token.403 Forbidden→ Missing scope (customer_invoices:all).422 Unprocessable Entity→ Totals mismatch or missing required fields.
Tip: In sandbox mode, you can test safely using dummy customers and sample amounts.
Always verify that yourcurrency_amountand line totals are consistent before moving to production.
Step 5 | (Optional) Categorizing the Invoice
Categorization helps you analyze your sales by product line, department, or business activity.
You can assign one or several categories to a customer invoice.
1. Get or Create a Category
# List existing categories
GET /api/external/v2/categories
# Create a new category
POST /api/external/v2/categories
2. Assign the Category to the Invoice
curl --request PUT \
--url https://app.pennylane.com/api/external/v2/customer_invoices/{invoice_id}/categories \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "Content-Type: application/json" \
-d '[
{ "id": 123, "weight": 1.0 }
]'Note:
The
weightvalue represents the percentage allocation:
1.0= 100%,0.5= 50%. You can assign multiple categories with different weights
When to Use /import vs /create
/import vs /createBoth endpoints create customer invoices, but they serve different workflows:
| Action | Endpoint | When to use |
|---|---|---|
| Uploading and attaching a PDF file | /customer_invoices/import | When your invoice is already generated by another system (e.g., POS, ERP, or billing software) |
| Generating invoices directly | /customer_invoices | When you want to create and send invoices programmatically from your own app |
Tip: Use
/importwhen you already have a PDF.Use
/createwhen you need Pennylane to build the invoice from structured data.
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 → Import - Replace
<ACCESS_TOKEN>with your sandbox token - Use real
customer_idandledger_account_idfrom your sandbox company
✅ Result
Testing in sandbox ensures your flow works end-to-end before deploying to production.
Common Pitfalls
| Issue | Likely Cause | How to Fix |
|---|---|---|
400 Bad Request | Invalid payload — unexpected field or missing required property | Check field names, required fields, and data types |
401 Unauthorized | Missing or expired OAuth token | Refresh token or check header format |
403 Forbidden | Missing scope (customer_invoices:all) | Add missing scope to your OAuth app |
404 File not found | Uploaded file expired or deleted | Re-upload using /file_attachments |
422 Entry lines are not balanced | Totals mismatch between HT / TVA / TTC | Recalculate totals |
| Duplicate invoices | Re-importing the same payload | Implement deduplication logic (e.g., store file_attachment_id or external reference) |
Tip: If you re-import the same file, the API does not automatically deduplicate entries - handle this in your integration logic.
Tip: A
400 Bad Requestis often returned when a field name is misspelled, missing, or not supported by the endpoint.For example, including
"label"or an extra field not expected in/customer_invoices/importwill trigger this error.
Best Practices
- Validate before import — Check totals and VAT consistency client-side.
- Match to the right accounts — e.g.,
706xxxfor sales,445xxxfor VAT. - Ensure idempotency — Use a stable label or external reference to prevent duplicates.
- Secure tokens — Rotate and store OAuth tokens safely.
- Test in sandbox first — Validate the flow before running it in production.
Idempotency - Avoiding duplicates
Pennylane does not enforce idempotency for invoice imports.
To prevent duplicate invoices, your integration should handle deduplication client-side.
We recommend using a stable unique identifier, such as:
- the
file_attachment_id, or- a combination of
(customer/supplier_id, invoice_number, date).
How Pennylane Handles Accounting
When you import a customer invoice:
- Pennylane automatically creates revenue entries in your accounting ledgers.
- The entries appear in your Sales journal.
- The invoice is visible in Invoicing → Customer Invoices in your workspace.
Note
Entries are generated based on your company’s accounting scheme and the
ledger_account_idvalues provided in your payload.
Updated about 3 hours ago
