Create a Customer Invoice

How to Create Customer Invoices via API

This guide will help you create customer invoices using Pennylane's API. We'll go through each step to create professional invoices that are automatically integrated into your accounting using Pennylane invoicing editor.

1. Prerequisites

Before creating invoices, you need:

  • OAuth token with customer_invoices:all scope
  • Customer ID (existing customer)
  • Product IDs (if using products, optional)
  • Invoice template ID (optional)
  • Ledger account IDs (for precise accounting mapping, optional)

2. Get Required References

2.1 Find or Create Customer

First, check if your customer exists:

curl --request GET \
     --url 'https://app.pennylane.com/api/external/v2/customers?filter=[{"field":"external_reference","operator":"eq","value":"YOUR_REF"}]' \
     --header 'authorization: Bearer xxx'

If not, create a company customer, for example :

curl --request POST \
     --url 'https://app.pennylane.com/api/external/v2/company_customers' \
     --header 'authorization: Bearer xxx' \
     --header 'content-type: application/json' \
     --data '{
       "name": "ACME Corp",
       "emails": ["[email protected]"],
       "external_reference": "YOUR_REF",
       "billing_address": {
         "address": "123 Business St",
         "postal_code": "75001",
         "city": "Paris",
         "country": "FR"
       }
     }'

You can also create an individual customer depending on the customer nature (B2C = individual, B2B = company).

📘

Keep the customer_id returned in the response for invoice creation

2.2 Get Products (Optional)

List available products:

curl --request GET \
     --url 'https://app.pennylane.com/api/external/v2/products' \
     --header 'authorization: Bearer xxx'

Products help maintain consistent pricing and descriptions

2.3 Get Invoice Template (Optional)

List available templates:

curl --request GET \
     --url 'https://app.pennylane.com/api/external/v2/customer_invoice_templates' \
     --header 'authorization: Bearer xxx'

2.4 Get Ledger Accounts (Optional)

For precise accounting mapping, you can specify ledger accounts for revenue and VAT:

# Get revenue accounts (706xxx)
curl --request GET \
     --url 'https://app.pennylane.com/api/external/v2/ledger_accounts?filter=[{"field":"number","operator":"start_with","value":"706"}]' \
     --header 'authorization: Bearer xxx'

Response example:

{
  "items": [
    {
      "id": 123456,
      "number": "706100",
      "label": "Prestations de services (TVA 10%)",
      "vat_rate": "FR_100",
      "country_alpha2": "FR",
      "enabled": true
    },
    {
      "id": 123457,
      "number": "706200",
      "label": "Prestations de services (TVA 20%)",
      "vat_rate": "FR_200",
      "country_alpha2": "FR",
      "enabled": true
    }
  ]
}

📘

Common account numbers:

  • 706xxx: Service revenue accounts
  • 707xxx: Product sales accounts

Benefits of specifying ledger accounts:

  • Precise accounting categorization
  • Automatic VAT handling
  • Consistent revenue recognition
  • Simplified reporting

3. Create Invoice Layout

Let's analyze a simple invoice structure:

{
  "customer_id": 123,
  "date": "2025-01-15",
  "deadline": "2025-02-15",
  "language": "fr_FR",
  "invoice_lines": [
    {
      "label":"Your product",
      "quantity": 2,
      "raw_currency_unit_price": "100.00",
			"unit":"piece",
      "vat_rate": "FR_200"
    }
  ]
}

📘

Key Components:

  • customer_id: Your customer reference
  • date: Invoice date (YYYY-MM-DD)
  • invoice_lines: Array of items with quantity and pricing
  • raw_currency_unit_price : The unit price excluding taxes (undiscounted if a discount is set). Can be set up to 6 decimals.

3.1 VAT Rates

Common French VAT rates:

  • FR_200: 20%
  • FR_100: 10%
  • FR_550: 5.5%
  • exempt: 0%

4. Create the Invoice

4.1 Basic Invoice

curl --request POST \
     --url 'https://app.pennylane.com/api/external/v2/customer_invoices' \
     --header 'authorization: Bearer xxx' \
     --header 'content-type: application/json' \
     --data '{
  		 "customer_id": 123,
       "date": "2025-01-15",
       "deadline": "2025-02-15",
       "language": "fr_FR",
       "invoice_lines": [
         {
            "label":"Your product",
            "quantity": 2,
            "raw_currency_unit_price": "100.00",
			      "unit":"piece",
            "vat_rate": "FR_200"
         }
        ]
      }'

4.2 Invoice with Products

curl --request POST \
     --url 'https://app.pennylane.com/api/external/v2/customer_invoices' \
     --header 'authorization: Bearer xxx' \
     --header 'content-type: application/json' \
     --data '{
  		 "customer_id": 123,
       "date": "2025-01-15",
       "deadline": "2025-02-15",
       "language": "fr_FR",
       "invoice_lines": [
         {
            "label":"Your product",
            "quantity": 2,
            "raw_currency_unit_price": "100.00",
			      "unit":"piece",
            "vat_rate": "FR_200",
						"product_id":456
         }
        ]
      }'

4.3 Invoice with Custom Template

curl --request POST \
     --url 'https://app.pennylane.com/api/external/v2/customer_invoices' \
     --header 'authorization: Bearer xxx' \
     --header 'content-type: application/json' \
     --data '{
       "customer_id": 123,
       "date": "2025-01-15",
       "deadline": "2025-02-15",
       "customer_invoice_template_id": 456,
       "invoice_lines": [...]
     }'

5. Handle the Response

Success response (201):

{
  "id": 42,
  "label": "Invoice label",
  "invoice_number": "F20230001",
  "currency": "EUR",
  "amount": "230.32",
  "currency_amount": "230.32",
  "currency_amount_before_tax": "196.32",
  "exchange_rate": "1.0",
  "date": "2023-08-30",
  "deadline": "2020-09-02",
  "currency_tax": "34.0",
  "tax": "34.0",
  "language": "fr_FR",
  "paid": false,
  "status": "archived",
  "discount": {
    "type": "absolute",
    "value": "25"
  },
  "ledger_entry": {
    "id": 42002
  },
  "public_file_url": "https://app.pennylane.com/public/invoice/pdf?encrypted_id=bzjoVJe...3D%3D",
  "filename": "my_file.pdf",
  "remaining_amount_with_tax": "20.0",
  "remaining_amount_without_tax": "16.0",
  "draft": false,
  "special_mention": "Additional details",
  "customer": {
    "id": 42,
    "url": "https://app.pennylane.com/api/external/v2/customers/42"
  },
  "invoice_line_sections": {
    "url": "https://app.pennylane.com/api/external/v2/customer_invoices/42/invoice_line_sections"
  },
  "invoice_lines": {
    "url": "https://app.pennylane.com/api/external/v2/customer_invoices/42/invoice_lines"
  },
  "custom_header_fields": {
    "url": "https://app.pennylane.com/api/external/v2/customer_invoices/42/custom_header_fields"
  },
  "categories": {
    "url": "https://app.pennylane.com/api/external/v2/customer_invoices/42/categories"
  },
  "pdf_invoice_free_text": "Thanks for paying this invoice",
  "pdf_invoice_subject": "Invoice subject",
  "pdf_description": "Invoice description",
  "billing_subscription": {
    "id": 0
  },
  "credited_invoice": {
    "id": 40,
    "url": "https://app.pennylane.com/api/external/v2/customer_invoices/40"
  },
  "customer_invoice_template": {
    "id": 0
  },
  "transaction_reference": {
    "banking_provider": "bank",
    "provider_field_name": "label",
    "provider_field_value": "invoice_number"
  },
  "payments": {
    "url": "https://app.pennylane.com/api/external/v2/customer_invoices/42/payments"
  },
  "matched_transactions": {
    "url": "https://app.pennylane.com/api/external/v2/customer_invoices/42/matched_transactions"
  },
  "appendices": {
    "url": "https://app.pennylane.com/api/external/v2/customer_invoices/42/appendices"
  },
  "quote": {
    "id": 42
  },
  "external_reference": "FR123",
  "archived_at": "2023-08-30T10:08:08.146343Z",
  "created_at": "2023-08-30T10:08:08.146343Z",
  "updated_at": "2023-08-30T10:08:08.146343Z"
}

📘

The invoice can be created as draft of as finalized.

  • draft parameter has to be set up to true if the invoice is to be created as draft
  • PDF generation may take a few minutes
  • public_file_url expires after 30 minutes

6. Additional Actions

6.1 Send Invoice by Email

curl --request POST \
     --url 'https://app.pennylane.com/api/external/v2/customer_invoices/12345/send_by_email' \
     --header 'authorization: Bearer xxx'
     --data '{	
 				"recipients":["[email protected]"]
       }

6.2 Add Categories

curl --request PUT \
     --url 'https://app.pennylane.com/api/external/v2/customer_invoices/12345/categories' \
     --header 'authorization: Bearer xxx' \
     --data '[
       {"id": 789, "weight": "1.0"}
     ]'

6.3 Track Reconciliation Status

curl --request GET \
     --url 'https://app.pennylane.com/api/external/v2/customer_invoices/12345/
matched_transactions' \
     --header 'authorization: Bearer xxx'

7. Best Practices

  1. Data Validation

    • Verify customer exists
    • Check product IDs if used
    • Check ledger account IDs if used
    • Validate VAT rates
    • Ensure amounts are properly formatted
  2. Error Handling

    • 400: Invalid request format
    • 404: Customer/Product/Ledger account not found
    • 422: Validation error
  3. Invoice Management

    • Store invoice ID for reference
    • Track invoice status
    • Monitor payment status
    • Use external_reference for deduplication

⚠️

Important Notes

  • Amounts use "." as decimal separator
  • Dates must be YYYY-MM-DD
  • Store invoice IDs for future reference
  • You are responsible for avoiding duplicates

Need help? Contact support with your invoice ID for troubleshooting.