Biflus API

Read and write your Biflus data from your own code: clients, items, categories, and invoices. Whether you're connecting Zapier, syncing another tool, or just automating something for yourself, this is everything you need.

Test environment only · Base URL: https://api.biflus.com · Last updated 2026-07-30

Quick start

The whole API follows one pattern, so seeing it once tells you almost everything. Once you have a key (see Authentication), here's listing your clients:

GET https://api.biflus.com/v1/clients
Authorization: Bearer sk_test_9f2c7a1e4b8d3f0a6c5e2b1d8f4a7c3e

Response

{
  "data": [
    { "_id": "1785406744124x246658754867582050", "company_name": "Acme Corp", "type": "Company" /* … */ }
  ],
  "pagination": { "count": 1, "remaining": 0, "next_cursor": null }
}

Every other resource (items, categories, invoices) works the same way: GET to list, GET .../:id to fetch one, POST to create, PATCH to update, DELETE to remove. If you know one, you know them all.

Authentication

Every request is authenticated with an API key, sent as a bearer token:

Authorization: Bearer sk_test_9f2c7a1e4b8d3f0a6c5e2b1d8f4a7c3e

Keys are prefixed sk_test_ or sk_live_; the prefix alone determines which Bubble environment (test or live) a request reads and writes to, so a key only ever works against the data it was minted for.

Getting a key. There's no self-serve dashboard yet: keys are minted directly by the Biflus team on request. See Roadmap.

A key can be revoked at any time; revoked keys are rejected immediately on the next request, with no grace period.

Making requests

Base URL

https://api.biflus.com. Every path in this doc (/v1/clients, etc.) hangs off that.

Rate limits

100 requests per minute, per API key. Exceeding it returns 429; see Errors.

Pagination

List endpoints (GET /v1/clients, /v1/items, etc.) accept ?limit (default 25, max 100) and ?cursor. Every list response includes a pagination object: pass its next_cursor back in as ?cursor to get the next page; a null cursor means you're at the end.

{
  "data": [ /* … */ ],
  "pagination": {
    "count": 28,
    "remaining": 3,
    "next_cursor": "MjU="
  }
}

Errors

Errors are JSON with an error message, and sometimes a detail or valid_options field with more context.

{ "error": "\"cyan\" is not a valid icon", "valid_options": ["boxes", "rocket", ] }
400Missing or invalid fields in the request body.
401Missing, invalid, or revoked API key.
404The record doesn't exist, or belongs to a different account.
409Blocked: another record still depends on this one (e.g. deleting a category still used by an item).
422Blocked by content moderation.
429Rate limit exceeded: 100 requests/minute per key.
502An upstream service (Bubble, or PDF generation) failed.

Clients

The people or companies you invoice.

FieldTypeNotes
company_namestring requiredThe client's own name (doubles as a person's name when type is Individual).
type"Individual" | "Company" required 
emailstring 
phonestring 
contact_phonestringOnly meaningful when type is Company.
colorstringAuto-assigned for Company clients if omitted.
address, city, postal_code, countrystring 
tax_idstring 
GET/v1/clients available List clients, paginated
GET/v1/clients/:id available Fetch one client
POST/v1/clients available Create a client
Example

Request

{
  "company_name": "Acme Corp",
  "type": "Company",
  "email": "billing@acme.com",
  "country": "UK"
}

Response · 201

{
  "data": {
    "_id": "1785406744124x246658754867582050",
    "company_name": "Acme Corp",
    "type": "Company",
    "email": "billing@acme.com",
    "country": "UK",
    "color": "Indigo"
  }
}
PATCH/v1/clients/:id available Update a client
DELETE/v1/clients/:id available Blocked (409) if any invoice references this client

Items

Your catalog: the things you sell, used as invoice line items.

FieldTypeNotes
namestring required 
default_pricenumber required**Unless no_price_yet: true is sent instead.
description, skustring 
unitenumHour, Day, Piece, Session, Project, Kilogram, Liter, … (21 total)
iconenumDefaults to "boxes". 54 valid values (rocket, dog, gauge, …).
categorystring (id)A category id from Categories.
vat_ratenumber (%)e.g. 19. Resolved to (or creates) a matching tax rate.
active, track_stock_quantityboolean 
stock_quantitynumberSetting this on create also sets the item's starting stock snapshot.
GET/v1/items available List items, paginated
GET/v1/items/:id available Fetch one item
POST/v1/items available Create an item
Example

Request

{
  "name": "Consulting Hour",
  "default_price": 150,
  "unit": "Hour",
  "vat_rate": 19
}

Response · 201

{
  "data": {
    "_id": "1785406746780x104116111658747680",
    "name": "Consulting Hour",
    "default_price": 150,
    "unit": "Hour",
    "icon": "boxes",
    "default_vat_rate": "1785406801418x619076360946587600"
  }
}
PATCH/v1/items/:id available Update an item
DELETE/v1/items/:id available Always allowed: past invoices keep their own frozen line-item snapshot

Categories

Groups for organizing items.

FieldTypeNotes
namestring required 
colorstringAuto-assigned if omitted.
key_letterstringThe 2-letter chip shown in the app. Auto-derived from name if omitted.
GET/v1/categories available List categories, paginated
GET/v1/categories/:id available Fetch one category
POST/v1/categories available Create a category
PATCH/v1/categories/:id available Update a category
DELETE/v1/categories/:id available Blocked (409) if any item still uses this category

Invoices

Creating an invoice also generates and attaches a real PDF.

Line items

Each entry in items is either a catalog item (item_id, price/VAT pulled from that item unless overridden) or a custom one-off line (title + unit_price directly, not saved to your catalog).

FieldTypeNotes
client_idstring (id) required 
itemsarray requiredAt least one line. Each: item_id or title, plus optional quantity (default 1), unit_price, vat_rate.
issue_date, due_datedate stringDefault to today, and today + your account's default payment terms.
GET/v1/invoices available List invoices, paginated
GET/v1/invoices/:id available Fetch one invoice
POST/v1/invoices available Create a Draft invoice + PDF
Example

Request

{
  "client_id": "1785406744124x246658754867582050",
  "items": [
    { "item_id": "1785406746780x104116111658747680", "quantity": 2 },
    { "title": "One-off setup fee", "unit_price": 50 }
  ]
}

Response · 201

{
  "data": {
    "_id": "1785406752229x437683015977495360",
    "invoice_number": "IPP/1028",
    "status": "Draft",
    "subtotal": 350, "vat_amount": 0, "total": 350,
    "currency": "EUR",
    "pdf_file_URL": "https://invoices.biflus.com/pdfs/…/IPP1028.pdf"
  },
  "line_items": [ /* full resolved line breakdown */ ]
}
PATCH/v1/invoices/:id available Edit line items (only while still Draft)
Example

Request

{ "items": [ { "item_id": "1785406746780x104116111658747680", "quantity": 3 } ] }

Response · 200

{
  "data": { "total": 300, "pdf_file_URL": "https://invoices.biflus.com/pdfs/…/IPP1029.pdf" /* … */ },
  "line_items": [ /* full resolved line breakdown, quantities recalculated */ ]
}

Pass the full new list of line items, not just the ones changing. Old lines are replaced, not merged. The PDF is regenerated automatically.

DELETE/v1/invoices/:id available Only while still Draft (409 otherwise)
POST/v1/invoices/:id/status available Mark Sent / Paid / Canceled
Example: marking Paid

Request

{
  "status": "Paid",
  "payment_method": "Bank Transfer",
  "payment_reference": "TX-12345"
}

Response · 200

{
  "data": { "status": "Paid", "payment_record": "1785429702412x796431784023203200" /* … */ },
  "receipt_url": "https://invoices.biflus.com/pdfs/…/REC-IPP1030.pdf"
}

Status only moves forward: once an invoice leaves Draft it can't go back, and Paid/Canceled are final. Marking Paid also creates a payment record and generates a receipt PDF automatically. payment_method, payment_reference, and payment_notes are all optional.

Webhooks

Get notified the moment something happens, instead of polling for it.

Events

invoice.created, invoice.sent, invoice.paid, invoice.canceled. Each delivery's body carries the full invoice record under data.

{
  "event": "invoice.paid",
  "created_at": "2026-07-30T17:45:26.525Z",
  "data": { "_id": "…", "invoice_number": "IPP/1031", "status": "Paid" /* … */ }
}

Verifying a delivery

Every request carries an X-Biflus-Signature header: sha256=<hex>, an HMAC-SHA256 of the raw request body using your webhook's secret. Recompute it on your end and compare before trusting the payload.

// Node.js example
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const valid = expected === req.headers['x-biflus-signature'];

Delivery and retries

A 2xx response marks a delivery successful. Anything else (including a timeout, 10s) is retried automatically with backoff — 1 minute, 5 minutes, 30 minutes, then 2 hours — up to 5 attempts total, after which it's marked dead and given up on.

FieldTypeNotes
urlstring requiredMust be http(s).
eventsarray requiredAt least one, from the list above.
activebooleanDefaults to true on create. Set false to pause without deleting.
GET/v1/webhooks available List your webhooks, paginated
GET/v1/webhooks/:id available Fetch one webhook
POST/v1/webhooks available Create a webhook
Example

Request

{
  "url": "https://yourapp.com/webhooks/biflus",
  "events": ["invoice.paid", "invoice.sent"]
}

Response · 201

{
  "data": {
    "_id": "1785433508831x140304457758445360",
    "url": "https://yourapp.com/webhooks/biflus",
    "events": ["invoice.paid", "invoice.sent"],
    "active": true,
    "secret": "a3f8c1…"
  }
}

Save secret — it's how you verify deliveries are really from Biflus.

PATCH/v1/webhooks/:id available Update url, events, or pause with active: false
DELETE/v1/webhooks/:id available Remove a webhook

Roadmap

What's built, and what's next. Kept current as we go.

Self-serve API keys. Not started: right now keys are minted directly by the Biflus team on request. A dashboard for generating your own is planned, likely as a settings page inside the main Biflus app rather than a separate site.

Changelog

2026-07-30

Webhooks shipped: subscribe to invoice events instead of polling.

  • invoice.created, invoice.sent, invoice.paid, invoice.canceled.
  • Every delivery is signed (HMAC-SHA256) so you can verify it's really from Biflus.
  • Failed deliveries retry automatically with backoff, up to 5 attempts.
2026-07-30

The API is now publicly reachable at https://api.biflus.com, and this documentation moved to its own site at docs.biflus.com.

2026-07-30

Invoice lifecycle completed: edit and delete Drafts, and move a Draft through Sent, Paid, or Canceled.

  • Marking an invoice Paid creates a real payment record and generates a receipt PDF automatically.
  • Status only moves forward: once sent, an invoice can't go back to Draft; Paid and Canceled are final.
  • Editing or deleting only works while an invoice is still a Draft.
2026-07-30

Invoice creation shipped, with real PDF generation attached automatically.

2026-07-30

Write endpoints shipped for clients, items, and categories.

  • Create, update, and delete, each enforcing the same rules as the main app (required fields, valid icon/unit choices, content moderation on every write).
  • Deleting a category or client that's still in use is blocked, not silently allowed.
2026-07-30

Initial read-only API shipped: invoices, clients, items, categories.

  • API key authentication, 100 req/min rate limiting, cursor pagination.