Invoice Generation API

Generate professional invoice PDFs programmatically from your own server, suitable for internal systems, marketplaces, or your own SaaS product.

1. Authentication#

Create an API key at app.invosend.com/settings/api-keys. A paid workspace plan and owner role are required.

Send the key as a Bearer token in the Authorization header on every request:

Authorization: Bearer isk_live_xxxxxxxxxxxxxxxxxxxxxxxx
Missing, malformed, invalid, or revoked API keys are rejected with 403. Keys are also rejected when the workspace is on a free plan, suspended, or expired.

2. Endpoint#

POST/api/invoices/generate-external

Base URL: https://app.invosend.com

Machine-readable API description: OpenAPI 3.1 and Postman collection. Import either one into Postman, Insomnia, or an SDK generator.

3. Request Body#

FieldTypeRequiredDescription
customer_namestringYesClient or invoice recipient name.
customer_emailstringNoValid email address. The invoice created by this call is not emailed (this API does not send email). Required when the invoice is recurring: each invoice generated afterwards is emailed to this address.
customer_tax_idstringNoTax registration number of the client (NPWP in Indonesia, up to 40 characters). Printed under the client's address on the invoice.
itemsarrayYesAt least 1 valid item with description, quantity > 0, and price >= 0.
items[].descriptionstringNoProduct or service name. Defaults to Item when empty.
items[].quantitynumberYesNumber of units, with up to 2 decimals (for example 1.5 hours), from 0.01 to 999,999.
items[].pricenumberYesPrice per unit.
items[].discountnumberNoDiscount amount per item. Defaults to 0.
currencystringNoCurrency code, such as IDR or USD. Defaults to IDR.
tax_ratenumberNoTax percentage calculated from subtotal after discount. Defaults to 0.
due_datestringNoFormat YYYY-MM-DD. Defaults to 14 days after generation.
notesstringNoAdditional notes shown on the invoice.
items[].unitstringNoUnit label shown next to the quantity, such as pcs or hour.
items[].tax_ratenumberNoPer-item tax percentage override (0-100). Omit to use the invoice-level tax_rate for that item.
discount_totalnumberNoInvoice-level discount. It is taken off before tax, so tax is calculated on the discounted amount (shared across the items in proportion to their value). Defaults to 0.
templatestringNoID of the invoice template to use. List the available ones with GET /api/invoices/external/templates. Without it, the default template chosen in Settings is used, then Invosend's default.
document_languagestringNoLanguage of the words on the invoice document, the PDF and the email: en (the default), id, es, fr, de, nl, it, pt, zh, ja, hi, ar (Indonesian, Spanish, French, German, Dutch, Italian, Brazilian Portuguese, Simplified Chinese, Japanese, Hindi, Arabic). Any other value is refused with an error. The plain built-in layout, used only when no template exists, is always English.
custom_payment_notesstringNoExtra payment instructions shown on the PDF.
footer_textstringNoShort text shown at the bottom of the PDF.
is_recurringbooleanNoCreates a recurring series. This invoice is the first one; the next invoices are generated and emailed to customer_email automatically on schedule, using 1 invoice credit each. Requires a plan with the recurring invoices feature, plus recurring_interval and customer_email.
recurring_intervalstringNoHow often the series repeats: one of weekly, fortnightly, monthly, quarterly, semi-annually, yearly. Required when is_recurring is true.
recurring_countintegerNoTotal number of invoices in the series, including this first one: a whole number from 2 to 121. Defaults to 4. Use this or recurring_end_date, not both.
recurring_end_datestringNoDate after which no more invoices are generated, YYYY-MM-DD, in the future. Use this or recurring_count, not both.
payment_gatewaystringNoOne of midtrans, xendit, stripe, paypal, razorpay, mollie. The gateway must already be connected in the workspace.
payment_accountstringNoID of an existing payment account (bank details) to attach to the invoice.

Complete request example:

curl -X POST https://app.invosend.com/api/invoices/generate-external \
  -H "Authorization: Bearer isk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "Acme Corp",
    "customer_email": "billing@acme.com",
    "items": [
      { "description": "Consulting Services", "quantity": 2, "price": 500, "tax_rate": 11 }
    ],
    "discount_total": 50,
    "currency": "USD",
    "notes": "Thank you for your business."
  }'

Recurring invoice example (first invoice now, then one every month, 6 in total):

curl -X POST https://app.invosend.com/api/invoices/generate-external \
  -H "Authorization: Bearer isk_live_xxxxxxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "customer_name": "Acme Corp",
    "customer_email": "billing@acme.com",
    "items": [
      { "description": "Monthly retainer", "quantity": 1, "price": 2500 }
    ],
    "currency": "USD",
    "recurring_interval": "monthly",
    "recurring_count": 6
  }'
To retry safely after a timeout or network error, add an Idempotency-Key header (1-255 characters, for example a UUID you generate for each invoice). If the first request succeeded, the retry returns the same response with the header Idempotent-Replayed: true, without creating another invoice or using another credit (credits_remaining is the value from the first call). Keys are kept for 24 hours per workspace. Only successful responses are stored, so a request that failed can be retried with the same key.

4. Response#

200 OK means the invoice was created and the PDF was generated:

{
  "data": {
    "id": "cjld2cjxh0000qzrmn831i7rn",
    "invoice_number": "INV-1-202609-0016",
    "pdf_url": "https://cdn.invosend.com/company/1/pdf/Invoice_INV_1_202609_0016_UNPAID_0da2beeabb.pdf",
    "credits_remaining": 24,
    "is_recurring": false,
    "template": {
      "id": "k3p9x2m7c0000abcd1234efgh",
      "name": "Classic",
      "source": "system_default"
    }
  }
}
credits_remaining is null when the workspace has unlimited quota. id identifies this invoice for the Additional Endpoints below. Invoices created through this API also appear in the workspace invoice list and use the same invoice number sequence as manually-created invoices. Invoice numbers look like INV-{workspace id}-{YYYYMM}-{sequence}. For a recurring invoice the response also includes a recurring object: schedule_id, interval, occurrences, next_issue_date, end_date. recurring.schedule_id is the id to use with the recurring endpoints below.

5. Error Codes#

StatusCause
400Required fields are missing or invalid: customer_name, empty items, no valid item, an unrecognized payment_gateway, a payment_gateway that is not connected for this workspace, or, for a recurring invoice, a missing recurring_interval or customer_email or an invalid recurring_count / recurring_end_date. This status is also returned if the PDF could not be generated: retry the request (no credit is deducted).
402The workspace invoice_credits balance is empty. Top up credits to continue.
403The Authorization header is missing or malformed, the API key is invalid or revoked, or the workspace plan does not include API access.
403 (recurring)is_recurring was set but the workspace plan does not include recurring invoices.
409A request with the same Idempotency-Key is still being processed. Retry in a moment.
422The Idempotency-Key was already used with a different request body. Use a new key for a new invoice.
429Rate limit exceeded. See the Rate Limit section.

6. Rate Limit#

ScopeLimit
Per workspace (all API requests combined)Professional 10/min · Specialist 20/min · Business 30/min · Enterprise 60/min
Per IP address (all API requests combined)30 requests / minute
Active API keys per workspaceProfessional 3 · Specialist 5 · Business 10 · Enterprise 20
Generating invoices and the read/manage endpoints share the same limit. Successful responses include X-RateLimit-Limit and X-RateLimit-Remaining headers. Workspace API quotas are configured per membership plan in Invosend. Need higher production volume? Contact us from your workspace account. Revoked keys do not count toward the key limit.

7. Additional Endpoints#

The same API key can also read and manage invoices it created through generate-external:

MethodPathDescription
GET/api/invoices/externalList invoices for this workspace. Supports ?status= and ?page=/?pageSize= query params.
GET/api/invoices/external/templatesList the invoice templates you can name in template, with whether your plan allows each and which is your default.
GET/api/invoices/external/:idGet one invoice by its id (the value returned by generate-external).
PUT/api/invoices/external/:id/statusUpdate the invoice status. Body: { "status": "paid" } (one of unpaid, paid, overdue, cancelled).
PUT/api/invoices/external/:id/archiveArchive or unarchive an invoice. Body: { "is_archived": true }.
POST/api/invoices/external/:id/pdfRe-fetch the PDF URL, regenerating it if needed. Body: { "force": true } to force regeneration.
GET/api/invoices/external/recurringList recurring series. Supports ?status= (active, paused, completed) and ?page=/?pageSize=.
GET/api/invoices/external/recurring/:idGet one series (the recurring.schedule_id returned by generate-external) with all its occurrences: date, status, and the invoice id each one produced.
PUT/api/invoices/external/recurring/:id/statusPause or resume a series. Body: { "status": "paused" } or { "status": "active" }. Resuming needs a plan with recurring invoices.
PUT/api/invoices/external/recurring/:id/extendAdd occurrences after the last one. Body: { "count": 3 } or { "end_date": "2027-12-31" }.
PUT/api/invoices/external/recurring/:id/frequencyChange how often the series repeats; upcoming occurrences that have not been generated are replaced. Body: { "frequency": "monthly", "start_date": "2027-01-01", "count": 6 } (use end_date instead of count if you prefer; start_date must be in the future).
POST/api/invoices/external/recurring/:id/skipRemove one upcoming occurrence. Body: { "date": "2027-01-01" }. Occurrences that already produced an invoice cannot be removed.
To collect an online payment, set payment_gateway (and optionally payment_account) on generate-external, then send your customer the invoice's public payment page: https://app.invosend.com/p/invoice/{id}, using the id returned by the API. They pay there through the connected gateway and the invoice is marked paid automatically. Checkout is handled by that page; there is no separate public checkout endpoint to call.

8. Webhooks#

Instead of polling, register an HTTPS URL and Invosend sends it a POST request with a JSON body whenever an invoice changes. Add endpoints in Settings > Webhooks (workspace owner, plan with API access, up to 5 endpoints). Each endpoint has its own signing secret, shown only once.

Events are sent for invoices created in the dashboard and through this API alike. Recurring series templates are never sent.

EventSent when
invoice.createdAn invoice is created. The PDF may not be ready yet, so pdf_url can be null.
invoice.paidThe status changes to paid (by you, through the API, or by an online payment).
invoice.overdueThe status changes to overdue (automatically after the due date, or manually).
invoice.cancelledThe status changes to cancelled.

Every event has the same envelope. data.object is the invoice, in the same shape as GET /api/invoices/external/:id; status events also include data.previous_status:

{
  "id": "evt_9f2c1b7a4e3d5c6b8a1f0e2d",
  "object": "event",
  "type": "invoice.paid",
  "created": "2026-10-01T09:30:12.000Z",
  "data": {
    "object": {
      "id": "cjld2cjxh0000qzrmn831i7rn",
      "invoice_number": "INV-1-202610-0007",
      "customer_name": "Acme Corp",
      "customer_email": "billing@acme.com",
      "currency": "USD",
      "subtotal": 500,
      "discount_total": 0,
      "tax_rate": 0,
      "tax_amount": 0,
      "amount": 500,
      "amount_paid": 500,
      "amount_credited": 0,
      "amount_received": 500,
      "invoice_status": "paid",
      "is_archived": false,
      "is_recurring": false,
      "recurring_interval": "none",
      "issue_date": "2026-10-01T02:15:40.000Z",
      "due_date": "2026-10-15",
      "paid_at": "2026-10-01T09:30:11.000Z",
      "pdf_url": "https://cdn.invosend.com/company/1/pdf/Invoice_INV_1_202610_0007_PAID.pdf",
      "pdf_status": "ready"
    },
    "previous_status": "unpaid"
  }
}

Each request carries these headers. To verify it, compute an HMAC-SHA256 of {t}.{raw body} with the endpoint's secret and compare it with v1; reject it when t is more than 5 minutes old:

POST /your/webhook/url
Content-Type: application/json
Invosend-Event: invoice.paid
Invosend-Delivery: evt_9f2c1b7a4e3d5c6b8a1f0e2d
Invosend-Signature: t=1790847012,v1=5b1d...e9a2

// Node.js
import crypto from "crypto";

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto
    .createHmac("sha256", secret)
    .update(parts.t + "." + rawBody)
    .digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}
Reply with any 2xx status within 10 seconds. Anything else is retried up to 7 more times (after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours, 12 hours and 24 hours). An endpoint that keeps failing is switched off and you are notified. An event can arrive more than once or out of order: use the event id to ignore duplicates and treat invoice_status as the source of truth. Delivery logs are kept for 30 days, and any event can be resent from Settings > Webhooks.

9. Limitations#

This API is intentionally scoped to invoice generation and light invoice management. The following are not available yet:

  • Webhooks cover only invoice created, paid, overdue and cancelled. Other changes, such as edits or archiving, are not sent: read the current state with GET /invoices/external/:id.
  • The invoice created by each request is not emailed: this API only generates the PDF, so delivering it to your customer is up to you (or use the dashboard). The exception is recurring series: each follow-up invoice generated by the scheduler is emailed automatically to customer_email.
  • No editing of an invoice after creation — only its status and archived state can be changed via this API.
  • Payment gateway support requires the gateway to already be connected in the dashboard; this API only attaches it to the invoice, it does not connect a gateway on its own.
  • A recurring series can be paused, resumed, extended, re-timed and have upcoming occurrences skipped through the API, but its content (items, customer, amounts) cannot be edited and a series cannot be deleted from the API; do those in the dashboard.

Frequently Asked Questions#

How do I get an Invosend API key?

Create an account at app.invosend.com, upgrade the workspace to a paid plan, then open Settings > API Keys and click Generate key. The raw key is shown only once when it is created.

How much does each generate invoice API call cost?

Each successful request deducts 1 invoice credit from your workspace balance. It uses the same invoice credit pool as invoices created through the dashboard.

What is the rate limit for this API?

The workspace rate limit follows your paid plan configuration. The Rate Limit section above lists the current limit of each plan.