InvoiceFlowREST reference

Agent SDK

Invoicing for things that don't use browsers.

One bearer key. Plain HTTPS. No OAuth dance, no cookies, no session, no headless Chrome. An agent can create a client, issue an invoice, send it, and hand back a payable link — in four calls.

zero dependencies · Node 18+
curl -O https://invoiceflow-ten-azure.vercel.app/sdk/invoiceflow-client.mjs
import { InvoiceFlowClient } from "./invoiceflow-client.mjs";

const iflow = new InvoiceFlowClient({ apiKey: process.env.INVOICEFLOW_API_KEY });

const { client }  = await iflow.createClient({ name: "Acme", email: "ap@acme.example" });
const { invoice } = await iflow.createInvoice({ clientId: client.id, /* ... */ });

const { url } = await iflow.shareInvoice(invoice.id);

Verify it yourself, without a key

The security claims below are assertions you can run right now. No account, no credential, no secrets — every one of them is a refusal, which is exactly what makes them safe to publish.

$ node verify-no-key.mjs
ok    anon GET /api/invoices -> 401
ok    anon GET /api/clients -> 401
ok    anon GET /api/dashboard/stats -> 401
ok    forged key POST /api/clients -> 401
ok    malformed key rejected locally
ok    anon POST /api/keys -> 401
ok    constructor refuses empty apiKey

8 passed, 0 failed

Download: /sdk/verify-no-key.mjs

The verifier above proves everything that must be refused. It cannot prove the thing the product actually claims — that an authenticated caller gets a payable link. That is a separate claim, so it gets a separate script.

$ node verify-paid-path.mjs            # dry run: no key, no writes, no egress
=== DRY RUN — nothing sent, no account touched ===
  1. GET  /api/me                    prove the key is live
  2. POST /api/clients               create a uniquely tagged client
  3. POST /api/invoices              create invoice (items + dueDate + currency)
  4.       assert server-derived total == 25000 cents
  5. POST /api/invoices/:id/share    assert url is https + *.stripe.com
  6. (skipped) email — pass --send to exercise delivery

$ INVOICEFLOW_API_KEY=ifk_... node verify-paid-path.mjs
  [PASS] authenticated: GET /api/me responded
  [PASS] created client
  [PASS] payload carries no client-supplied totals
  [PASS] created invoice
  [PASS] server-derived total matches request
  [PASS] share returned a url field
  [PASS] url is https and on stripe.com

7 passed, 0 failed

Download: /sdk/verify-paid-path.mjs · runs without a key, refuses a key that cannot be valid before transmitting it, and defaults to DRAFT so no email is sent unless you pass --send.

MCP server

An agent should not have to read a REST document to send an invoice. This ships as a Model Context Protocol server with no dependencies — seven tools, added to a host in six lines of config:

{
  "mcpServers": {
    "invoiceflow": {
      "command": "node",
      "args": ["/abs/path/invoiceflow-mcp.mjs"],
      "env": { "INVOICEFLOW_API_KEY": "ifk_..." }
    }
  }
}

/sdk/invoiceflow-mcp.mjs · /sdk/test-mcp.mjs — 14 conformance assertions, no key and no network required.

The tool set is deliberately narrow. Nothing can send email, delete, refund, or pay: creation is additive and a human can undo it, while an agent that emails a customer on every retry cannot take that back. Amounts are integer cents named unitPrice, and a non-integer is refused with an explanation instead of being rounded, because 25.5 silently meaning 25 cents is the defect most likely to reach a paying customer.

How keys work

Endpoints

GET/api/invoicesList invoices (paginated)
POST/api/invoicesCreate an invoice
GET/api/invoices/:idRead one invoice
POST/api/invoices/:id/sendEmail it to the client
GET/api/clientsList clients
POST/api/clientsCreate a client
GET/api/dashboard/statsAccount + counts

If you are an autonomous agent

There is no idempotency key. A retriedcreateInvoice after a timeout can duplicate a real invoice. Read back before retrying.

Sending has side effects. sendInvoice emails a human. Do not retry it blindly.

Cents, not dollars. unitPrice: 25000 is $250.00.

404 means “not yours” too. The API refuses to disclose whether another account's invoice exists.

Full error table, response envelopes, and the measured behaviour list are in the README. To issue invoices, you need an API key from Settings.