Biflus Plugins
Build on Biflus
A plugin lets your service add capability to a Biflus company's invoices — a "Submit to SDI" action, a Shopify item sync, whatever you need. This is an early, invite-only stage: every submission is reviewed by hand before it can run against a real company's data.
Your code describes itself
There's no form asking you to re-type what your plugin does. You build one URL that behaves two ways:
- GET your URL → your server returns a small JSON manifest describing your plugin: what it is, what it's allowed to do (permissions), and what it adds to the app (extends). This is the only thing you submit below.
- POST your URL → the real trigger, sent when a company runs one of your actions. See the payload further down.
A full manifest, for a plugin that adds a button, a status badge, and a few extra fields:
{
"manifest_version": 1,
"name": "SDI Submission",
"version": "1.0.0",
"description": "Submit invoices to Italy's SDI network.",
"pricing": "€2/month",
"permissions": ["invoice:read", "invoice:actions", "invoice:badges", "invoice:fields", "client:fields"],
"extends": {
"invoice.actions": [
{ "id": "send_to_sdi", "label": "Submit to SDI", "icon": "file-text" }
],
"invoice.badges": true,
"invoice.fields": [
{ "key": "sdi_transmission_id", "label": "SDI Transmission ID", "type": "text" }
],
"client.fields": [
{ "key": "codice_destinatario", "label": "Codice Destinatario", "type": "text" }
]
}
}
A plugin with nothing for a user to tap — a background sync — just omits extends and declares no permissions:
{
"manifest_version": 1,
"name": "Shopify Items Sync",
"version": "1.0.0",
"description": "Keeps your Shopify catalogue synced as Items.",
"pricing": "Free",
"permissions": []
}
Changed your plugin later? Just resubmit the same URL below — Biflus re-reads your manifest and refreshes what it knows. A manifest change puts the plugin back to pending review, since what it's allowed to do may have changed.
How the trigger works
When a user taps one of your actions, Biflus sends a POST to your URL with:
{
"company_id": "1770572052950x686750812358836200",
"invoice_id": "1785713158043x913743297649344900",
"action_id": "send_to_sdi",
"token": "<a bearer token, scoped to exactly the permissions you declared>"
}
Your service does whatever it needs to, then writes the result back through Biflus's own public API using that token — a badge, a field, whatever your manifest declared. That write-back API isn't live yet; your developer_email below is how we'll reach you when it is.
Design contract
Everything your manifest can add to the app is deliberately limited, so it always looks native to Biflus — not a random plugin's own visual style bolted on. This is enforced when your manifest is fetched, not just documented:
- permissions must come from a fixed list: invoice:read, invoice:actions, invoice:badges, invoice:fields, client:read, client:fields, storage. Every key you use under extends needs its matching permission declared, or the manifest is rejected.
- extends["invoice.actions"][].icon must be one of a fixed set of Lucide icons — the same icon library every other icon in Biflus is drawn from: puzzle, send, refresh-cw, upload, download, link, link-2, shield-check, file-text, receipt, database, credit-card, globe, check-circle, alert-triangle, zap, package, truck, building-2, banknote.
- A status badge (extends["invoice.badges"]: true) is limited to the same 5 colors already used for Draft / Sent / Paid / Cancelled — gray blue green red orange — no custom hex, set when your plugin actually writes the badge.
- Extra fields (extends["invoice.fields"] / extends["client.fields"]) declare a key, label and type (text or link for now) up front — at write-time you only ever send the value for a key you already declared, always rendered as a plain label/value row.
Submit a plugin