> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.paymentkit.com/guides/billing/invoices/create-an-invoice/llms.txt. > For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.paymentkit.com/_mcp/server. # Create an invoice # One-off invoices Create a standalone invoice for a customer that is not tied to a subscription. Use one-off invoices for custom charges, consulting fees, setup fees, or any non-recurring billing. #### Dashboard 1. Go to **Billing > Invoices** in the sidebar 2. Click **Create Invoice** 3. Select a customer 4. Set the currency and issued date 5. Add line items with descriptions, quantities, and amounts 6. Click **Create** The invoice is created in **Draft** state. Finalize it to begin collection. #### API ```bash curl -X POST https://app.paymentkit.com/api/{account_id}/invoices \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cus_abc123", "currency": "USD", "issued_at": "2026-02-01T00:00:00Z", "description": "Setup fee - Enterprise onboarding", "items": [ { "description": "Enterprise onboarding", "quantity": 1, "amount": 500.00 }, { "description": "Custom integration development", "quantity": 4, "unit_amount": 150.00 } ] }' ``` #### Python SDK ```python invoice = client.invoices.create( account_id="acc_abc123", customer_id="cus_abc123", currency="USD", issued_at="2026-02-01T00:00:00Z", description="Setup fee - Enterprise onboarding", items=[ { "description": "Enterprise onboarding", "quantity": 1, "amount": 500.00 }, { "description": "Custom integration development", "quantity": 4, "unit_amount": 150.00 } ] ) ``` ## Line item fields | Field | Description | | ------------- | ------------------------------------------------------------------------- | | `name` | Display name for the line item | | `description` | Description displayed on the invoice | | `quantity` | Number of units (default: 1) | | `unit_amount` | Price per unit | | `amount` | Total amount for this line item (alternative to `unit_amount * quantity`) | | `price_id` | Link to an existing price in your product catalog | | `metadata` | Free-form metadata for the item | Use `price_id` to reference a catalog price, or provide `amount`/`unit_amount` for ad-hoc charges. # Subscription invoices Subscription invoices are generated automatically by the billing lifecycle. You do not need to create them manually. PaymentKit generates invoices at these points: | Event | Billing reason | | ------------------------------------------------------------ | --------------------- | | Subscription created | `subscription_create` | | Billing period renews | `subscription_cycle` | | Subscription items changed (with `always_invoice` proration) | `subscription_update` | Each invoice contains line items built from the subscription's active items at the time of generation. # Invoice lifecycle Every invoice follows this state flow: | State | Description | | ------------------ | ------------------------------------------------------------------- | | **Draft** | Invoice is being prepared. Items and amounts can still be modified. | | **Open** | Finalized and ready for payment. Amounts are locked. | | **Paid** | Payment collected in full. | | **Partially paid** | Some payment received, balance remaining. | | **Past due** | Payment deadline has passed. Dunning retries are active. | | **Void** | Invoice cancelled. No payment expected. | | **Uncollectible** | All collection attempts exhausted. | ## Transitions | From | To | Trigger | | -------------- | -------------- | -------------------------------- | | Draft | Open | Invoice finalized | | Draft | Void | Invoice voided before finalizing | | Open | Paid | Payment succeeds in full | | Open | Partially paid | Partial payment received | | Open | Past due | Payment deadline passes | | Open | Void | Invoice voided | | Open | Uncollectible | Dunning exhausted | | Partially paid | Paid | Remaining balance paid | | Partially paid | Past due | Payment deadline passes | | Partially paid | Void | Invoice voided | | Past due | Paid | Payment recovered | | Past due | Partially paid | Partial payment received | | Past due | Uncollectible | Dunning exhausted | | Uncollectible | Void | Invoice voided | # Finalize an invoice Finalizing moves a draft invoice to **Open** state, locks all amounts, and optionally triggers PDF generation and email delivery. #### API ```bash curl -X POST https://app.paymentkit.com/api/{account_id}/invoices/{invoice_id}/finalize \ -H "Authorization: Bearer sk_live_..." ``` #### Python SDK ```python invoice = client.invoices.finalize( account_id="acc_abc123", invoice_id="in_abc123" ) ``` # Collect payment Attempt to collect payment on an open invoice: ```bash curl -X POST https://app.paymentkit.com/api/{account_id}/invoices/{invoice_id}/collect \ -H "Authorization: Bearer sk_live_..." ``` ## Success response When payment succeeds, the response includes the payment outcome: ```json { "invoice_id": "in_abc123", "invoice_status": "paid", "subscription_id": null, "payment_status": "paid", "error_message": null } ``` ## Payment failure (402 response) When payment fails, the API returns a **402 Payment Required** status code and the invoice remains in its current state (OPEN or PAST\_DUE): **Card declined:** ```json { "error": "Payment failed for invoice collection", "error_code": "PAYMENT_FAILED", "payment_status": "failed", "payment_error": "Your card was declined. Please try a different payment method.", "orchestrator_summary": "Card declined by issuer (insufficient_funds)", "invoice_id": "in_abc123", "invoice_status": "open" } ``` **No payment method:** ```json { "error": "Cannot collect invoice without a payment method", "error_code": "PAYMENT_FAILED", "payment_status": "no_payment_method", "payment_error": "no_payment_method", "invoice_id": "in_abc123", "invoice_status": "open" } ``` > **Warning** > > When payment fails, the invoice is **NOT marked as paid** and remains in its current state. This allows for safe retry without double-charging. Update the payment method and retry collection. If the invoice is still in **Draft** state, the collect endpoint finalizes it before attempting payment. > **Tip** > > For subscription invoices, payment collection is handled automatically by the lifecycle engine. Use the collect endpoint for manual invoices or to retry a failed payment. # Batch payment collection Pay multiple invoices at once using confirmed payment intents. This endpoint is designed for checkout flows that create multiple subscriptions simultaneously, where payment intents are collected upfront and then applied to invoices in batch. ```bash curl -X POST https://app.paymentkit.com/api/{account_id}/subscriptions/pay-multiple-invoices \ -H "Authorization: Bearer sk_live_..." \ -H "Content-Type: application/json" \ -d '{ "account_id": "acc_abc123", "customer_id": "cus_xyz789", "payment_intent_ids": ["pi_intent1", "pi_intent2"] }' ``` **Request fields:** | Field | Type | Required | Description | | -------------------- | ------ | -------- | ------------------------------------------------------------------------------- | | `account_id` | string | Yes | Account external ID | | `customer_id` | string | Yes | Customer external ID (all payment intents must belong to this customer) | | `payment_intent_ids` | array | Yes | List of payment intent IDs (must all be in `succeeded` status) | | `invoice_ids` | array | No | Specific invoices to pay. If omitted, matches invoices automatically by amount. | **Response:** ```json { "outcomes": [ { "invoice_id": "inv_abc123", "invoice_status": "paid", "subscription_id": "sub_xyz789", "subscription_state": "active", "processor_attempt_id": "pa_attempt1", "processor_attempt_status": "succeeded", "success": true, "error_message": null } ], "total_processed": 2, "total_succeeded": 2, "total_failed": 0, "overall_success": true } ``` **Key behaviors:** * **Payment matching**: Invoices are matched to payment intents by exact amount first, then by sorted order * **Subscription activation**: Incomplete subscriptions linked to paid invoices automatically transition to **Active** * **Partial failures**: Each invoice processes independently — one failure does not block others * **Idempotent**: Safe to retry; already-paid invoices are skipped > **Note** > > All payment intents must be in `succeeded` status and belong to the same customer. The endpoint validates this before processing. # Webhook events | Event | Trigger | | ------------------------------ | ---------------------------- | | `invoice.created` | Invoice created | | `invoice.finalized` | Draft finalized to Open | | `invoice.paid` | Payment collected in full | | `invoice.payment_succeeded` | A payment attempt succeeded | | `invoice.payment_failed` | A payment attempt failed | | `invoice.overdue` | Invoice became past due | | `invoice.voided` | Invoice voided | | `invoice.marked_uncollectible` | Invoice marked uncollectible | > Generate one-off invoices for custom charges, or understand how subscription invoices are created automatically.