Create invoices from your app, mark them paid from your payment webhook, and receive signed events when anything changes.
https://app.invforge.com/api/public/v1POST https://app.invforge.com/api/public/v1/invoicesCreate an API key in Organization → API keys. Keys are scoped to a single organization and start with sk_live_. The full key is shown once at creation — store it in a secure place.
Send it as a bearer token on every request:
curl https://app.invforge.com/api/public/v1/me \
-H "Authorization: Bearer sk_live_..."Base URL: /api/public/v1. All responses are JSON, all POST/PATCH bodies must be JSON.
Omit number to use your organization's automatic numbering (uses prefix, padding, next number). Supply it to set it manually — duplicates within the same organization return 409.
curl -X POST https://app.invforge.com/api/public/v1/invoices \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"client_name": "Apex Manufacturing Co.",
"client_address": "890 Industrial Park Road, Dallas, TX 75201",
"client_email": "[email protected]",
"client_vat": "US334455667",
"currency": "USD",
"issue_date": "2026-07-26",
"due_date": "2026-09-01",
"items": [
{
"description": "Industrial Equipment Maintenance",
"qty": 6, "rate": 1200,
"tax": 8.25, "discount": 300
},
{
"description": "Replacement Parts",
"qty": 15, "rate": 85,
"tax": 8.25, "discount": 0
},
{
"description": "On-site Technician Visit",
"qty": 4, "rate": 450,
"tax": 8.25, "discount": 100,
"discount_type": "percent"
}
],
"discount_type": "fixed",
"discount_value": 500,
"tax_type": "percent",
"tax_value": 8.25,
"tax_label": "Sales Tax",
"shipping": 150,
"status": "draft"
}'Discounts can be applied at the invoice level and/or per line item. Both support the same two modes, which map directly to Stripe coupon types:
| discount_type | Behaviour | Stripe equivalent |
|---|---|---|
| "percent" | Deduct N% from the line / subtotal | Coupon percent_off |
| "fixed" | Deduct a flat currency amount | Coupon amount_off |
Inheritance rule: if a line item does not include discount_type, it inherits the invoice-level discount_type. In the example above, the first two items have no per-item type so they inherit "fixed" from the invoice — their discount values (300 and 0) are treated as flat currency amounts. The third item explicitly overrides to "percent", so its 100 means 100% off.
curl -X POST https://app.invforge.com/api/public/v1/invoices/<id>/status \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "status": "paid" }'Add an endpoint URL in Organization → Webhooks and pick which events it should receive. Every delivery includes:
X-InvForge-Event — event typeX-InvForge-Delivery — unique delivery ID (dedupe on this)X-InvForge-Signature — HMAC-SHA256 of the raw request body, using your endpoint's secretEvents: invoice.created, invoice.updated, invoice.status_changed, invoice.paid, invoice.client_edited, invoice.deleted, client.created, client.updated.
Failed deliveries retry 5 times with backoff (1m, 5m, 30m, 2h, 12h).
import { createHmac, timingSafeEqual } from "crypto";
export function verify(rawBody: string, header: string, secret: string) {
const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(header, "hex");
const b = Buffer.from(expected, "hex");
return a.length === b.length && timingSafeEqual(a, b);
}Connect Stripe in Organization → Auto-invoice from Stripe. On every successful checkout.session.completed event we create a paid invoice using the buyer's name, address, and VAT from the Stripe customer, mapping each Checkout line item to an invoice line. Duplicates are deduped by Stripe session id, so retries are safe.
checkout.session.completed event.whsec_…) back into the form.Typical use: create an invoice from your own payment or order webhook.
Node / fetch
await fetch("https://app.invforge.com/api/public/v1/invoices", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.INVFORGE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
client_name: "Acme Inc",
client_address: "1 Market St\nSan Francisco CA",
currency: "USD",
status: "paid",
items: [{ description: "Pro plan (monthly)", qty: 1, rate: 29 }],
}),
});Python
import os, requests
requests.post(
"https://app.invforge.com/api/public/v1/invoices",
headers={"Authorization": f"Bearer {os.environ['INVFORGE_API_KEY']}"},
json={
"client_name": "Acme Inc",
"currency": "USD",
"status": "paid",
"items": [{"description": "Pro plan", "qty": 1, "rate": 29}],
},
)cURL
curl https://app.invforge.com/api/public/v1/invoices \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"client_name":"Acme","currency":"USD","status":"paid","items":[{"description":"Pro","qty":1,"rate":29}]}'Errors return this shape:
{ "error": { "code": "invalid_input", "message": "Validation failed", "details": { ... } } }401 unauthorized — missing/invalid/revoked key404 not_found — resource does not belong to this organization409 duplicate_number — invoice number already exists422 invalid_input — payload failed validation500 server_error — unexpected error