Developer docs

REST API & Webhooks

Create invoices from your app, mark them paid from your payment webhook, and receive signed events when anything changes.

Base URLhttps://app.invforge.com/api/public/v1
Create invoice endpoint: POST https://app.invforge.com/api/public/v1/invoices

Authentication

Create 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.

Invoices

GET/api/public/v1/invoiceslist, ?limit=&offset=&status=
POST/api/public/v1/invoicescreate
GET/api/public/v1/invoices/:id
PATCH/api/public/v1/invoices/:idpartial update
DELETE/api/public/v1/invoices/:id
POST/api/public/v1/invoices/:id/statusmark draft/sent/paid

Create an invoice

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"
  }'

Discount types

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_typeBehaviourStripe equivalent
"percent"Deduct N% from the line / subtotalCoupon percent_off
"fixed"Deduct a flat currency amountCoupon 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.

Change status

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" }'

Clients

GET/api/public/v1/clients
POST/api/public/v1/clients
GET/api/public/v1/clients/:id
PATCH/api/public/v1/clients/:id
DELETE/api/public/v1/clients/:id

Webhooks

Add an endpoint URL in Organization → Webhooks and pick which events it should receive. Every delivery includes:

  • X-InvForge-Event — event type
  • X-InvForge-Delivery — unique delivery ID (dedupe on this)
  • X-InvForge-Signature — HMAC-SHA256 of the raw request body, using your endpoint's secret

Events: 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).

Verify a signature (Node)

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);
}

Auto-invoice from Stripe (no code)

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.

  1. Paste your Stripe secret key (live or test).
  2. Copy the webhook URL we show you, add it in Stripe → Developers → Webhooks, and select the checkout.session.completed event.
  3. Paste the signing secret (whsec_…) back into the form.

Use from your SaaS backend

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

Errors return this shape:

{ "error": { "code": "invalid_input", "message": "Validation failed", "details": { ... } } }
  • 401 unauthorized — missing/invalid/revoked key
  • 404 not_found — resource does not belong to this organization
  • 409 duplicate_number — invoice number already exists
  • 422 invalid_input — payload failed validation
  • 500 server_error — unexpected error