Documentation for 4.0 (pre-release). Using 3.x? See the README on main.
Skip to content

Articles (Artikel) ​

API wrapper for articles under /articles and their two sub-resources (/article-tags, /article-property-values).

Access ​

php
$billomat->articles                  // the articles themselves
$billomat->articles->tags            // tags on an article
$billomat->articles->propertyValues  // custom property values on an article

The property definitions (schemas) live separately under $billomat->articles->properties, see Property definitions. This page only covers the values a specific article holds for those definitions.

Model ​

An article is a reusable line item that invoices, offers and recurring invoices can reference as a position.

  • Identification: the sequential number combined with numberPre and numberLength gives the displayed article_number.
  • Prices: up to five sales price levels (salesPrice to salesPrice5) for tiered prices or customer groups, optionally a purchasePrice with its own currency.
  • References: taxId points to a tax rate, unitId to a unit, and supplierId to a supplier. categoryId refers to the account's article categories.
  • Type: type (ItemType) marks the article as PRODUCT or SERVICE.
  • Custom fields: dynamic values via $billomat->articles->propertyValues (e.g. "lead time", "warranty"); the property definitions themselves are managed via $billomat->articles->properties.

Endpoint overview ​

/articles ​

MethodHTTPPath
list($filters?)GET/articles
listPage($filters?)GET/articles (one page with metadata)
iterateAll($filters?, $pageSize?)GET/articles (all pages)
get($id) / getOrFail($id)GET/articles/{id}
create($options)POST/articles
update($id, $options)PUT/articles/{id}
delete($id)DELETE/articles/{id}

/article-tags ​

MethodHTTPPath
list($articleId, $filters?)GET/article-tags?article_id={id}
listPage($articleId, $filters?) / iterateAll($articleId, $filters?, $pageSize?)GET/article-tags?article_id={id} (paginated)
cloud()GET/article-tags
get($id) / getOrFail($id)GET/article-tags/{id}
create($articleId, $options)POST/article-tags
delete($id)DELETE/article-tags/{id}

/article-property-values ​

MethodHTTPPath
list($articleId?, $filters?)GET/article-property-values
listPage($articleId?, $filters?)GET/article-property-values
iterateAll($articleId?, $filters?, $pageSize?)GET/article-property-values (all pages)
get($id) / getOrFail($id)GET/article-property-values/{id}
create($articleId, $options)POST/article-property-values

Articles ​

list(ArticleFilter|array $filters = []): list<Article> ​

Filters according to the Billomat docs: article_number, title, description, currency_code, unit_id, tags, supplier_id, category_id. Use the typed filter ArticleFilter with the camelCase fields articleNumber, title, description, currencyCode, unitId, tags (list, sent comma-separated) and supplierId, plus orderBy, page and perPage. Parameters without their own field (e.g. category_id) go into extra: [...]; a plain array still works too.

php
use Justpilot\Billomat\Api\ArticleFilter;
use Justpilot\Billomat\Api\OrderBy;

$active = $billomat->articles->list(new ArticleFilter(
    currencyCode: 'EUR',
    orderBy: OrderBy::asc('title'),
));

listPage() returns the same list plus pagination metadata, iterateAll() walks through all pages lazily, see Pagination.

get(int $id): ?Article ​

Returns null on 404. getOrFail(int $id): Article throws a NotFoundException instead.

create(ArticleCreateOptions $options): Article ​

php
use Justpilot\Billomat\Api\ArticleCreateOptions;
use Justpilot\Billomat\Model\Enum\ItemType;
use Justpilot\Billomat\Model\Money;

$opts = new ArticleCreateOptions(
    title: 'Web hosting Basic',
    description: 'Shared hosting, 10 GB SSD',
    salesPrice: Money::of('19.90'),
    currencyCode: 'EUR',
    unit: 'month',
    taxId: 12345,
    type: ItemType::SERVICE,
);

$article = $billomat->articles->create($opts);

update(int $id, ArticleUpdateOptions $options): Article ​

Partial update: fields left null stay unchanged.

delete(int $id): void ​

Deletes the article.

Write model: ArticleCreateOptions ​

Constructor: new ArticleCreateOptions(string $title, …). title is required and the first parameter. All other fields are constructor parameters with a default and are passed as named arguments. The object is immutable (final readonly); each field has a with*() method that returns a modified copy (e.g. $opts->withDescription('New')).

Identification ​

PropertyBillomat fieldTypeNotes
titletitlestring (required)—
numberPrenumber_pre?stringArticle number prefix (e.g. ART-)
numbernumber?intSequential number; otherwise Billomat increments it itself
numberLengthnumber_length?intMinimum number of digits, padded with zeros
descriptiondescription?string—
typetype?ItemTypePRODUCT or SERVICE

Prices ​

Amounts are Money values, not floats.

PropertyBillomat fieldTypeNotes
salesPricesales_price?MoneyDefault sales price
salesPrice2sales_price2?MoneyLevel 2 (e.g. resellers)
salesPrice3sales_price3?MoneyLevel 3
salesPrice4sales_price4?MoneyLevel 4
salesPrice5sales_price5?MoneyLevel 5
currencyCodecurrency_code?stringISO 4217, e.g. EUR
purchasePricepurchase_price?MoneyPurchase price
purchasePriceCurrencyCodepurchase_price_currency_code?stringSeparate currency for the purchase price
purchasePriceNetGrosspurchase_price_net_gross?NetGrossWhether purchasePrice is net or gross

References ​

PropertyBillomat fieldTypeNotes
unitunit?stringFree-text unit (e.g. piece), alternative to unitId
unitIdunit_id?intReference to /units
supplierIdsupplier_id?intReference to a supplier
taxIdtax_id?intReference to taxes
categoryIdcategory_id?intReference to /article-categories

Write model: ArticleUpdateOptions ​

Mirrors ArticleCreateOptions, but title is no longer required and every field is nullable. null means "don't send", the value stays unchanged. Clear::Field (Justpilot\Billomat\Api\Clear) sends the field as JSON null so Billomat empties it, e.g. new ArticleUpdateOptions(description: Clear::Field).

Read model: Article ​

final readonly class Article.

PropertyType
idint
created?\DateTimeImmutable
articleNumber?string (combined: numberPre + padded number)
number?int
numberPre?string
numberLength?int
title?string
description?string
salesPrice?Money
salesPrice2 … salesPrice5?Money
currencyCode?string
unitId?int
purchasePrice?Money
supplierId?int
taxId?int
updated?\DateTimeImmutable
archived?bool
type?ItemType
revenueAccountNumber?string
costCenter?string
purchasePriceNetGross?NetGross (whether purchasePrice is net or gross)
customfield?string (free-form extra field, see Custom meta attributes)
extraarray<string, mixed> (unmapped fields, see Models)

Article tags ​

Works exactly like Invoice Tags, but for an article instead of an invoice.

Methods ​

php
use Justpilot\Billomat\Api\ArticleTagCreateOptions;

$tag = $billomat->articles->tags->create(
    $article->id,
    new ArticleTagCreateOptions(name: 'hosting'),
);

$tags  = $billomat->articles->tags->list($article->id);
$cloud = $billomat->articles->tags->cloud(); // aggregated, with count
$billomat->articles->tags->delete($tag->id);

There is no update(): delete the tag and create a new one.

Write model: ArticleTagCreateOptions ​

The article ID isn't part of the options: pass it as the first argument of create(), which adds article_id to the payload.

Both values are required and set via the constructor:

PropertyBillomat fieldTypeNotes
namenamestring (required)—

Read model: ArticleTag ​

PropertyType
idint
articleIdint
namestring
customfield?string (free-form extra field, see Custom meta attributes)
extraarray<string, mixed>

Read model: ArticleTagCloudEntry ​

PropertyType
id?int
namestring
countint
extraarray<string, mixed>

Article property values ​

Stores the values for the article properties defined in properties.md ($billomat->articles->properties). A property definition (ArticleProperty) is the schema (name + PropertyType); an ArticlePropertyValue is the actual value on one article.

php
use Justpilot\Billomat\Api\ArticlePropertyValueCreateOptions;
use Justpilot\Billomat\Api\ArticlePropertyValueFilter;
use Justpilot\Billomat\Api\OrderBy;

// List the values of one article
$values = $billomat->articles->propertyValues->list($article->id);

// Only the values of property 12, typed filter with sorting
$values = $billomat->articles->propertyValues->list($article->id, new ArticlePropertyValueFilter(
    articlePropertyId: 12,
    orderBy: OrderBy::desc('updated'),
));

// Set the value of the property "Lead time" (id = 42)
$value = $billomat->articles->propertyValues->create(
    $article->id,
    new ArticlePropertyValueCreateOptions(
        articlePropertyId: 42,
        value: '3-5 business days',
    ),
);

There is neither update() nor delete(): calling create() again with the same articlePropertyId overwrites the existing value.

Write model: ArticlePropertyValueCreateOptions ​

The article ID isn't part of the options: pass it as the first argument of create(), which adds article_id to the payload.

Both values are required and set via the constructor.

PropertyBillomat fieldTypeNotes
articlePropertyIdarticle_property_idint (required)ID of the property definition
valuevaluemixed (required)CHECKBOX expects 0/1

Read model: ArticlePropertyValue ​

PropertyType
idint
articleIdint
articlePropertyIdint
type?PropertyType (TEXTFIELD, TEXTAREA, CHECKBOX)
name?string (name from the property definition)
valuemixed
created, updated?\DateTimeImmutable
customfield?string (free-form extra field, see Custom meta attributes)
extraarray<string, mixed>

Enums used ​

  • ItemType: PRODUCT, SERVICE (type; replaces the former ArticleType).
  • NetGross: NET, GROSS, SETTINGS (purchasePriceNetGross).
  • PropertyType: TEXTFIELD, TEXTAREA, CHECKBOX, on the property definition (ArticleProperty) and on ArticlePropertyValue::$type.

Pitfalls ​

  • No update() for tags and property values. Delete tags and create them again; for property values, another POST with the same article_property_id overwrites the existing entry.
  • unit vs. unitId. Both fields exist side by side: unit is a free string, unitId a reference to /units. Setting both at once can make documents display differently than expected; set only one.
  • number is assigned automatically otherwise. Without an explicit number, Billomat increments from the last assigned article number. A manual number must be unique, otherwise you get a 422.
  • articleNumber is read-only. The field only appears in the read model and is built server-side from numberPre + number (padded to numberLength).
  • purchasePriceCurrencyCode can differ from currencyCode. For an article bought in USD and sold in EUR, set both fields.
  • PropertyType::CHECKBOX expects 0/1 as value. Boolean strings ("true"/"false") aren't recognized.

End-to-end example ​

php
<?php

declare(strict_types=1);

require __DIR__ . '/vendor/autoload.php';

use Justpilot\Billomat\Api\ArticleCreateOptions;
use Justpilot\Billomat\Api\ArticlePropertyValueCreateOptions;
use Justpilot\Billomat\Api\ArticleTagCreateOptions;
use Justpilot\Billomat\Api\ArticleUpdateOptions;
use Justpilot\Billomat\BillomatClient;
use Justpilot\Billomat\Model\Enum\ItemType;
use Justpilot\Billomat\Model\Money;

$billomat = BillomatClient::create(
    billomatId: 'mycompany',
    apiKey: getenv('BILLOMAT_API_KEY'),
);

// 1) Create an article
$opts = new ArticleCreateOptions(
    title: 'Web hosting Basic',
    description: 'Shared hosting, 10 GB SSD, daily backup',
    salesPrice: Money::of('19.90'),
    salesPrice2: Money::of('17.90'), // reseller level
    currencyCode: 'EUR',
    unit: 'month',
    taxId: 12345,
    type: ItemType::SERVICE,
);

$article = $billomat->articles->create($opts);
printf("Article #%d (%s) created\n", $article->id, $article->articleNumber);

// 2) Add tags for later filtering
$billomat->articles->tags->create(
    $article->id,
    new ArticleTagCreateOptions(name: 'hosting'),
);
$billomat->articles->tags->create(
    $article->id,
    new ArticleTagCreateOptions(name: 'subscription'),
);

// 3) Set a custom property ("Lead time" = ArticleProperty #42)
$billomat->articles->propertyValues->create(
    $article->id,
    new ArticlePropertyValueCreateOptions(
        articlePropertyId: 42,
        value: 'Available immediately',
    ),
);

// 4) Change the price later: partial update
$update = new ArticleUpdateOptions(salesPrice: Money::of('24.90'));
$billomat->articles->update($article->id, $update);

// 5) Cloud overview of all article tags
foreach ($billomat->articles->tags->cloud() as $entry) {
    printf("%s (%d)\n", $entry->name, $entry->count);
}

Unofficial SDK, not affiliated with Billomat GmbH. Released under the Apache License 2.0.