Machine credentials
InvoiceFlow for agents
InvoiceFlow accepts a scoped bearer API key on its JSON API, so an autonomous agent or a script can create and manage invoices with no browser session. Every key belongs to exactly one human account — the agent acts as that account, with that account’s permissions, and nothing more.
1 A human issues the key, once
Sign in, then call POST /api/keys with { "name": "my-agent" }. The response contains the key exactly once:
HTTP 201
{ "key": "ifk_<64 hex>", "prefix": "ifk_ab12cd34",
"warning": "This key is shown once and cannot be recovered." }Store it in the agent’s secret store and send it as Authorization: Bearer ifk_<64 hex>. If it is lost, issue a new one — it cannot be recovered. Revoking is immediate.
Why a key cannot mint a key. POST /api/keys and DELETE /api/keys/{id} are session-only on purpose. A leaked agent key can therefore never replicate its own credentials — it can do invoice work until a human revokes it, and that is all.
2 What a key can do
| Method | Path | Purpose |
|---|---|---|
GET | /api/invoices | list invoices (paginated) |
POST | /api/invoices | create an invoice |
GET | /api/invoices/{id} | read one invoice |
PATCH | /api/invoices/{id} | status transition (DRAFT to SENT) |
PUT | /api/invoices/{id} | edit a draft |
GET | /api/invoices/{id}/pdf | render the PDF (binary, %PDF-) |
GET | /api/clients | list clients |
POST | /api/clients | create a client |
GET | /api/estimates | list estimates |
POST | /api/estimates | create an estimate |
POST | /api/estimates/{id}/convert | estimate to invoice |
GET | /api/dashboard/stats | aggregate totals |
GET | /api/export/invoices | CSV export |
GET | /api/export/clients | CSV export |
Deliberately not available to a key
- POST /api/keys — a key cannot mint another key
- DELETE /api/keys/{id} — revocation is human-only
- POST /api/invoices/{id}/send — sends email, session only
- POST /api/invoices/{id}/share — mints a public link, session only
- /api/stripe/connect — binds a payment account to a human
3 Create an invoice
curl -X POST https://invoiceflow-ten-azure.vercel.app/api/invoices \
-H "Authorization: Bearer $INVOICEFLOW_KEY" \
-H "Content-Type: application/json" \
-d '{
"clientId": "clx...",
"dueDate": "2026-12-02",
"currency": "USD",
"tax": 0,
"notes": "work performed by an autonomous agent",
"items": [
{ "description": "Agent integration work",
"quantity": 2, "unitPrice": 150 }
]
}'HTTP 201
{
"invoice": {
"id": "cmvy...",
"invoiceNumber": "INV-202611-0001",
"subtotal": 300,
"tax": 0,
"total": 300,
"status": "DRAFT"
}
}Payload rules
The schema is strict: unknown keys are rejected with 400, not ignored. issueDate is not accepted — it is derived server-side; only dueDate is sent.
- Totals and the invoice number are always server-derived in integer cents. A client-supplied total is rejected.
-
itemstakes 1–500 entries; quantity × unit price is rounded once per line, then summed in cents. -
statusdefaults toDRAFT; the other accepted value isSENT.
4 Errors an agent must handle
| Code | Meaning | Correct reaction |
|---|---|---|
400 | bad payload or unknown key | fix the body, do not retry blindly |
401 | missing, malformed, or revoked key | stop; ask the human for a new key |
404 | not found in your account | verify the id |
409 | state conflict (editing a non-draft) | re-read, then decide |
429 | rate limited | back off |
5 Verified against production
The following was executed against the live site with a real database record, then the test record was deleted:
no auth -> 401
bogus bearer -> 401
valid key POST /api/invoices -> 201 INV-202611-0001 total 300 DRAFT
GET /api/invoices -> 200 (reads the same invoice)
GET /api/invoices/{id}/pdf -> 200 2345 bytes magic %PDF-
GET /api/dashboard/stats -> 200
POST /api/keys using a key -> 401 (cannot mint credentials)6 Security notes
- Keys are stored only as SHA-256 hashes. The plaintext exists once, at creation, and is never retrievable.
- A short
prefixis kept so a human can identify a key in a list without revealing it. - Lookup is by hash;
lastUsedAtis updated on use for auditing. - Setting
revokedAtstops a key immediately — no restart, no cache to wait out.
Ready to let an agent run your invoicing?
Sign in, create a key, and hand it to your agent. The key can only do invoice work, and you can revoke it at any time.
Sign in to issue a key