The public API can send an SMS, a tracked letter, or a registered letter with acknowledgement of receipt. These sends consume credits and cannot be recalled. The recommended flow is therefore:
- preview the message and its cost;
- set
maxCreditsto the cost you accept; - send with a stable
idempotencyKey; - read the communication back to follow its delivery.
Every endpoint on this page uses the OAuth scope dunning:manage. Reading a communication also
accepts communications:read.
Pricing
| Channel | Cost |
|---|---|
Sms |
1 credit per segment, following GSM-7 or Unicode encoding |
TrackedLetter |
6 credits, plus 1 credit per page after the first |
RegisteredLetter |
12 credits, plus 1 credit per page after the first |
The server always computes the cost from the final content. A long SMS can span several segments. Invoices and credit notes attached to a letter increase its page count.
Preview
POST [baseURL]/api/public/v1/accounts/ACCOUNT_ID/outgoing-message-communications/preview
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
SMS example:
{
"channel": "Sms",
"contactId": "CONTACT_ID",
"recipientPhone": "+33612345678",
"invoiceIds": ["INVOICE_ID"],
"paymentDeadline": "2026-08-25"
}
Letter example:
{
"channel": "RegisteredLetter",
"invoiceIds": ["INVOICE_ID"],
"creditNoteIds": [],
"paymentDeadline": "2026-08-25",
"recipientAddress": {
"fullName": "Client SA",
"street": "1 rue de la Paix",
"postalCode": "75001",
"city": "Paris",
"country": "FR"
},
"includePdf": true
}
The response charges no credit:
{
"pdfBase64": null,
"html": "",
"body": "Your final message",
"pageCount": 0,
"credits": 1
}
For an SMS, pageCount is 0, html is empty and pdfBase64 is null. For a letter,
pageCount includes the attachments. includePdf: true assembles the complete document set into
pdfBase64.
Send without creating a duplicate
POST [baseURL]/api/public/v1/accounts/ACCOUNT_ID/outgoing-message-communications
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Reuse the fields from the preview and add the two safeguards:
{
"channel": "Sms",
"contactId": "CONTACT_ID",
"recipientPhone": "+33612345678",
"invoiceIds": ["INVOICE_ID"],
"paymentDeadline": "2026-08-25",
"idempotencyKey": "account-123-invoice-456-sms-2026-08-25",
"maxCredits": 1
}
idempotencyKey is optional in the API, but any automated integration should send one. Billabex
derives the communication identifier from it. Replaying the same key within the organization
returns the communication already recorded, with no new charge and no new call to the provider.
One key must always mean the same send intent. Use a new key for a new recipient, new content, or a new deadline.
maxCredits is optional. If the rendered message costs more, the API refuses before any charge and
returns a 400. Prefer setting it to the credits value the preview returned.
Known limitation: two strictly concurrent requests carrying the same key can both pass the initial read before the first communication is recorded. Do not parallelise your own replays.
Read the delivery back
GET [baseURL]/api/public/v1/accounts/ACCOUNT_ID/outgoing-message-communications/COMMUNICATION_ID
Authorization: Bearer YOUR_ACCESS_TOKEN
The response carries channel, creditsCharged, deliveryStatus and statusHistory, among
others. Provider webhooks stay internal to Billabex. Poll this endpoint for the latest known state.
Errors
| Status | Meaning |
|---|---|
400 |
Invalid selection, recipient, address, letter settings, or cap |
402 |
Not enough credits |
404 |
Account or communication not found |
502 |
Provider result unknown, or send failed |
A 400 always carries a stable code field: branch on it rather than on the message. Exceeding
the cap returns app-dunning.outgoing-message-communication.maximum-credits-exceeded and a
details object holding credits, the real cost of the rendered message, and maxCredits, the
limit you had set. You can therefore decide to retry with a higher cap without previewing again.
When the provider result is unknown, credits stay charged because the message may have been accepted. Do not start over with another key.
The details a letter needs are configured through
PUT /api/public/v1/organizations/:organizationId/settings, under letterDetails.
MCP
The MCP server exposes the same flow:
| Tool | Scope | Purpose |
|---|---|---|
preview-outgoing-message |
mcp:read |
Compute the content and its cost |
send-outgoing-message |
mcp:write |
Send, with a mandatory key |
get-outgoing-message |
mcp:read |
Read the message and its delivery state |
The MCP preview returns neither base64 PDF nor HTML. send-outgoing-message requires
idempotencyKey and states its cost and its irreversibility explicitly.
The older /outgoing-letters endpoints and the preview-outgoing-letter /
send-outgoing-letter tools remain available for existing integrations, but are deprecated.