B
Biflus Docs
biflus.com Get API key

Items

Your catalog: the things you sell, used as invoice line items. An item's price and VAT are just defaults — any invoice line can override them per line.

Fields you can set

These are the fields you can send on create and update. An item also carries read-only fields the system fills in.

namestringRequired
default_pricenumberRequired*

*Unless no_price_yet: true is sent instead.

description, skustring
unitenum

Hour, Day, Piece, Session, Project, Kilogram, Liter, … (21 total)

iconenum

Defaults 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 for your company.

active, track_stock_quantityboolean
stock_quantitynumber

Setting this on create also sets the item's starting stock snapshot; on update it only updates the current count.

Read-only fields

Returned on every item but managed by the system or derived from the fields above — you can't set them directly.

_idstring (id)

The item's public id, e.g. item_2e88ac.

companystring (id)

Your company — scoping is automatic.

default_vat_ratestring (id)

The tax-rate record your vat_rate resolved to. Send vat_rate to change it.

display_vat_percentage, display_unitstring

Preformatted strings the app renders, derived from the fields above.

Initial_stock_qtynumber

The starting stock snapshot, captured once from the first stock_quantity you send.

flaggedboolean

Set by the system's content check; you can't write it.

Created Date, Modified Datedate

ISO timestamps maintained automatically.

Best practices
  • Send a plain percentage to vat_rate (e.g. 19) — don't try to look up or pass a tax-rate id yourself, it's resolved for you.
  • Use no_price_yet: true for items you're still pricing rather than sending a placeholder 0, which reads as "this item is free" everywhere else it's used.
  • If a 400 comes back on icon or unit, read valid_options from the response — it's the live list, safer than a hardcoded copy.
Common mistakes
  • Don't rely on delete to retire an item. Unlike clients and categories, DELETE on an item is never blocked by usage — if you want it to disappear from new invoices but stay valid for reference, set active: false instead.
  • Don't expect updating stock_quantity to reset the starting snapshot. Initial_stock_qty is only ever set on create; a later stock_quantity update changes the current count, not the baseline.
  • Don't pass a category id you haven't verified belongs to your company. It's checked as a real id shape, but not ownership at write time in the same pass — fetch it via Categories first if you're not sure.

List items

GET/v1/items available Paginated

Same shape as every other list endpoint. See Pagination.

limitquery param, number

Default 25, max 100.

cursorquery param, string

From the previous page's pagination.next_cursor.

curl https://api.biflus.com/v1/items?limit=2 \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY"
import requests

resp = requests.get(
    "https://api.biflus.com/v1/items",
    headers={"Authorization": "Bearer sk_test_YOUR_API_KEY"},
    params={"limit": 2},
)
print(resp.json())
const res = await fetch("https://api.biflus.com/v1/items?limit=2", {
  headers: { "Authorization": "Bearer sk_test_YOUR_API_KEY" }
});
const data = await res.json();
using System.Net.Http;
using System.Net.Http.Headers;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.biflus.com/v1/items?limit=2");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "sk_test_YOUR_API_KEY");
var response = await client.SendAsync(request);
var data = await response.Content.ReadAsStringAsync();
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.biflus.com/v1/items?limit=2", nil)
	req.Header.Set("Authorization", "Bearer sk_test_YOUR_API_KEY")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(string(data))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.biflus.com/v1/items?limit=2"))
    .header("Authorization", "Bearer sk_test_YOUR_API_KEY")
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'

uri = URI("https://api.biflus.com/v1/items?limit=2")
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer sk_test_YOUR_API_KEY'

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Response
{
  "data": [
    {
      "_id": "item_2e88ac",
      "name": "Consulting Hour",
      "default_price": 150 /* … */
    },
    {
      "_id": "item_71fc0b",
      "name": "Onboarding Kit",
      "default_price": 40 /* … */
    }
  ],
  "pagination": {
    "count": 2,
    "remaining": 11,
    "next_cursor": "Mg=="
  }
}

Get an item

GET/v1/items/:id available Fetch one item
idpath param, stringRequired

The item's _id.

curl https://api.biflus.com/v1/items/item_2e88ac \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY"
import requests

resp = requests.get(
    "https://api.biflus.com/v1/items/item_2e88ac",
    headers={"Authorization": "Bearer sk_test_YOUR_API_KEY"},
)
print(resp.json())
const res = await fetch("https://api.biflus.com/v1/items/item_2e88ac", {
  headers: { "Authorization": "Bearer sk_test_YOUR_API_KEY" }
});
const data = await res.json();
using System.Net.Http;
using System.Net.Http.Headers;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.biflus.com/v1/items/item_2e88ac");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "sk_test_YOUR_API_KEY");
var response = await client.SendAsync(request);
var data = await response.Content.ReadAsStringAsync();
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("GET", "https://api.biflus.com/v1/items/item_2e88ac", nil)
	req.Header.Set("Authorization", "Bearer sk_test_YOUR_API_KEY")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(string(data))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.biflus.com/v1/items/item_2e88ac"))
    .header("Authorization", "Bearer sk_test_YOUR_API_KEY")
    .method("GET", HttpRequest.BodyPublishers.noBody())
    .build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'

uri = URI("https://api.biflus.com/v1/items/item_2e88ac")
req = Net::HTTP::Get.new(uri)
req['Authorization'] = 'Bearer sk_test_YOUR_API_KEY'

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Response
{
  "data": {
    "_id": "item_2e88ac",
    "name": "Consulting Hour",
    "default_price": 150,
    "unit": "Hour",
    "icon": "boxes",
    "active": true,
    "default_vat_rate": "vat_5c14e8"
  }
}
{
  "error": "item not found"
}

Create an item

POST/v1/items available Create an item

icon and unit are validated server-side against fixed enums — an invalid value gets a 400 back with the full valid_options list, not a silent fallback.

curl -X POST https://api.biflus.com/v1/items \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Consulting Hour", "default_price": 150, "unit": "Hour", "vat_rate": 19}'
import requests

resp = requests.post(
    "https://api.biflus.com/v1/items",
    headers={"Authorization": "Bearer sk_test_YOUR_API_KEY"},
    json={"name": "Consulting Hour", "default_price": 150, "unit": "Hour", "vat_rate": 19},
)
print(resp.json())
const res = await fetch("https://api.biflus.com/v1/items", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk_test_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ name: "Consulting Hour", default_price: 150, unit: "Hour", vat_rate: 19 })
});
const data = await res.json();
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Post, "https://api.biflus.com/v1/items");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "sk_test_YOUR_API_KEY");
request.Content = new StringContent("{"name": "Consulting Hour", "default_price": 150, "unit": "Hour", "vat_rate": 19}", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var data = await response.Content.ReadAsStringAsync();
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"name": "Consulting Hour", "default_price": 150, "unit": "Hour", "vat_rate": 19}`)
	req, _ := http.NewRequest("POST", "https://api.biflus.com/v1/items", body)
	req.Header.Set("Authorization", "Bearer sk_test_YOUR_API_KEY")
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(string(data))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.biflus.com/v1/items"))
    .header("Authorization", "Bearer sk_test_YOUR_API_KEY")
    .header("Content-Type", "application/json")
    .method("POST", HttpRequest.BodyPublishers.ofString("{"name": "Consulting Hour", "default_price": 150, "unit": "Hour", "vat_rate": 19}"))
    .build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'

uri = URI("https://api.biflus.com/v1/items")
req = Net::HTTP::Post.new(uri)
req['Authorization'] = 'Bearer sk_test_YOUR_API_KEY'
req['Content-Type'] = 'application/json'
req.body = '{"name": "Consulting Hour", "default_price": 150, "unit": "Hour", "vat_rate": 19}'

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Response
{
  "data": {
    "_id": "item_2e88ac",
    "name": "Consulting Hour",
    "default_price": 150,
    "unit": "Hour",
    "icon": "boxes",
    "default_vat_rate": "vat_5c14e8"
  }
}
{
  "error": "\"cyan\" is not a valid icon",
  "valid_options": [
    "disc-3",
    "boxes",
    "rocket",
    … 51 more
  ]
}

Update an item

PATCH/v1/items/:id available Partial update

vat_rate is a percentage in, a resolved default_vat_rate id out; it finds a matching tax rate for your company or creates one.

curl -X PATCH https://api.biflus.com/v1/items/item_2e88ac \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"default_price": 175, "vat_rate": 21}'
import requests

resp = requests.patch(
    "https://api.biflus.com/v1/items/item_2e88ac",
    headers={"Authorization": "Bearer sk_test_YOUR_API_KEY"},
    json={"default_price": 175, "vat_rate": 21},
)
print(resp.json())
const res = await fetch("https://api.biflus.com/v1/items/item_2e88ac", {
  method: "PATCH",
  headers: {
    "Authorization": "Bearer sk_test_YOUR_API_KEY",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ default_price: 175, vat_rate: 21 })
});
const data = await res.json();
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Patch, "https://api.biflus.com/v1/items/item_2e88ac");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "sk_test_YOUR_API_KEY");
request.Content = new StringContent("{"default_price": 175, "vat_rate": 21}", Encoding.UTF8, "application/json");
var response = await client.SendAsync(request);
var data = await response.Content.ReadAsStringAsync();
package main

import (
	"fmt"
	"io"
	"net/http"
	"strings"
)

func main() {
	body := strings.NewReader(`{"default_price": 175, "vat_rate": 21}`)
	req, _ := http.NewRequest("PATCH", "https://api.biflus.com/v1/items/item_2e88ac", body)
	req.Header.Set("Authorization", "Bearer sk_test_YOUR_API_KEY")
	req.Header.Set("Content-Type", "application/json")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(string(data))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.biflus.com/v1/items/item_2e88ac"))
    .header("Authorization", "Bearer sk_test_YOUR_API_KEY")
    .header("Content-Type", "application/json")
    .method("PATCH", HttpRequest.BodyPublishers.ofString("{"default_price": 175, "vat_rate": 21}"))
    .build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'

uri = URI("https://api.biflus.com/v1/items/item_2e88ac")
req = Net::HTTP::Patch.new(uri)
req['Authorization'] = 'Bearer sk_test_YOUR_API_KEY'
req['Content-Type'] = 'application/json'
req.body = '{"default_price": 175, "vat_rate": 21}'

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Response
{
  "data": {
    "_id": "item_2e88ac",
    "name": "Consulting Hour",
    "default_price": 175,
    "default_vat_rate": "vat_d802f1" /* … unchanged fields */
  }
}

Delete an item

DELETE/v1/items/:id available Always allowed — past invoices keep their own frozen line-item snapshot

Unlike clients and categories, deleting an item is never blocked by usage — invoices already store a frozen copy of the line, not a live reference. If you want it gone from new invoices but still valid historically, prefer active: false.

curl -X DELETE https://api.biflus.com/v1/items/item_2e88ac \
  -H "Authorization: Bearer sk_test_YOUR_API_KEY"
import requests

resp = requests.delete(
    "https://api.biflus.com/v1/items/item_2e88ac",
    headers={"Authorization": "Bearer sk_test_YOUR_API_KEY"},
)
print(resp.json())
const res = await fetch("https://api.biflus.com/v1/items/item_2e88ac", {
  method: "DELETE",
  headers: { "Authorization": "Bearer sk_test_YOUR_API_KEY" }
});
const data = await res.json();
using System.Net.Http;
using System.Net.Http.Headers;

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Delete, "https://api.biflus.com/v1/items/item_2e88ac");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "sk_test_YOUR_API_KEY");
var response = await client.SendAsync(request);
var data = await response.Content.ReadAsStringAsync();
package main

import (
	"fmt"
	"io"
	"net/http"
)

func main() {
	req, _ := http.NewRequest("DELETE", "https://api.biflus.com/v1/items/item_2e88ac", nil)
	req.Header.Set("Authorization", "Bearer sk_test_YOUR_API_KEY")
	resp, _ := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	data, _ := io.ReadAll(resp.Body)
	fmt.Println(string(data))
}
import java.net.URI;
import java.net.http.*;

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.biflus.com/v1/items/item_2e88ac"))
    .header("Authorization", "Bearer sk_test_YOUR_API_KEY")
    .method("DELETE", HttpRequest.BodyPublishers.noBody())
    .build();
HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
require 'net/http'
require 'json'

uri = URI("https://api.biflus.com/v1/items/item_2e88ac")
req = Net::HTTP::Delete.new(uri)
req['Authorization'] = 'Bearer sk_test_YOUR_API_KEY'

res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)
Response
{
  "deleted": true,
  "id": "item_2e88ac"
}
This page is updated as the API changes. If something here doesn't match what you're seeing, that's a bug worth flagging to the Biflus team.