Account tasks in Billabex represent action items that require human input or approval. They are created exclusively by the Billabex AI agent when it needs user guidance, contact information, or explicit approval to proceed with dunning activities on a specific account.
This guide covers how to interact with account tasks via the API: listing, retrieving, canceling, setting aside, and responding with interactions.
Overview
Account tasks follow a simple lifecycle:
- Created - The Billabex agent creates an account task when user input is required
- Open - The task awaits user input
- Interaction - A user responds via an interaction message
- Closed/Canceled - The task is resolved by the agent or canceled by the user
Each account task has a type that determines what kind of response is expected.
Note: Account tasks can only be created by the Billabex agent. The public API allows you to read tasks, respond to them, set them aside and cancel them - but not create new ones.
Account Task Types
| Type | Description | Expected Response |
|---|---|---|
ApproveEligibility |
Approve or reject a contact for dunning | Structured: boolean |
NeedContacts |
Provide contact details for an account | Structured: contact object |
AskNextAction |
Free-form question from the agent | Text message |
NeedUserInput |
Agent needs clarification or information | Text message |
Unknown |
Fallback task type | Structured: boolean |
Notice |
The agent reports something it already did; nothing is expected back | None |
extraData.idempotencyKey
Task types that carry no structured payload of their own (Notice, AskNextAction,
NeedContacts, ApproveFirstOutreach, Unknown) expose extraData.idempotencyKey. It is a stable
key the agent sets when several tasks of the same type may legitimately coexist on one account, and
it is null otherwise. It does not change when the task is replayed, so use it to reconcile a task
you have already seen. A Notice about a customer complaint, for instance, is keyed
contact-grievance:<incoming email id>.
Required Scopes
| Scope | Access Level | Endpoints |
|---|---|---|
tasks:read |
Read-only | GET /account-tasks, GET /account-tasks/search, GET /account-tasks/:accountTaskId |
tasks:all |
Full access | All read + POST /account-tasks/:accountTaskId/cancel, POST /account-tasks/:accountTaskId/set-pending, add interactions |
List Active Account Tasks
Retrieve active account tasks for a specific account.
GET [baseURL]/api/public/v1/account-tasks?accountId=ACCOUNT_ID
Authorization: Bearer YOUR_ACCESS_TOKEN
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
accountId |
UUID | Yes | Filter account tasks by account |
type |
string | No | Filter by account task type (optional) |
Example response:
[
{
"task": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"organizationId": "org-uuid",
"accountId": "account-uuid",
"type": "NeedUserInput",
"title": "Clarification needed on invoice #1234",
"status": "UserActionRequired",
"isActive": true,
"isClosed": false,
"isCanceled": false,
"thread": [
{
"id": "interaction-uuid",
"message": {
"type": "Text",
"value": "The customer mentioned a dispute. How should I proceed?"
},
"attachments": [],
"sentAt": "2024-05-01T10:15:30.000Z",
"agent": {
"email": "agent@billabex.com",
"firstName": "Alex",
"lastName": "Martin"
}
}
],
"assignees": [],
"createdAt": "2024-05-01T10:15:30.000Z",
"closedAt": null,
"canceledAt": null,
"cancelReason": null,
"canceledBy": null,
"completedAt": null,
"isPending": false,
"pendingAt": null,
"pendingUntil": null,
"extraData": { "refEmailMessageIds": [] }
}
}
]
Search Account Tasks
GET /account-tasks answers a single account and returns its active tasks in
one shot. To work across the whole organization, with filters, sorting and
pagination, use the search endpoint.
GET [baseURL]/api/public/v1/account-tasks/search?organizationId=ORG_ID&first=20
Authorization: Bearer YOUR_ACCESS_TOKEN
Query parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
organizationId |
UUID | Yes | Organization to search in |
accountId |
UUID | No | Restrict to a single account |
query |
string | No | Free text search on the task title and the account name |
filters |
string | No | Filter conditions, see Pagination & Filtering |
sortBy |
string | No | createdAt, updatedAt, title, accountFullName, status or balance |
sortOrder |
string | No | asc or desc |
first |
integer | No | Between 1 and 100 |
after |
string | No | Cursor from pageInfo.endCursor |
Filterable fields:
| Field | Operators |
|---|---|
title |
contains, startsWith, equals |
accountName |
contains, startsWith, equals |
status |
is, isNot, isAnyOf |
type |
is, isNot, isAnyOf |
tags |
containsAny, containsAll, containsNone, isEmpty, isNotEmpty |
balance |
equals, greaterThan, lessThan, between, isEmpty, isNotEmpty |
createdAt |
on, before, after, between |
updatedAt |
on, before, after, between, isEmpty, isNotEmpty |
type accepts ApproveEligibility, ApproveContactChange,
ApprovePaymentArrangement, ApproveFirstOutreach, NeedContacts,
AskNextAction, NeedUserInput, PaymentScheduleInvalidated, Notice and
Unknown. status accepts the values of the table further down this page.
Open tasks of a given type, most recent first:
GET [baseURL]/api/public/v1/account-tasks/search?organizationId=ORG_ID&filters=status%3Ais%3AUserActionRequired%3Btype%3Ais%3ANeedContacts&sortBy=createdAt&sortOrder=desc
Tasks created since the start of the quarter, on accounts owing more than 1000:
GET [baseURL]/api/public/v1/account-tasks/search?organizationId=ORG_ID&filters=createdAt%3Aafter%3A2026-06-30%3Bbalance%3AgreaterThan%3A1000
Example response:
{
"nodes": [
{
"id": "123e4567-e89b-12d3-a456-426614174000",
"organizationId": "org-uuid",
"accountId": "account-uuid",
"accountFullName": "Acme Corporation",
"accountTagIds": [],
"title": "Clarification needed on invoice #1234",
"type": "NeedUserInput",
"status": "UserActionRequired",
"assignees": [],
"assigneesFullName": [],
"isActive": true,
"isClosed": false,
"isCanceled": false,
"isPending": false,
"balance": 1250.5,
"currency": "EUR",
"createdAt": "2026-05-01T10:15:30.000Z",
"updatedAt": "2026-05-02T08:00:00.000Z",
"closedAt": null,
"canceledAt": null,
"cancelReason": null,
"canceledBy": null,
"pendingAt": null,
"pendingUntil": null,
"completedAt": null,
"lastAgentInteractionAt": "2026-05-01T10:15:30.000Z"
}
],
"pageInfo": {
"endCursor": "v2:8f14e45fceea:2"
}
}
Two things to keep in mind:
- Items do not carry the interaction
thread. CallGET /account-tasks/:accountTaskIdfor the thread of a task. - The
endCursorbelongs to the filters and the sort it was issued for. Changing either one invalidates it, and the call returns400with the codeLIST_CURSOR_INVALID.
Get an Account Task
Retrieve a single account task by ID.
GET [baseURL]/api/public/v1/account-tasks/:accountTaskId
Authorization: Bearer YOUR_ACCESS_TOKEN
Returns the same structure as above, wrapped in { "task": { ... } }.
Cancel an Account Task
Cancel an active account task. This marks the task as canceled without resolving it.
POST [baseURL]/api/public/v1/account-tasks/:accountTaskId/cancel
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"reason": "Invoice re-sent by hand, the contact confirmed reception."
}
The body is optional, and so is reason (2000 characters at most). Sending one
is strongly recommended: cancelling is the only ending that answers nothing, and
several task types hold the automatic follow-ups back while they are active, so
cancelling one hands the cadence back to the agent. The reason is shown next to
the cancellation and is read back by the agent before it reopens the same
subject on that account.
Returns the updated account task with isCanceled: true, canceledAt set,
cancelReason carrying the reason when one was given, and canceledBy naming
who cancelled and through which door:
{
"canceledBy": {
"userId": "usr_01H8...",
"userFullName": "Camille Roy",
"channel": "PublicApi"
}
}
canceledBy is null when the agent cancelled the task, and on cancellations
recorded before 2026-09-03. channel is Webapp, PublicApi or Mcp, and is
null on a cancellation recorded before the door was tracked. userFullName is
null when the user could not be named, typically after they left the
organization.
Set an Account Task Aside
Defer a task instead of answering it now, optionally naming the date it should come back.
POST [baseURL]/api/public/v1/account-tasks/:accountTaskId/set-pending
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
{
"pendingUntil": "2026-09-20T08:00:00.000Z"
}
The body is optional, and so is pendingUntil, which must be an ISO 8601
instant in the future. Pass it whenever you know when the answer will be
available: on that date Billabex posts an agent message on the task explaining
that it is back, and the task returns to UserActionRequired on its own.
Omit it and the task still comes back, after 30 days. A task never stays set aside forever, which matters because several task types hold the automatic follow-ups of their account back while they are active: deferring one postpones the decision, it does not hand the cadence back. Cancel or answer the task for that.
Returns the updated account task with isPending: true, pendingAt set, and
pendingUntil carrying the date when one was given.
Add an Interaction
Respond to an account task by adding a user interaction. This is how users provide the requested input to the Billabex agent.
POST [baseURL]/api/public/v1/account-tasks/:accountTaskId/interactions
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Text Interaction
For NeedUserInput and AskNextAction account tasks:
{
"interaction": {
"message": {
"type": "Text",
"value": "Please offer a 3-month payment plan with 10% interest."
},
"attachments": []
}
}
Structured Interaction
For ApproveEligibility, NeedContacts, and Unknown account tasks, use structured messages.
ApproveEligibility Response
{
"interaction": {
"message": {
"type": "Structured",
"schema": "ApproveEligibility",
"schemaVersion": 1,
"value": true
}
}
}
The value is a boolean: true to approve, false to reject.
NeedContacts Response
{
"interaction": {
"message": {
"type": "Structured",
"schema": "NeedContacts",
"schemaVersion": 1,
"value": {
"fullName": "Jane Doe",
"email": "jane@company.com",
"language": "en"
}
}
}
}
Required fields in value:
fullName(string)email(string)language(string, e.g., “en”, “fr”)
Unknown Response
{
"interaction": {
"message": {
"type": "Structured",
"schema": "Unknown",
"schemaVersion": 1,
"value": true
}
}
}
Message Schema Reference
| Account Task Type | Schema Name | Version | Value Type |
|---|---|---|---|
ApproveEligibility |
ApproveEligibility |
1 | boolean |
NeedContacts |
NeedContacts |
1 | { fullName, email, language } |
Unknown |
Unknown |
1 | boolean |
AskNextAction |
N/A | N/A | Text message only |
NeedUserInput |
N/A | N/A | Text message only |
Validation Rules
The API enforces strict validation on interaction messages:
- Text account tasks (
NeedUserInput,AskNextAction) must receivetype: "Text"messages - Structured account tasks must receive
type: "Structured"messages with matching schema - Schema name and version must match exactly
- Value must conform to the expected type for that schema
Invalid messages return a 400 Bad Request with details.
Account Task Status Values
| Status | Description |
|---|---|
Open |
Task created, awaiting processing |
InProgress |
Agent is working on it |
UserActionRequired |
Waiting for user input |
Closed |
Task completed successfully |
Canceled |
Task was canceled |
Pending |
Task set aside, coming back on pendingUntil or after 30 days |
Common Errors
400 Bad Request - Invalid Message
{
"statusCode": 400,
"message": "Text message is required for this task type"
}
Cause: Sending a structured message to a text-only account task type.
404 Not Found - Account Task Not Found
{
"statusCode": 404,
"message": "Account task 123e4567-e89b-12d3-a456-426614174000 not found"
}
Cause: The account task ID does not exist or belongs to another organization.
403 Forbidden - Insufficient Scope
{
"statusCode": 403,
"message": "Forbidden",
"error": "insufficient_scope"
}
Cause: Your token lacks tasks:all for write operations.
Resolving follow-up safeguards
A NeedUserInput answer follows the same rules in the web app, the API and MCP. An instruction is
neither confirmed nor the task closed if its effects could not be applied. Closing, cancelling or
setting a task aside does not resolve the payment or the correction it asks you to verify.
For an announced correction, update the invoices concerned, then answer the task. If the correction was abandoned, say so explicitly and authorize the follow-ups to resume on the original invoice. A plain “thanks” or a generic resumption is not that decision. Announced payments are resolved by an explicit decision on the account, separately from the task.
Announced payments
An announced payment is not a recorded payment. The register stays active after its task is closed
or cancelled. Use the dunning:manage scope and the endpoints under
/api/public/v1/accounts/{accountId}/payment-declarations:
GET: read the declarations and their decisions.POST: recordsourceRef,declaredAt, and optionallypaymentDate,amount,currency,declaredStage,invoiceNumbers,proofDocumentAttached. ReusesourceRefwhen you retry.POST /{declarationId}/resolve: supply aresolutionand an explicitnote.
proofDocumentAttached says the customer attached a document they present as proof of that
payment: a transfer advice, a bank receipt, a screenshot of the transaction. The field proves
nothing about the money arriving and changes neither the announced stage nor the hold on
follow-ups; it says the reconciliation starts from a reference rather than from a bare claim, and
the review task reports it. A declaration recorded before the field existed carries false.
The review question opens as soon as the declaration is recorded, not when its grace period expires.
Resolutions: PaymentReceived, CreditApplied, InvoicesSettled, PaymentNotReceived,
Withdrawn, ResumeAuthorized. A confirmed receipt (paymentConfirmedAt) does not release the
follow-ups while invoices stay open. ResumeAuthorized authorizes the resumption without denying
the payment. A credit note or a settled invoice does not prove a bank receipt. All these routes
return the current list of declarations.
Next Steps
- Getting Started - Integration quick start
- OAuth Scopes - Required permissions
- Pagination & Filtering - Cursors and the
filtersparameter - Rate Limiting - Avoid hitting limits
- API Reference - Full endpoint documentation
Support
Questions about account tasks and interactions?
Contact us via the website contact form.