Automating Invoicing With an API, End to End
· 5 min read
You have a job runner that finishes a project, a CRM that flips a deal to Won, or a SaaS plan that just hit an overage. Now you need an invoice out the door, with the right PO number, the right billing entity, and a link the customer can actually act on. You do not want to build a billing UI, a PDF renderer, a versioning model, and an approval workflow to get there.
Here is a working plan for automating invoicing from your own code, what to send over the wire, what to keep on your side, and where humans still need to sit in the loop.
Decide what your system owns and what the invoicing API owns
Before any code, draw a line. Your system owns customers, contracts, usage, project milestones, and the trigger that says "bill this now." The invoicing layer owns invoice numbers, versioning, the recipient-facing link, PDF rendering, and the approval lifecycle. If you blur that line you end up with two sources of truth and a reconciliation problem at month end.
Concretely, store on your side: a stable customer_id, the line items you intend to bill, the currency, and the external invoice ID you get back. Do not store the rendered PDF or a copy of the invoice number sequence. Let the JupiterInvoice API and MCP server handle issuance, so your invoice number sequence stays consistent across manual and automated invoices.
Model the trigger that creates an invoice
Pick one event per billing motion. Examples:
- Project milestone marked complete in your PM tool, fires a webhook, your worker creates an invoice for that milestone's amount.
- Monthly cron at 09:00 on the 1st builds usage invoices for every active subscription.
- A signed quote in your CRM creates a draft invoice pre-populated from the quote's line items.
For each trigger, write down: what fields you have, what fields you do not, and which fields the customer will fill in themselves. PO number and AP contact almost always belong to the customer. Do not try to guess them. Leave them blank and let the recipient add them on the invoice link, which is what recipient editing on the invoice itself is built for.
A minimal create-and-send flow
The shape of an automated send is the same regardless of trigger. Build a draft, attach line items, set terms, issue it, then share the link.
- POST a draft invoice with
customer,currency,payment_terms(for examplenet_30), and an array ofline_itemswithdescription,quantity,unit_price. - PATCH the draft if you need to add a discount or a custom
due_date. - POST to the issue endpoint. The invoice moves from Draft to Sent and the invoice number is assigned.
- Store the returned
invoice_idand recipientlinkagainst your record. - Email the link, or let JupiterInvoice send it. Either works.
If you are building agents or tool-using LLM features, the MCP server exposes the same operations as tools, so a model can draft, revise, and issue an invoice without you writing a custom function-calling shim.
Handle webhooks for the parts you do not control
The interesting events happen after you send. Subscribe to webhooks for at least these state transitions:
- Viewed: the link was opened. Use this to suppress your "did you get it" follow-up email.
- Amended: the recipient edited the PO number, billing entity, or AP contact. Sync those values back to your CRM so the next invoice carries them forward.
- Change Requested: the recipient wants different line items, pricing, or terms. Route it to a human. Treat it like a support ticket with SLA, not a passive notification.
- Approved: the version is locked. This is your signal to mark the deal as billed and move accounting downstream.
A common mistake is treating amendments and change requests as the same event. They are not. Amendments apply directly and you get notified. Change requests need your approval. If you want the underlying mental model, the post on invoice versions vs amendments covers the distinction, and the change request workflow guide shows how to route them so they do not stall.
Idempotency, retries, and the things that bite at 3am
Every create call should send an Idempotency-Key derived from your internal record, for example invoice:project_4821:milestone_3. If your worker retries because a database write timed out, you do not want two invoices with two different numbers for the same milestone.
Webhook handlers should be idempotent too. Store the event ID, ignore duplicates, and process in order of the event timestamp, not the delivery time. If a webhook delivery fails, the API will retry. Your handler should be safe to run twice.
For sequence integrity, never let your code mint invoice numbers and pass them in. Let the API assign them at issue time. That is the only way to keep the sequence gap-free when issuance fails or a draft is discarded.
When to keep a human in the loop
Full automation is fine for predictable, low-variance invoices: monthly retainers, fixed-price milestones, usage with a clear meter. It is a bad idea for first invoices to a new enterprise customer, anything over a threshold you pick (say, 25,000 in your currency), or any invoice where the customer has historically requested changes. For those, generate the draft automatically and require a human to click issue. You still save the typing.
If you are evaluating this for a product team or an internal tools build, the developer-focused overview of JupiterInvoice covers the API surface, auth, and the recipient experience your customers will see. If you are working inside a SaaS that already runs Stripe for card payments, the SaaS invoicing notes explain where collaborative invoicing fits next to Stripe Billing.
A short build checklist
- Define one trigger per billing motion. Write the event payload down.
- Map each invoice field to either your system, the API, or the recipient.
- Implement create, patch, issue with idempotency keys.
- Subscribe to viewed, amended, change requested, and approved webhooks.
- Decide your auto-issue threshold. Everything above it gets a human click.
- Sync amended PO and billing fields back to your CRM after each invoice.
Once that is in place, your billing code stops being a project and starts being a background job that quietly does its work. If you want to sketch the flow against a real invoice first, create one by hand and inspect the fields before you wire up the API.