发票生成 API

从你自己的服务器以编程方式生成专业 PDF 发票,适用于内部系统、市场平台或 SaaS 产品。

1. 认证#

在 app.invosend.com/settings/api-keys 创建 API key。需要付费工作区套餐和 owner 角色。

每个请求都在 Authorization header 中以 Bearer token 发送 key:

Authorization: Bearer isk_live_xxxxxxxxxxxxxxxxxxxxxxxx
缺失、格式错误、无效或已撤销的 API key 会返回 403。免费、暂停或过期的工作区也会被拒绝。

2. Endpoint#

POST/api/invoices/generate-external

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

机器可读的 API 描述:OpenAPI 3.1 和 Postman collection。可将其中任意一个导入 Postman、Insomnia 或 SDK 生成器。

3. Request Body#

字段类型必填说明
customer_namestring是客户或发票接收方名称。
customer_emailstring否有效的电子邮件地址。通过本次调用创建的发票不会通过邮件发送(此 API 不发送邮件)。定期发票必填:之后自动生成的每张发票都会发送到该地址。
customer_tax_idstring否客户的税务登记号(印度尼西亚为 NPWP,最多 40 个字符)。显示在发票上客户地址的下方。
itemsarray是至少 1 个有效项目,包含描述、quantity > 0 且 price >= 0。
items[].descriptionstring否产品或服务名称。留空时使用 Item。
items[].quantitynumber是数量,最多 2 位小数(例如 1.5 小时),范围 0.01 到 999,999。
items[].pricenumber是单价。
items[].discountnumber否每项折扣金额,默认 0。
currencystring否货币代码,例如 CNY、USD 或 IDR。
tax_ratenumber否按折扣后小计计算的税率,默认 0。
due_datestring否格式 YYYY-MM-DD,默认生成后 14 天。
notesstring否显示在发票上的附加说明。
items[].unitstring否数量旁显示的单位,例如 pcs 或 小时。
items[].tax_ratenumber否单项税率覆盖(0-100)。留空则使用发票级别的 tax_rate。
discount_totalnumber否发票级别折扣。在计税前扣除,因此税额按折后金额计算(按各项金额比例分摊到各项目)。默认 0。
templatestring否要使用的发票模板 ID。可通过 GET /api/invoices/external/templates 查看可用模板。省略时使用在设置中选择的默认模板,其次是 Invosend 的默认模板。
document_languagestring否发票文档(PDF 和邮件)中文字所用的语言:en(默认)、id, es, fr, de, nl, it, pt, zh, ja, hi, ar(印度尼西亚语、西班牙语、法语、德语、荷兰语、意大利语、巴西葡萄牙语、简体中文、日语、印地语、阿拉伯语)。其他值会被拒绝并返回错误。仅在没有模板时使用的简易内置版式始终为英文。
custom_payment_notesstring否PDF 上显示的额外付款说明。
footer_textstring否PDF 底部显示的简短文字。
is_recurringboolean否创建一个定期发票系列。本发票是第一张;后续发票会按计划自动生成并发送到 customer_email,每张使用 1 个发票额度。需要包含定期发票功能的套餐,并同时提供 recurring_interval 和 customer_email。
recurring_intervalstring否系列的重复频率:weekly、fortnightly、monthly、quarterly、semi-annually、yearly 之一。当 is_recurring 为 true 时必填。
recurring_countinteger否系列中发票的总数(包含这第一张):2 到 121 的整数。默认为 4。请使用此字段或 recurring_end_date,不要同时使用。
recurring_end_datestring否在此日期之后不再生成发票,格式 YYYY-MM-DD,必须是未来日期。请使用此字段或 recurring_count,不要同时使用。
payment_gatewaystring否取值为 midtrans、xendit、stripe、paypal、razorpay、mollie 之一,该网关须已在工作区中连接。
payment_accountstring否要附加到发票的已有收款账户(银行信息)ID。

完整请求示例:

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": 3500, "tax_rate": 11 }
    ],
    "discount_total": 350,
    "currency": "CNY",
    "notes": "Thank you for your business."
  }'

定期发票示例(现在开出第一张,之后每月一张,共 6 张):

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": 17500 }
    ],
    "currency": "CNY",
    "recurring_interval": "monthly",
    "recurring_count": 6
  }'
为了在超时或网络错误后安全重试,请添加 Idempotency-Key 请求头(1-255 个字符,例如为每张发票生成的 UUID)。如果第一次请求已成功,重试会返回相同的响应并带有 Idempotent-Replayed: true 头,不会再创建发票,也不会再消耗额度(credits_remaining 是第一次调用时的值)。密钥按工作区保留 24 小时。只有成功的响应会被保存,因此失败的请求可以使用同一密钥重试。

4. 响应#

200 OK 表示发票已创建且 PDF 已生成:

{
  "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 为 null。id 用于调用下方的附加接口。通过 API 创建的发票也会出现在工作区发票列表中,并使用同一编号序列。 发票编号形如 INV-{工作区 ID}-{YYYYMM}-{序号}。对于定期发票,响应还会包含一个 recurring 对象:schedule_id、interval、occurrences、next_issue_date、end_date。 recurring.schedule_id 是下方定期接口所用的 ID。

5. 错误代码#

状态原因
400必填字段缺失或无效:customer_name、items 为空、没有有效条目、无法识别的 payment_gateway、此工作区尚未连接的 payment_gateway;对于定期发票,则是缺少 recurring_interval 或 customer_email,或 recurring_count / recurring_end_date 无效。PDF 生成失败时也会返回此状态:请重试请求(不会扣除额度)。
402工作区 invoice_credits 余额已用完。
403Authorization header 缺失/格式错误,API key 无效/撤销,或套餐不包含 API 访问。
403(周期性发票)设置了 is_recurring,但工作区套餐不包含周期性发票功能。
409具有相同 Idempotency-Key 的请求仍在处理中。请稍后重试。
422该 Idempotency-Key 已用于不同的请求正文。新发票请使用新的密钥。
429超过速率限制。请查看速率限制部分。

6. 速率限制#

范围限制
按工作区(所有 API 请求合计)Professional 10/min · Specialist 20/min · Business 30/min · Enterprise 60/min
按 IP 地址(所有 API 请求合计)30 次/分钟
每个工作区的有效 API 密钥数Professional 3 · Specialist 5 · Business 10 · Enterprise 20
生成发票与读取/管理类端点共用同一限额。成功的响应包含 X-RateLimit-Limit 和 X-RateLimit-Remaining 头。工作区的 API 配额按 Invosend 的会员套餐配置。需要更高的生产用量?请通过你的工作区账户联系我们。 已撤销的密钥不计入密钥数量上限。

7. 附加接口#

同一个 API key 也可以读取和管理通过 generate-external 创建的发票:

方法路径说明
GET/api/invoices/external列出该工作区的发票,支持 ?status= 及 ?page=/?pageSize= 查询参数。
GET/api/invoices/external/templates列出可在 template 中使用的发票模板,以及您的套餐是否允许、哪个是默认模板。
GET/api/invoices/external/:id根据 id(generate-external 返回的值)获取单张发票。
PUT/api/invoices/external/:id/status更新发票状态。请求体:{ "status": "paid" }(unpaid、paid、overdue、cancelled 之一)。
PUT/api/invoices/external/:id/archive归档或取消归档发票。请求体:{ "is_archived": true }。
POST/api/invoices/external/:id/pdf重新获取 PDF 链接,必要时重新生成。请求体:{ "force": true } 可强制重新生成。
GET/api/invoices/external/recurring列出定期系列。支持 ?status=(active、paused、completed)和 ?page=/?pageSize=。
GET/api/invoices/external/recurring/:id获取一个系列(即 generate-external 返回的 recurring.schedule_id)及其所有期次:日期、状态,以及每期生成的发票 id。
PUT/api/invoices/external/recurring/:id/status暂停或恢复系列。请求体:{ "status": "paused" } 或 { "status": "active" }。恢复需要包含定期发票功能的套餐。
PUT/api/invoices/external/recurring/:id/extend在最后一期之后追加期次。请求体:{ "count": 3 } 或 { "end_date": "2027-12-31" }。
PUT/api/invoices/external/recurring/:id/frequency更改系列的重复频率;尚未生成的后续期次会被替换。请求体:{ "frequency": "monthly", "start_date": "2027-01-01", "count": 6 }(也可用 end_date 代替 count;start_date 必须在未来)。
POST/api/invoices/external/recurring/:id/skip删除一个即将到来的期次。请求体:{ "date": "2027-01-01" }。已生成发票的期次无法删除。
要收取在线付款,请在 generate-external 中设置 payment_gateway(也可设置 payment_account),然后把发票的公开付款页面发给客户:https://app.invosend.com/p/invoice/{id},其中 id 为 API 返回的值。客户在该页面通过已连接的网关付款,发票会自动标记为已付。结账由该页面处理,没有需要另外调用的公开结账端点。

8. Webhook#

无需轮询:注册一个 HTTPS 地址后,每当发票发生变化,Invosend 都会向该地址发送带有 JSON 正文的 POST 请求。请在 设置 > Webhooks 中添加端点(需要工作区所有者、具备 API 访问权限的套餐,最多 5 个端点)。每个端点都有自己的签名密钥,仅显示一次。

在仪表板中创建的发票和通过此 API 创建的发票都会发送事件。定期发票模板不会发送。

事件发送时机
invoice.created发票被创建。PDF 可能尚未就绪,因此 pdf_url 可能为 null。
invoice.paid状态变为 paid(由您、通过 API 或在线支付触发)。
invoice.overdue状态变为 overdue(到期后自动变更,或手动变更)。
invoice.cancelled状态变为 cancelled。

所有事件使用相同的信封结构。data.object 是发票,格式与 GET /api/invoices/external/:id 相同;状态事件还包含 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"
  }
}

每个请求都带有以下请求头。验证方法:使用端点密钥对 {t}.{原始正文} 计算 HMAC-SHA256,并与 v1 比较;如果 t 早于 5 分钟前则拒绝:

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));
}
请在 10 秒内返回任意 2xx 状态码。否则最多再重试 7 次(分别在 1 分钟、5 分钟、30 分钟、2 小时、6 小时、12 小时和 24 小时后)。持续失败的端点会被关闭,并通知您。事件可能重复或乱序到达:请用事件 id 去重,并以 invoice_status 为准。投递日志保留 30 天,可在设置 > Webhooks 中重新发送任意事件。

9. 限制#

此 API 有意限定在发票生成和轻量管理范围内,以下功能尚不支持:

  • Webhook 仅涵盖发票创建、已付款、逾期和已取消。其他变更(如编辑或归档)不会发送:请使用 GET /invoices/external/:id 读取当前状态。
  • 每次请求创建的发票不会通过邮件发送:此 API 只生成 PDF,将其送达客户由你自己负责(或使用控制台)。例外是定期系列:调度器生成的每张后续发票都会自动发送到 customer_email。
  • 发票创建后不可编辑 —— 通过此 API 只能修改状态和归档状态。
  • 使用支付网关前必须已在仪表盘中连接该网关;此 API 只是将其关联到发票,不会自动连接网关。
  • 定期系列可以通过 API 暂停、恢复、延长、调整频率并跳过后续期次,但其内容(项目、客户、金额)无法编辑,也不能通过 API 删除系列;请在控制台中操作。

常见问题#

如何获取 Invosend API key?

在 app.invosend.com 创建账号,将工作区升级到付费套餐,然后打开 Settings > API Keys 并点击 Generate key。原始 key 只会在创建时显示一次。

每次生成发票 API 调用消耗多少?

每个成功请求会从工作区余额扣除 1 个发票额度,与通过仪表盘创建发票使用同一额度池。

API 的速率限制是多少?

工作区的速率限制取决于你的付费套餐配置。上方“Rate Limit”部分列出了各套餐当前的限制。