Authentication

MCP Server

Connect AI tools to Billabex using the Model Context Protocol (MCP) with OAuth 2.1.

MCP Server

Billabex exposes a Model Context Protocol (MCP) server so AI tools can access Billabex data using OAuth 2.1. This guide explains discovery, authorization, available tools and resources, and how to connect.

MCP Endpoint

The MCP server uses Streamable HTTP (stateless):

POST [baseURL]/mcp

OAuth Discovery Flow

MCP clients follow RFC 9728 (Protected Resource Metadata) and RFC 8414 (OAuth Authorization Server Metadata):

  1. Call POST /mcp without a token.
  2. The server responds with WWW-Authenticate including resource_metadata and scope.
  3. Fetch the resource metadata URL to discover authorization_servers and scopes_supported for /mcp.
  4. Fetch the authorization server metadata to locate the authorization and registration endpoints.

Example 401 Response

The MCP server returns:

WWW-Authenticate: Bearer resource_metadata="[baseURL]/.well-known/oauth-protected-resource", scope="mcp:read mcp:write"

Protected Resource Metadata (Path-Aware)

For /mcp, request:

[baseURL]/.well-known/oauth-protected-resource/mcp

The response includes:

{
  "resource": "[baseURL]",
  "authorization_servers": ["[baseURL]"],
  "scopes_supported": ["mcp:read", "mcp:write"],
  "bearer_methods_supported": ["header"]
}

Authorization Server Metadata

GET [baseURL]/.well-known/oauth-authorization-server

This returns the authorization_endpoint, token_endpoint, and registration_endpoint needed for OAuth 2.1 + PKCE.

Scopes

MCP uses two scopes:

Scope Access Description
mcp:read Read List and get invoices, accounts, communications.
mcp:write Write Create, update, delete operations.

If an authorization request omits the scope parameter, Billabex defaults to mcp:read mcp:write for MCP clients.

Protocol Details

  • Transport: Streamable HTTP (stateless mode).
  • Authentication: Authorization: Bearer <token> header (required).
  • Rate limiting: 300 requests/minute per token (sliding window).
  • CORS header: Clients may send Mcp-Protocol-Version header.

Resources

MCP resources provide metadata that AI tools can read to understand Billabex capabilities.

URI Description
billabex://about Platform overview, value proposition, key features
billabex://concepts Key entities, statuses, relationships, and business rules
billabex://workflow Step-by-step dunning workflow, task handling, and processes

These resources help AI agents understand:

  • About: What Billabex is, how it helps businesses manage invoice collection
  • Concepts: Domain model including invoices, accounts, customer balances, communications, and tasks
  • Workflow: Standard dunning workflow from invoice overdue to payment recovery

Prompts

MCP prompts provide business-oriented templates for AI agents to perform common tasks. Unlike tools, prompts use human-readable parameters (account names, organization names) instead of technical IDs.

Prompt Parameters Use Case
financial-snapshot organizationName?, period? (7/30/all days) Overview of outstanding balances, top accounts, cash flow
account-360 accountName, organizationName? Complete account summary with invoices, communications, tasks
daily-plan organizationName? Daily action plan: tasks, reminders, follow-ups
daily-digest organizationName? Summary of today’s activities and updates
communication-intelligence accountName?, organizationName? Recent replies, payment promises, accounts that replied but didn’t pay
risk-report organizationName? Accounts at risk, silent accounts, accounts without contacts
silent-accounts organizationName?, period? (30/90/all days) Accounts that never replied to a follow-up email
blocked-accounts organizationName? Accounts whose emails bounced or were marked as spam
inbound-email-digest organizationName?, period? (7/30/90 days) Synthesis of the incoming emails over a recent period

How Prompts Work

  1. User provides names: You call a prompt with an account name like “Acme Corp” instead of a UUID
  2. Agent resolves IDs: The prompt instructs the AI agent to use tools like list-accounts and list-organizations to find the matching IDs
  3. Agent gathers data: The agent calls multiple tools to gather relevant data
  4. Agent presents results: Results are formatted naturally without exposing technical IDs

Example: Financial Snapshot

{
  "name": "financial-snapshot",
  "arguments": {
    "organizationName": "My Company",
    "period": "30days"
  }
}

The agent will:

  1. Use list-organizations to find “My Company”
  2. Use list-customer-balances to get outstanding balances
  3. Use list-invoices to find upcoming due invoices
  4. Present a formatted overview of your financial position

Tools Reference

Tools are grouped by domain. Read tools require mcp:read; Write tools require mcp:write.

Tool outputs include human-friendly fields such as displayName and summary where available. Prefer those fields for user-facing responses and keep IDs for follow-up tool calls.

All tools include annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) to help AI agents understand their behavior.

Invoicing

Tool Access Description
list-invoices Read List invoices with the account payment method (paginated).
get-invoice Read Get invoice by ID, or by number within an account or an org.
create-invoice Write Create invoice with base64 file upload and optional sourceId.
update-invoice-paid-amount Write Update paid amount on an invoice.
update-invoice-payment-schedule Write Replace or remove an invoice payment schedule.
delete-invoice Write Delete an invoice.
list-accounts Read List customer accounts with their type and payment method.
get-account Read Get account type, details and contacts.
get-account-financial-overview Read Explain the account balance and list every invoice installment.
create-account Write Create a customer account with an optional account type.
update-account Write Update account type, name, currency, address or payment method.
delete-account Write Delete a customer account.
list-contacts Read List contacts on an account.
get-contact Read Get a specific contact.
create-contact Write Add a contact to an account.
update-contact Write Update a contact.
delete-contact Write Remove a contact.
upsert-contact Write Create or update contact by email/name match.
set-contact-enabled Write Enable or disable a contact on an account.
list-credit-notes Read List credit notes with the account payment method (paginated).
get-credit-note Read Get credit note details, by ID or by number.
create-credit-note Write Create a credit note, with an optional sourceId.
update-credit-note-refunded-amount Write Record a partial or full cash refund.
delete-credit-note Write Delete a credit note.
apply-credit-allocation Write Apply credit from a credit note to an invoice.
remove-credit-allocation Write Remove a credit allocation.
add-account-tags Write Attach existing labels to an account.
remove-account-tag Write Detach a label from an account.
link-account-source Write Link an external source to an account.
update-account-source Write Update account source last-sync timestamp.
unlink-account-source Write Unlink an external source.

update-invoice-payment-schedule receives an invoice UUID, the complete installments array and an optional skipConfirmation. Each installment contains a calendar date and a positive amount. Send an empty array to remove the schedule. Confirmation is requested by default; automated clients can set skipConfirmation: true.

A disabled contact is never contacted proactively by the agent. Re-enabling one is a human decision, so set-contact-enabled should not be used to undo a disable the client asked for.

create-contact, update-contact and upsert-contact take a phones list of { number, countryCode? }. A number starting with + or 00 carries its own country code; any other number is national and requires countryCode (ISO 3166-1 alpha-2), because Billabex never guesses a country. Contacts are returned with phones: [{ number, type, country, origin }], where type (Mobile / Landline / Unknown) and country are derived from the number itself and read-only. Writing phones replaces only what a human entered, never the numbers a connector brought (origin: "Connector"); omit the field to leave everything alone, send [] to clear the manual ones. Only a French mobile can receive an SMS. See Contacts for the full model.

Documents created by create-invoice and create-credit-note carry the Mcp channel, distinct from the PublicApi one the REST API stamps. They can live on an organization that is also synced with one or more connectors: see Sources for why an account’s source never tells you where one of its documents came from.

Both create tools also take an optional sourceId, your own identifier for the document. It is frozen at creation, returned on invoices and credit notes alongside connectionId, unique within the organization (a re-create with the same value is refused rather than duplicated), and filterable on list-invoices and list-credit-notes with is, isAnyOf, isEmpty and isNotEmpty only, because it is case-sensitive. Document numbers, on the other hand, are unique per account: get-invoice and get-credit-note answer exactly when given accountId plus number, while a lookup by organizationId plus number may answer any one of several namesakes.

create-contact, update-contact and upsert-contact may return a warnings array next to the contact when part of the write was not kept: a fullName or language a human owns that an automated sync tried to overwrite (MANUALLY_SET), or a phone number a sync sent that could not be normalized (UNPARSEABLE). The rest of the write still landed. Read the array instead of trusting a bare success.

list-invoices and list-credit-notes return accountPaymentMethod as DIRECT_DEBIT, BANK_TRANSFER, or null. The value is read from the account when the list is requested, so changing the account does not require updating each document.

list-accounts, list-credit-notes, get-account, get-invoice and get-credit-note return readOnlyReason: null when the record accepts writes, otherwise the connector that would overwrite them. Read it instead of deriving a verdict from channel or source. list-invoices does not carry it, because its projection cannot tell a legacy invoice apart; call get-invoice when you need the verdict for one of those rows.

Account tools expose accountType as Professional, Consumer, or Unknown. create-account and update-account accept the same field. Omit it at creation to infer Professional from a legal identifier, otherwise the account remains Unknown. A standard connector controls the value and cannot be overridden through MCP; a Custom source remains editable. Consumer accounts are not checked against BODACC, while their SIREN and VAT number, if present, remain available through the API.

Dunning

Tool Access Description
list-customer-balances Read List customer outstanding balances (paginated).
get-customer-balance Read Get balance details with invoices/credit notes.
list-communications Read List incoming and outgoing emails at once, filterable server-side.
list-silent-accounts Read List accounts emailed at least once that have never replied.
list-outgoing-communications Read List outgoing dunning emails (paginated).
get-outgoing-communication Read Get outgoing communication details.
list-outgoing-communications-by-account Read List outgoing communications for an account.
update-outgoing-communication Write Update a scheduled outgoing email.
send-outgoing-communication Write Send an email immediately on behalf of the agent, to free-form recipients.
preview-outgoing-message Read Preview and price an SMS or postal reminder without returning a PDF.
send-outgoing-message Write Send a paid message with a required idempotency key.
get-outgoing-message Read Get a sent SMS or postal reminder and its delivery state.
preview-outgoing-letter Read Deprecated alias for postal previews.
send-outgoing-letter Write Deprecated alias for postal sends.
list-incoming-communications Read List incoming (reply) communications.
get-incoming-communication Read Get incoming communication details.
list-incoming-communications-by-account Read List incoming communications for an account.
get-account-dunning-status Read Read whether an account is being chased, and the one reason it is not.
pause-account-dunning Write Suspend reminders with a fact reason, or until a date for a dated wait.
resume-account-dunning Write Lift human-resumable pauses and return any protections that remain.

get-account-dunning-status answers “why is nothing going out on this account”. Its silenceReason names the single cause, ranked once on the server, so the answer matches the one the web application shows and the one GET /accounts/{accountId}/dunning returns on the REST API: suspended, then blocking-task, then below-minimum-amount, then connector-sync-stale, then first-reminder-delay, and null when nothing holds the account back. Read it rather than ranking the individual flags yourself.

list-communications reads a pre-joined projection and filters, sorts and paginates on the server. Prefer it over paging the incoming and outgoing lists separately. Beyond its own accountId, type and date arguments, it takes the filters conditions described in Filtering Lists: direction, status, deliveryStatus, accountName, contactName, subject and date.

A communication outlives the account it belonged to. When that account has been deleted, the item still carries its accountId and accountFullName, and accountDeletedAt holds the instant of the deletion. Treat a non-null accountDeletedAt as “do not try to read this account”: the account tools answer as if it never existed. It is null on every account still there.

Email

Tool Access Description
list-emails Read List all emails (incoming + outgoing).
list-incoming-emails Read List incoming emails (paginated).
get-incoming-email Read Get incoming email with simplified body.
list-outgoing-emails Read List outgoing emails (paginated).
get-outgoing-email Read Get outgoing email with simplified body.

Platform

Tool Access Description
list-organizations Read List organizations for the user, with their credits.
get-organization Read Get organization details, including the credit balance.
update-organization Write Update organization name or email domain.
update-organization-settings Write Update reminder delays and the reminder threshold.
set-organization-logo Write Set or remove the organization logo (base64 image).
list-organization-members Read List members and their profile picture metadata.
remove-organization-member Write Remove a member from an organization.
set-my-avatar Write Set or replace the authenticated user’s profile picture.
remove-my-avatar Write Remove the authenticated user’s profile picture.
create-organization-invitation Write Invite a user to an organization.
cancel-organization-invitation Write Cancel a pending invitation.
list-tags Read List the organization’s labels plus the standard ones.
create-tag Write Create a label owned by the organization.

Both organization tools return credits, the prepaid balance that pays for the SMS and postal letter reminders Billabex sends. See Credits below.

They also return the two effective first-reminder rules, firstReminderDelayForDirectDebit and firstReminderDelayForBankTransfer, each { days, direction } with direction being BEFORE or AFTER the due date, or null when neither an override nor firstReminderDelayDays is set. update-organization-settings writes them under the same names: an absent key leaves that mode alone, an explicit null records that the mode has no rule of its own and falls back on firstReminderDelayDays. An account whose payment method is unknown follows firstReminderDelayDays alone, never either of these two.

They also return the reminder threshold as a pair, minimumDunningAmount and minimumDunningAmountCurrency: an account is still reminded when its balance equals the amount, and no longer when it is strictly below, the balance being converted to the threshold currency first. New organizations start at 2 EUR. A null currency next to a non-null amount is a legacy setting written before currencies existed: the amount is then compared in each account’s own currency.

update-organization-settings writes the pair:

{ "minimumDunningAmount": 5, "minimumDunningAmountCurrency": "USD" }
  • both keys together set the pair;
  • the amount alone keeps the currency already set, or keeps the legacy comparison when there is none;
  • { "minimumDunningAmount": null, "minimumDunningAmountCurrency": null } removes the threshold;
  • a currency without its amount is rejected, and so is a non-null amount with an explicit null currency.

They also return logo, the image uploaded for the organization, or null. null does not mean no logo is shown: with none uploaded, one is derived from the organization’s email domain at display time and nothing is stored. set-organization-logo takes a base64 PNG, JPEG or WebP up to 2 MB, or file: null to remove it. The real file signature is checked, not the declared type, so an SVG is refused.

Tasking

Tool Access Description
list-account-tasks Read Search the organization’s tasks (paginated).
get-account-task Read Get task details and required input format.
cancel-account-task Write Cancel an account task, with an optional reason.
add-account-task-text-interaction Write Add a text response to a task.
add-account-task-structured-interaction Write Add a structured response to a task.

list-account-tasks changed contract. It used to take a required accountId, return every active task of that account at once and never paginate. It now takes organizationId, accountId being optional, returns tasks of any status by default, and pages. Its former type argument became a type filter condition. Items keep their full shape, interaction thread included: the projection selects the page, then every row is rehydrated from its aggregate.

Filtering Lists

list-accounts, list-invoices, list-credit-notes, list-communications and list-account-tasks take a filters argument: an array of conditions, each { field, operator, value } or { field, operator, values }.

{
  "organizationId": "123e4567-e89b-12d3-a456-426614174000",
  "filters": [
    {
      "field": "status",
      "operator": "isAnyOf",
      "values": ["Overdue", "Issued"]
    },
    { "field": "dueDate", "operator": "before", "value": "2026-01-01" },
    { "field": "remainingBalance", "operator": "greaterThan", "value": "1000" }
  ],
  "first": 50
}
  • Conditions combine with AND; the values of one condition combine with OR.
  • A field carries at most one condition, and at most 20 conditions per call.
  • Fields and operators come from a per-tool allowlist, listed in the description of each tool. An unknown field, or an operator the field does not accept, is refused, never ignored.
  • Dates are written YYYY-MM-DD and compared by whole day. Text comparison ignores case and accents.
Tool Fields
list-accounts accountName, tags, dunningPaused, daysOverdue, overdueInvoiceCount, overdueBalance, remainingBalance, hasPaymentSchedule, paymentMethod, collectiveProceeding, source
list-invoices number, accountName, tags, status, dueDate, paymentSchedule, paymentMethod, totalAmount, remainingBalance, channel
list-credit-notes number, accountName, tags, status, issuedDate, paymentMethod, totalAmount, remainingAmount, channel
list-communications direction, status, deliveryStatus, accountName, contactName, subject, date
list-account-tasks title, accountName, status, type, tags, balance, createdAt, updatedAt

On list-invoices and list-credit-notes, tags matches the labels of the account the document belongs to, not labels on the document itself, which carries none. It takes label identifiers, the ones list-tags returns, never their display names. That is how a group of companies is read in one call: label the accounts once, then { "field": "tags", "operator": "containsAny", "values": ["<label id>"] } returns every open invoice of the whole group, instead of one call per account.

The same conditions are available on the public REST API, where they travel as one query string. See Pagination & Filtering for the operator table and the exact semantics.

Credits

get-organization and list-organizations return a credits field: the prepaid balance that pays for the SMS and postal letter reminders Billabex sends on your behalf. Emails are free and never consume it.

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "name": "Acme Corporation",
  "credits": 250
}

At zero, SMS and letters stop going out; email reminders keep running. Nothing else in the API signals that, which is why the balance is worth reading if you build a dashboard or an alert on top of Billabex.

The field is read-only, but send-outgoing-message spends it after rendering the selected invoices and credit notes. Call preview-outgoing-message first to obtain the exact segment or page price without spending anything, then pass that value as maxCredits. Reuse the same idempotencyKey for every retry. send-outgoing-communication still sends a free email. Topping the balance up is not self-service; Billabex handles it manually.

Pagination

Tools returning lists use cursor-based pagination:

  • Parameters: first (items per page, default 20, max 100), after (cursor).
  • Response: nodes array + pageInfo.endCursor.
  • Sort: list-accounts, list-invoices, list-credit-notes, list-communications and list-account-tasks take sortBy and sortOrder (asc/desc). sortBy accepts the same values as the matching REST list, enumerated in each tool description.

On a filtered list, an endCursor belongs to the exact set of filters and the sort it was issued for. Pass it back unchanged to get the next page; whenever a condition or the sort changes, drop it and read the list again from its first page, otherwise the call is refused.

Notes

  • OAuth is required for all MCP access.
  • Tokens must be sent in the Authorization: Bearer <token> header.
  • Rate limiting applies to MCP requests (300/min per token).
  • update-outgoing-communication accepts message in markdown. Do not include a signature; the platform renders HTML/text and appends the agent signature automatically.

Next Steps