Reminders (Mahnungen)
API wrapper for payment reminders under /reminders and their two sub-resources (/reminder-items, /reminder-tags).
Access
$billomat->reminders // reminders
$billomat->reminders->items // line items of a reminder (read-only)
$billomat->reminders->tags // tagsThe configurable reminder text blocks live in a separate resource: $billomat->reminderTexts (see ReminderTextsApi).
Model
A reminder always references an underlying invoice via invoiceId (required on create). Content and line items are generated by Billomat from the original invoice plus the selected reminder text block (reminderTextId), so the reminder-items are read-only.
The lifecycle is status-driven:
DRAFT ── complete() ──▶ OPEN ── invoice is paid ──▶ PAID
│ │
│ ├── due date passed ──▶ OVERDUE
│ │
│ └── cancel() ──▶ CANCELED ── uncancel() ──▶ OPEN
└── delete() (any status; prefer cancel() once completed)An open reminder can be followed by further dunning levels: Billomat increments the level internally and uses the templates stored as dunning levels in reminderTexts (see Reminder levels).
Endpoint overview
/reminders
| Method | HTTP | Path |
|---|---|---|
list($filters?) | GET | /reminders |
listPage($filters?) | GET | /reminders (one page plus pagination metadata) |
iterateAll($filters?, $pageSize?) | GET | /reminders (all pages, lazily) |
get($id) / getOrFail($id) | GET | /reminders/{id} |
create($options) | POST | /reminders |
update($id, $options) | PUT | /reminders/{id} |
delete($id) | DELETE | /reminders/{id} |
complete($id, $templateId?) | PUT | /reminders/{id}/complete |
cancel($id) | PUT | /reminders/{id}/cancel |
uncancel($id) | PUT | /reminders/{id}/uncancel |
email($id, $options?) | POST | /reminders/{id}/email |
pdf($id, $type?) | GET | /reminders/{id}/pdf |
pdfContent($id, $type?) | GET | /reminders/{id}/pdf?format=pdf |
uploadSignature($id, $base64Pdf) | PUT | /reminders/{id}/upload-signature |
/reminder-items
| Method | HTTP | Path |
|---|---|---|
list($reminderId, $filters?) | GET | /reminder-items?reminder_id={id} |
listPage($reminderId, $filters?) / iterateAll($reminderId, $filters?, $pageSize?) | GET | /reminder-items?reminder_id={id} (paginated) |
get($id) / getOrFail($id) | GET | /reminder-items/{id} |
Read-only: there is no create/update/delete and no ReminderItemCreateOptions class. Billomat generates the items from the invoice.
/reminder-tags
| Method | HTTP | Path |
|---|---|---|
list($reminderId, $filters?) | GET | /reminder-tags?reminder_id={id} |
listPage($reminderId, $filters?) / iterateAll($reminderId, $filters?, $pageSize?) | GET | /reminder-tags?reminder_id={id} (paginated) |
cloud() | GET | /reminder-tags |
get($id) / getOrFail($id) | GET | /reminder-tags/{id} |
create($reminderId, $options) | POST | /reminder-tags |
delete($id) | DELETE | /reminder-tags/{id} |
Methods
Reminders
list(ReminderFilter|array $filters = []): list<Reminder>
Billomat filters by client_id, contact_id, invoice_id, invoice_number, status, from, to, tags and more.
The typed filter ReminderFilter is recommended. Its camelCase fields are clientId, contactId, invoiceNumber, status (list<ReminderStatus>), from/to (DateTimeImmutable), label, subject, intro, note, tags and articleId. Multi-value fields are lists and are sent comma-separated. On top of that there are orderBy, page, perPage and extra for further query parameters (e.g. invoice_id). A plain array still works: it is passed through to the query string as is, and array values are sent as key[]=….
use Justpilot\Billomat\Api\OrderBy;
use Justpilot\Billomat\Api\ReminderFilter;
use Justpilot\Billomat\Model\Enum\ReminderStatus;
$overdue = $billomat->reminders->list(new ReminderFilter(
status: [ReminderStatus::OVERDUE],
orderBy: OrderBy::asc('due_date'),
));listPage() takes the same filters and returns a Page with pagination metadata; iterateAll() walks through all pages lazily. See Pagination and filtering.
get(int $id): ?Reminder
Returns null on 404. Embedded reminder-items are hydrated automatically. getOrFail() throws a NotFoundException instead of returning null.
create(ReminderCreateOptions $options): Reminder
Only invoiceId is required. Billomat takes the address, reminder text, due date etc. from the invoice or the client configuration unless you override them.
use Justpilot\Billomat\Api\ReminderCreateOptions;
$opts = new ReminderCreateOptions(
invoiceId: 9876,
reminderTextId: 42,
dueDays: 7,
subject: 'Payment reminder for invoice 2026-0042',
);
$reminder = $billomat->reminders->create($opts);update(int $id, ReminderUpdateOptions $options): Reminder
Only advisable in status DRAFT. invoiceId can no longer be changed here; the link to the invoice is fixed.
complete(int $id, ?int $templateId = null): void
Status DRAFT → OPEN. An optional templateId forces a specific layout template.
cancel(int $id): void / uncancel(int $id): void
cancel() sets the status to CANCELED, uncancel() reactivates a canceled reminder (CANCELED → OPEN). Reminders have no clear()/unclear() (Billomat answers 404).
email(int $id, ?EmailOptions $options = null): void
Without options, Billomat uses all defaults from the configured email template.
use Justpilot\Billomat\Api\EmailOptions;
$mail = new EmailOptions(to: ['client@example.com'], subject: 'Reminder: open invoice 2026-0042');
$billomat->reminders->email($reminder->id, $mail);pdf(int $id, ?PdfType $type = null): ReminderPdf / pdfContent(int $id, ?PdfType $type = null): string
pdf() returns a ReminderPdf model (metadata plus base64 body). pdfContent() appends ?format=pdf and returns the binary PDF content directly; it throws an UnexpectedResponseException if the body is empty or doesn't start with %PDF-.
$pdf = $billomat->reminders->pdf($reminder->id); // model
$raw = $billomat->reminders->pdfContent($reminder->id); // string (PDF binary)uploadSignature(int $id, string $base64Pdf): void
Attaches a PDF signed by the client to the reminder.
delete(int $id): void
Only works on reminders in status DRAFT.
Reminder items (read-only)
$items = $billomat->reminders->items->list($reminder->id);
$item = $billomat->reminders->items->get($itemId);There is no ReminderItemCreateOptions and no write endpoint. Items show the original invoice amount and late fees, generated from the configured reminder text block.
Reminder tags
use Justpilot\Billomat\Api\ReminderTagCreateOptions;
$billomat->reminders->tags->create(
$reminder->id,
new ReminderTagCreateOptions(name: 'dunning-level-2'),
);
$tags = $billomat->reminders->tags->list($reminder->id);
$cloud = $billomat->reminders->tags->cloud(); // aggregated, with countThere is no update. To rename a tag, delete it and create it again.
Write model: ReminderCreateOptions
Constructor: new ReminderCreateOptions(int $invoiceId, …). invoiceId is required and the first parameter. Every field is a constructor parameter with a default and is passed as a named argument. The object is immutable (final readonly); each field has a with*() method that returns a modified copy (e.g. $opts->withSubject('New')).
| Property | Billomat field | Type | Notes |
|---|---|---|---|
invoiceId | invoice_id | int (required) | source invoice; can't be changed after creation |
contactId | contact_id | ?int | overrides the client contact |
address | address | ?string | multi-line address |
numberPre | number_pre | ?string | |
number | number | ?int | manual numbering |
numberLength | number_length | ?int | |
date | date | ?\DateTimeImmutable | formatted as Y-m-d |
dueDays | due_days | ?int | due in days from date |
dueDate | due_date | ?\DateTimeImmutable | absolute due date (overrides dueDays) |
subject | subject | ?string | letterhead title |
label | label | ?string | |
intro | intro | ?string | |
note | note | ?string | |
reminderTextId | reminder_text_id | ?int | ID from /reminder-texts (reminder text block) |
templateId | template_id | ?int | layout template ID |
Write model: ReminderUpdateOptions
Mirrors ReminderCreateOptions without invoiceId; the link to the invoice is fixed. Fields left at null are stripped from the payload. Clear::Field instead sends the field as JSON null, and Billomat empties it, e.g. new ReminderUpdateOptions(note: Clear::Field).
Write model: ReminderTagCreateOptions
The reminder ID isn't part of the options: pass it as the first argument of create(), which adds reminder_id to the payload.
| Property | Billomat field | Type | Notes |
|---|---|---|---|
name | name | string (required) |
Write model: EmailOptions
Shared class for sending every document type by email (invoices, offers, confirmations, credit notes, delivery notes, reminders, letters); the full description is on the Invoices page.
| Property | Billomat field | Type | Notes |
|---|---|---|---|
emailTemplateId | email_template_id | ?int | |
from | from | ?string | |
to | recipients.to | list<string> | |
cc | recipients.cc | list<string> | |
bcc | recipients.bcc | list<string> | |
subject | subject | ?string | |
body | body | ?string | |
filename | filename | ?string | name of the PDF attachment |
attachments | attachments.attachment | list<array{filename,mimetype,base64file}> | additional attachments |
Empty recipient arrays are left out, so Billomat falls back to the template.
Note on reminder items
There is no ReminderItemCreateOptions and no ReminderItemUpdateOptions. Reminder items are generated on the server from the original invoice and the reminder text block; they are read-only.
Read model: Reminder
final readonly class Reminder.
| Property | Type |
|---|---|
id | int |
clientId | int |
contactId | ?int |
invoiceId | ?int |
created | ?\DateTimeImmutable |
status | ?ReminderStatus |
date | ?\DateTimeImmutable |
dueDays | ?int |
dueDate | ?\DateTimeImmutable |
address, subject, label, intro, note | ?string |
totalGross | ?Money |
currencyCode | ?string |
reminderTextId, templateId | ?int |
items | list<ReminderItem> |
updated | ?\DateTimeImmutable |
paidAmount | ?Money (amount already paid) |
openAmount | ?Money (outstanding amount) |
reminderLevel | ?int (dunning level, see ReminderTextsApi) |
reminderLevelName | ?string (name of the dunning level) |
isOld | ?bool |
customfield | ?string (free-form extra field, see Custom meta attributes) |
extra | array<string, mixed> (raw fields the SDK doesn't map yet, see Models) |
Read model: ReminderItem
final readonly. Leaner than InvoiceItem, with no tax detail fields, since reminder items are display values taken from the original invoice.
| Property | Type |
|---|---|
id | int |
reminderId | ?int |
articleId | ?int |
position | ?int |
unit | ?string |
quantity, unitPrice | float, Money |
title, description | ?string |
total | ?Money |
isCharge | ?bool (the item is a dunning fee) |
customfield | ?string (free-form extra field, see Custom meta attributes) |
extra | array<string, mixed> |
Read model: ReminderTag
| Property | Type |
|---|---|
id | int |
reminderId | int |
name | string |
customfield | ?string (free-form extra field, see Custom meta attributes) |
extra | array<string, mixed> |
Read model: ReminderTagCloudEntry
| Property | Type |
|---|---|
id | ?int |
name | string |
count | int |
extra | array<string, mixed> |
Read model: ReminderPdf
final class ReminderPdf.
| Property | Type |
|---|---|
id, reminderId | int |
created | ?\DateTimeImmutable |
filename, mimeType | string |
fileSize | int |
base64file | string |
getBinary(): string decodes base64file back into raw bytes.
Enums used
ReminderStatus:DRAFT,OPEN,OVERDUE,PAID,CANCELED. Note: Billomat setsOVERDUEautomatically oncedueDatehas passed; never send it yourself.PdfType: shared PDF variants forpdf().
There is no comment action key enum for reminders: reminders simply have no /reminder-comments resource. Activity is tracked in the comments of the underlying invoice.
Pitfalls
delete()isn't limited toDRAFT. Billomat's API deletes reminders in any status. Once completed, prefercancel(): the document is numbered, and deleting it leaves a gap in the number sequence (GoBD).invoiceIdis required and immutable. A reminder without an invoice doesn't exist; aftercreate()the link is fixed and not part ofReminderUpdateOptions.- Items are read-only. To add line items, adjust the reminder text block (
/reminder-texts) or set a differentreminderTextIdbefore callingcomplete(). - No
/reminder-commentsendpoint. The activity log and free-form notes belong to the parent invoice workflow, not to the reminder itself. dueDatebeatsdueDays. If both are set, the absolutedue_datewins. It is cleaner to maintain only one of the two fields.- A
reminderTextIdwithout a matching entry in/reminder-textsleads to 422. Beforecreate(), make sure the reminder text block exists (via$billomat->reminderTexts->list()). - Status
OVERDUEis read-only. It is set on the server; the write models have nostatusfield at all. - Multiple dunning levels for the same invoice are possible. A second reminder for the same invoice automatically gets the next dunning level according to the levels configured in
reminderTexts(ReminderTextsApi). - Tag cloud and list share the same endpoint.
GET /reminder-tagswithoutreminder_idreturns the aggregated cloud (tagwrapper); withreminder_idit returns the actual tags (reminder-tagwrapper). The SDK splits this intocloud()andlist().
End-to-end example
<?php
declare(strict_types=1);
require __DIR__ . '/vendor/autoload.php';
use Justpilot\Billomat\Api\EmailOptions;
use Justpilot\Billomat\Api\InvoiceFilter;
use Justpilot\Billomat\Api\OrderBy;
use Justpilot\Billomat\Api\ReminderCreateOptions;
use Justpilot\Billomat\Api\ReminderTagCreateOptions;
use Justpilot\Billomat\BillomatClient;
use Justpilot\Billomat\Model\Enum\InvoiceStatus;
$billomat = BillomatClient::create(
billomatId: 'mycompany',
apiKey: getenv('BILLOMAT_API_KEY'),
);
// 1) Pick an open invoice
$openInvoices = $billomat->invoices->list(new InvoiceFilter(
clientId: 12345,
status: [InvoiceStatus::OPEN],
orderBy: OrderBy::asc('due_date'),
));
if ([] === $openInvoices) {
exit('No open invoice to remind.');
}
$invoice = $openInvoices[0];
// 2) Choose a reminder text block (e.g. the first level)
$reminderTexts = $billomat->reminderTexts->list();
$firstStage = $reminderTexts[0] ?? null;
// 3) Create the reminder as a draft
$opts = new ReminderCreateOptions(
invoiceId: $invoice->id,
reminderTextId: $firstStage?->id,
dueDays: 7,
subject: 'Payment reminder for invoice ' . ($invoice->invoiceNumber ?? ''),
);
$reminder = $billomat->reminders->create($opts);
printf("Reminder #%d created (status: %s)\n",
$reminder->id,
$reminder->status?->label() ?? 'unknown',
);
// 4) Add a tag (e.g. the dunning level) before completing
$billomat->reminders->tags->create(
$reminder->id,
new ReminderTagCreateOptions(name: 'dunning-level-1'),
);
// 5) Complete: DRAFT → OPEN
$billomat->reminders->complete($reminder->id);
// 6) Send by email
$mail = new EmailOptions(
to: ['client@example.com'],
subject: 'Reminder: invoice still open',
body: "Hello,\n\nplease settle our open invoice.\n\nBest regards",
);
$billomat->reminders->email($reminder->id, $mail);
// 7) Save the PDF locally
$pdfBinary = $billomat->reminders->pdfContent($reminder->id);
file_put_contents("reminder-{$reminder->id}.pdf", $pdfBinary);
// 8) Inspect the generated reminder items (read-only)
foreach ($billomat->reminders->items->list($reminder->id) as $item) {
printf("- %s: %s\n", $item->title ?? 'Item', $item->total?->format(2) ?? '-');
}