---
title: "Account Tasks & Interactions"
description: "Respond to Billabex agent account tasks: search, list, get, cancel, set aside, and add user interactions."
canonical: https://developer.billabex.com/en/guides/account-tasks/
lang: en
alternate: https://developer.billabex.com/fr/guides/taches-de-compte/
last-updated: 2026-10-27
---

# Account Tasks & Interactions

> Respond to Billabex agent account tasks: search, list, get, cancel, set aside, and add user interactions.

Source: https://developer.billabex.com/en/guides/account-tasks/
Language: English (en)
French version: https://developer.billabex.com/fr/guides/taches-de-compte/

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.

Task timestamps in lists and details are ISO 8601 instants in UTC with the `Z` suffix
(for example `2026-10-19T16:34:00.123Z`). Preserve that timezone when calculating durations;
convert to the user's timezone only for display.

## Overview

Account tasks follow a simple lifecycle:

1. **Created** - The Billabex agent creates an account task when user input is required
2. **Open** - The task awaits user input
3. **Interaction** - A user responds via an interaction message
4. **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`      | Choose the next action                                               | Structured choice          |
| `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/close`, `POST /account-tasks/:accountTaskId/set-pending`, add interactions |

## List Active Account Tasks

Retrieve active account tasks for a specific account.

```http
GET https://next.billabex.com/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:

```json
[
  {
    "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@example.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.

```http
GET https://next.billabex.com/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](/en/guides/pagination/#filtering-a-list) |
| `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:

```http
GET https://next.billabex.com/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:

```http
GET https://next.billabex.com/api/public/v1/account-tasks/search?organizationId=ORG_ID&filters=createdAt%3Aafter%3A2026-06-30%3Bbalance%3AgreaterThan%3A1000
```

Example response:

```json
{
  "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`. Call
  `GET /account-tasks/:accountTaskId` for the thread of a task.
- The `endCursor` belongs to the filters and the sort it was issued for.
  Changing either one invalidates it, and the call returns `400` with the code
  `LIST_CURSOR_INVALID`.

## Get an Account Task

Retrieve a single account task by ID.

```http
GET https://next.billabex.com/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.

```http
POST https://next.billabex.com/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:

```json
{
  "canceledBy": {
    "userId": "usr_01H8...",
    "userFullName": "Camille Roy",
    "channel": "PublicApi"
  }
}
```

A payment check cannot be cancelled: a task whose `extraData.paymentDeclarationId` is set answers
`400` with the code `app-tasking.account-task.payment-review-requires-decision` and
`details.paymentDeclarationId`. Record the decision on the declaration (see
[Announced payments](#announced-payments)): it closes the check.

`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.

## End an Account Task

Close a task whose subject was dealt with elsewhere, without asking the agent anything: no model
decides the closure and no email is written for it.

```http
POST https://next.billabex.com/api/public/v1/account-tasks/:accountTaskId/close
Authorization: Bearer YOUR_ACCESS_TOKEN
```

The body is empty; any field sent is refused with `400`. The author is the token user. Only a
`NeedUserInput` task asking for information can be ended this way, and not while one of your
replies is being processed (`InProgress`):

| Refusal (`400`)                                             | Case                                                                            |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `app-tasking.account-task.payment-review-requires-decision` | payment check: record the decision on the declaration                           |
| `app-tasking.account-task.closure-not-allowed`              | another task type, an announced correction or a dispute: answer with its action |
| `app-tasking.account-task.reply-in-progress`                | a reply is being processed: retry once the agent answered                       |
| `app-tasking.account-task.canceled`                         | the task is cancelled and stays so                                              |

Missing information is not marked as provided and pauses stay in place. The closure may lift the
hold on follow-ups this task represented: read the account dunning status to know. Returns the task
with `isClosed: true`, `closedAt` and `closedBy` (same shape as `canceledBy`). Sending the same
closure again returns the closed task with its first author. `closedBy` is `null` on a closure the
agent decided.

Ordinary `NeedUserInput` tasks referencing an email, and estimated-date reviews whose key starts
with `estimated-payment-date-review:`, reject `/close` and `/cancel` with
`app-tasking.account-task.closure-not-allowed`. Use the reasoned resolution below. A linked draft
also requires its dedicated decision before closing or cancelling. Recipient-review cancellation
remains available and preserves its pause.

### Reasoned resolution of an ordinary information request

Read the task first, then send a structured interaction to
`POST /api/public/v1/account-tasks/:accountTaskId/interactions`:

```json
{
  "message": {
    "type": "Structured",
    "schema": "ResolveNeedUserInput",
    "schemaVersion": 1,
    "value": {
      "decision": "handled-elsewhere",
      "reason": "Documents supplied directly to the debtor",
      "waivedRequirementIds": ["purchase-order"]
    }
  },
  "attachments": []
}
```

`waivedRequirementIds` must match every current requirement ID: `null` for an unknown contract,
`[]` for an empty contract. Stale, partial or duplicate selections are rejected. Choose
`handled-elsewhere`, `no-longer-needed` or `continue-follow-up` and supply a reason of 1 to 4,000
characters after trimming whitespace.

The human decision, receipt and closure are saved together. A waiver is never evidence of a
received document or a verified payment. No email is sent; independent tasks, disputes and pauses
remain unchanged. Attachments and tasks with a dedicated decision are rejected. An identical
retry by the same user returns the existing interaction. Read the task again for its receipt
and current state.

For MCP, pass the `value` object above as `resolution` to `close-account-task`. The tool reads
back and returns the task state after recording the decision.


## Set an Account Task Aside

Defer a task instead of answering it now, optionally naming the date it should
come back.

```http
POST https://next.billabex.com/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.

```http
POST https://next.billabex.com/api/public/v1/account-tasks/:accountTaskId/interactions
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
```

### Text Interaction

For `NeedUserInput` account tasks:

```json
{
  "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.

#### AskNextAction

The `resume` choice accepts optional `instructions` (at most 2,000 characters) for the next email only. A nonblank instruction prepares a draft and keeps reminders paused. Read `extraData.pendingCommunicationId` and the full communication, then submit `DecideOutgoingEmailDraft` v1 on this task with `{ "communicationId": "…", "decision": "send" }` to approve the email and request resumption. `discard` abandons the draft and keeps reminders paused. Without instructions, `resume` retains its immediate behavior. MCP uses the existing `decide-account-task-next-action` and `decide-account-task-email-draft` tools.

`AskNextAction` requires a structured response: `take-over`, `resume` or `add-contact`. The last choice requires `contact.email` and `contact.language`; `contact.fullName` is optional. Clients previously sending free text now receive an immediate HTTP 400 error, without recording an interaction or changing task status. No new schema or migration is required.

```json
{
  "interaction": {
    "message": {
      "type": "Structured",
      "schema": "AskNextAction",
      "schemaVersion": 1,
      "value": { "choice": "resume" }
    }
  }
}
```

#### ApproveEligibility Response

```json
{
  "interaction": {
    "message": {
      "type": "Structured",
      "schema": "ApproveEligibility",
      "schemaVersion": 1,
      "value": true
    }
  }
}
```

The `value` is a boolean: `true` to approve, `false` to reject.

#### NeedContacts Response

```json
{
  "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

```json
{
  "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`      | `AskNextAction`                                | 1       | `{ choice, contact?, instructions? }`                   |
| `NeedUserInput`      | `DecideOutgoingEmailDraft` for a pending draft | 1       | `{ communicationId, decision }`, or text |

## Validation Rules

The API enforces strict validation on interaction messages:

- `NeedUserInput` accepts text, or `DecideOutgoingEmailDraft` when it carries a
  `pendingCommunicationId`
- **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.

To approve an email prepared by the task, send a structured interaction with the same
`communicationId` and `decision: "send"`. Use `decision: "discard"` to abandon it. A successful POST
proves that the decision was recorded; read the communication and verify
`awaitingApproval: false` before treating the email as armed. Free text such as "yes, send it"
never approves the draft.

## Account Task Status Values

| Status               | Description                                                                                                                                                            |
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Open`               | Task created, awaiting processing                                                                                                                                      |
| `InProgress`         | Agent is processing the last reply. If that processing fails, the task returns to `UserActionRequired` within 3 hours with an agent message, and accepts a reply again |
| `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

```json
{
  "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

```json
{
  "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

```json
{
  "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. An announced payment requires an
explicit decision, which can now be recorded from its linked task as described below.

The review closes automatically when its linked invoices are settled. For a declaration with no
invoice references, it also closes if the account has at least one invoice and every invoice is
individually settled. The decision is `InvoicesSettled`, without implicitly confirming payment
receipt. An account with no invoices or a missing invoice reference still requires review. This
rule applies across all three surfaces.

A text reply in a linked payment-review task can carry the user's decision: receipt
(`PaymentReceived`), non-receipt found after checking (`PaymentNotReceived`), or resuming reminders
without ruling on the payment (`ResumeAuthorized`). The decision is recorded on behalf of the
replying user, using the same authorization and accounting checks as the account operation, and
needs no repeat in the account form. Processing is asynchronous: read the task and payment
declarations again for the result. If the linked invoices remain open after a receipt, the receipt
is recorded and reminders stay held until reconciliation: while the settlement grace runs, the task
is closed; once it has passed, a task asks for the invoices to be updated or for reminders to
resume. A canceled or closed review task is not recreated, except once at the end of that grace if
it was opened before it. A closure request alone, a claim relayed
from the debtor, a promise or a request to keep waiting decide nothing, and this interaction never
changes invoices. The webapp, public API and MCP share this behavior; `CreditApplied`,
`InvoicesSettled` and `Withdrawn` use the explicit account operations.

## 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`: record `sourceRef`, `declaredAt`, and optionally `paymentDate`, `amount`, `currency`,
  `declaredStage`, `invoiceNumbers`, `proofDocumentAttached`. Reuse `sourceRef` when you retry.
- `POST /{declarationId}/resolve`: supply a `resolution`, and an optional `note`. Without a note, a
  factual note derived from the decision is recorded under your name.

A check task carries `extraData.paymentDeclarationId`, the identity of the declaration it checks
(opaque, not necessarily a UUID). Every declaration carries `reviewTaskStatus` (`open`, `closed`,
`canceled`, `unknown`, `null` without a task) and `resolvedByUserFullName`. A decision next to a check
still `open` is recorded: its closure is being retried, and replaying the same decision retries it.

A payment already held open is not recorded twice. When a new declaration names exactly the same
invoices as an unresolved one, comes from another `sourceRef`, and no amount, currency or payment
date disagrees (equal, or given on one side only), it is folded into the existing declaration
instead of creating a second one. The existing declaration fills in what it did not know, takes the
stage of the latest announcement, and lists the new one, as it was sent, under `restatements`. The
response then holds your `sourceRef` inside that list rather than as a declaration of its own, and
resolving with it decides the declaration it was folded into. A different amount, currency, payment
date or set of invoices, or a declaration already decided, still creates a new declaration.

`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`, `PartiallyReceived`, `CreditApplied`, `InvoicesSettled`,
`PaymentNotReceived`, `Withdrawn`, `ResumeAuthorized`. `PartiallyReceived` confirms part of the
payment arrived: first set the total paid of each invoice concerned (`PUT
/invoices/{invoiceId}/paid-amount` with `expectedPaidAmount`, the amount you read), then record the
decision. It changes no invoice, requires at least one partly paid invoice and a remaining balance,
and follow-ups resume on the balance still owed. A confirmed receipt (`PaymentReceived`) concerns the announced amount, including an installment. It completes the review even with an outstanding balance when every named invoice is identified, recorded payments cover the announced amount in the account currency, and no competing announcement makes the allocation ambiguous. Otherwise, `paymentConfirmedAt` is recorded while reconciliation remains pending. No invoice amount changes. `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.

A person's decision is final. Replaying the same decision changes nothing and returns the list
unchanged; sending a different one answers `400` with the code
`app-dunning.payment-declaration.already-resolved`, whose `details` field carries `declarationId`,
`resolution`, `resolvedAt` and `resolvedBy`. Show that decision rather than the failure.

One exception, and only one: the decision the billing data took on its own. Settling the invoices of
a declaration decides it as `InvoicesSettled` within the second, with `resolvedBy` set to
`accounting`. That decision answers whether the invoices are cleared, never whether the money
arrived, and an explicit user decision replaces it without an error. It is the only path by which
`PaymentReceived` is ever recorded on such a declaration, and `PaymentReceived` is the only decision
that lets the next reminder thank the debtor. The replacement goes one way: the automatic review
never revisits a person's decision, and the invoice verification applies to a replacement exactly as
to a first decision. Re-read the declaration before offering a decision to a user.

## Next Steps

- **[Getting Started](/en/guides/getting-started/)** - Integration quick start
- **[OAuth Scopes](/en/guides/scopes/)** - Required permissions
- **[Pagination & Filtering](/en/guides/pagination/)** - Cursors and the `filters` parameter
- **[Rate Limiting](/en/guides/rate-limiting/)** - Avoid hitting limits
- **[API Reference](/en/api-reference/)** - Full endpoint documentation

## Support

Questions about account tasks and interactions?  
Contact us via the [website contact form](https://billabex.com).

## Confirm reminder recipients

For MCP, pass only `accountTaskId` and `value` (plus optional `attachments`). The examples below show the public API body; MCP fixes `schema` and `schemaVersion` itself.

A `NeedUserInput` task with `extraData.idempotencyKey = "dunning-recipient-review"` blocks automatic reminders when more than five contacts with valid email addresses are enabled without a confirmed selection. Read the current account contacts, then use the existing structured interaction endpoint or MCP `confirm-account-task-recipients` tool:

```json
{
  "type": "Structured",
  "schema": "ConfirmDunningRecipients",
  "schemaVersion": 1,
  "value": {
    "reviewedContacts": [
      { "id": "contact-id", "email": "contact@example.com" }
    ],
    "selectedContactIds": ["contact-id"],
    "confirmLargeSelection": false
  }
}
```

`reviewedContacts` contains all enabled contacts with valid emails that the user reviewed, including their current addresses. `selectedContactIds` must be a nonempty subset with no duplicates. More than five selected contacts requires `confirmLargeSelection: true`. Unselected reviewed contacts are disabled, not deleted. The task closes after the selection is saved. If a contact is added or an address changes before submission, read the contacts again. Text replies and generic task closure cannot confirm this selection.

## Contact designation and reminder resumption

For a `NeedUserInput` task with `extraData.idempotencyKey = "dunning-recipient-routing"`,
a text reply such as `hello@example.com` designates the contact for future reminders.
The agent adds or reuses that contact before closing the task. Disabled contacts and rejected
addresses remain protected; an unsuccessful write keeps the task open. Providing an address
alone does not request an immediate email or authorize resuming paused reminders.

A task with key `dunning-resume-confirmation` asks whether reminders should resume. Reply with a
structured interaction, through the structured-interaction endpoint or the MCP
`confirm-account-task-resume` tool:

```json
{
  "type": "Structured",
  "schema": "ConfirmDunningResume",
  "schemaVersion": 1,
  "value": { "decision": "resume" }
}
```

`decision` is `resume` or `keep_paused`. The schema is rejected on any other task. The exact text
replies `Resume reminders`, `Keep reminders paused`, `Reprendre les relances` and
`Maintenir la suspension` remain supported for compatibility; any other text is read by the
agent. The task closes only after the corresponding account action succeeds.

## Keep an existing payment schedule

For `ApprovePaymentArrangement`, send `schema: "DecidePaymentArrangement"`,
`schemaVersion: 1`, `value: { "choice": "keep_existing" }` through the structured
interaction endpoint or MCP tool. Omit both `invoiceIds` and `installments`.
The server rereads the entire frozen scope: at least one outstanding invoice must
still have unpaid installments. It closes the task without modifying invoices or
sending an email. Only the task's own `payment-plan` pause is lifted.
`grant` remains available with `invoiceIds` and `installments`, and `refuse` without them.

## Platform rejections

A `NeedUserInput` task can expose `extraData.platformRejection`: `platform` (`Basware` or
`Mailinblack`), `reason`, `sender`, `destination`, `expiresAt`, `sourceEmailIds`,
`outgoingEmailIds`, `resolution`, and `resolutionNote`.
Only this account's sender/destination pair is restricted. Ordinary tasks keep their global scope.

A `Mailinblack` task means the destination's mailbox is protected by a security gateway that holds
the reminder until the sender is validated by a person at the recipient. The agent never follows
the validation link. The destination is left out of automatic reminders until `expiresAt`, five
business days after the first challenge of the episode; reminders then resume even if the task is
still open, and a later challenge does not move that date. Basware tasks have no `expiresAt`.

Use `POST /api/public/v1/account-tasks/{accountTaskId}/resolve-platform-rejection` with OAuth
`tasks:all`, or MCP `resolve-platform-rejection-task` with `mcp:write`:

```json
{ "resolution": "exclude", "note": "Use the confirmed accounting contact." }
```

MCP also requires `accountTaskId`. `exclude` retains the restriction after closure; `retry`
explicitly permits another attempt after registration is fixed. For a `Mailinblack` task, `retry`
means the agent address is now allowed and also asks for the next reminder to be planned
(a replanning, not an immediate send). A note of 1 to 2,000 characters
and an authorized user are required. This sends no email and lifts no other pause. Generic
closure, cancellation, deletion and free-text replies are refused for this task. Incoming
email operations expose `PlatformRejected` and `platformRejection` metadata including
`authenticated` and the correlated `outgoingEmailId`, when available. This is neither an SMTP
failure nor a human reply. Ambiguous notifications remain visible without an automatic block.

MCP exposes the same structure at `task.platformRejection`.

## Explicitly ignore a BODACC alert

An `Unknown` task with `extraData.idempotencyKey = bodacc-alert:<siren>:<noticeId>` concerns one
notice. Acknowledgement (`UnknownV1`, `value: true`) closes the task and preserves the BODACC
pause. `GET /api/public/v1/accounts/:accountId` remains available after closure and exposes
`bodaccSiren`, `bodaccNoticeId`, `bodaccStatus`, dates, notice URL, `canIgnoreBodaccAlert` and
`bodaccAlertOverride` (state, justification, authenticated actor, channel and timestamps).

After an explicit user decision, `POST /api/public/v1/accounts/:accountId/bodacc-alert/ignore`,
scope `dunning:manage`, accepts:

```json
{
  "siren": "123456789",
  "noticeId": "A202600011",
  "status": "Receivership",
  "confirmed": true,
  "justification": "The debtor document confirms a different identity."
}
```

Use the scope actually presented to the user, not this synthetic example. Optional `taskId` must
identify a task for that alert. Justification must be nonempty after trimming, at most 2000
characters. The server derives the actor from authentication and rejects unknown fields. A stale
scope returns `409`: reload the account before obtaining another decision.

The response includes `account`, `dunningStatus`, `blockingTasks`, `closedTaskIds`, `applied`,
`retryRequired` and `error`. A persisted decision may require retrying the same request after a
resume or closure failure. Other pauses, waits and blocking tasks remain effective. The decision
neither validates the legality of reminders nor guarantees an immediate send. `Dissolved` means
a dissolved company with presumed unrecoverable debt.

`POST /api/public/v1/accounts/:accountId/bodacc-alert/revoke` accepts the same scope and
`confirmed: true`, without justification or task, and reconciles the block against the current
finding. Monitoring continues and a new notice invalidates the previous decision. A historical
check without `bodaccNoticeId` must be checked again first. Durable identity correction is
separate: edit a manual account or its billing source for a connector-managed account.

### Confirm receipt for missing historical references

For `POST /api/public/v1/accounts/{accountId}/payment-declarations/{declarationId}/resolve`
or `resolve-account-payment-declaration`, use a user identity, `PaymentReceived`,
`historicalInvoicesOutsideBillabex: true`, and a required personal `note` (1 to 4000 characters,
not a default decision note). Explain how full receipt and the historical scope were verified,
including possible number variants. Service accounts cannot take this decision.

Responses expose `invoiceNumbersMissingFromAccount`, `historicalReceiptEligible`, and
`historicalReceiptAccountInvoices` (current invoice numbers, dates and amounts for review).
Submission rechecks eligibility: no invoices identified at recording, explicit references,
no matching invoice on the account or organization, successful reads, and no prepared email
awaiting approval on the review task. A previous different decision, including ordinary
`PaymentReceived`, returns the existing conflict.

The terminal result carries `historicalInvoicesOutsideBillabex: true`, the author, date and note.
It changes no invoice. Identical retries preserve the audit and retry review closure only.
Other open declarations continue holding reminders; otherwise ordinary follow-ups may resume
under their usual guards. This action sends no email. Missing references never prove receipt.

### Preparation, decisions and asynchronous results

A text message can request a reminder, designate a contact and select an invoice by its exact
reference or civil due date. The agent prepares a draft for review without arming it. Without
an email thread, a single eligible contact is proposed; several contacts require a choice.
Financial documents are attached only when explicitly designated. Attachments remain removable
and are checked again when the email is approved.

The structured `DecideOutgoingEmailDraft` v1 interaction remains the way to approve or discard a
draft. Once the interaction is accepted, read the task and communication until the business
receipt appears. A confirmed discard removes the draft; an absent draft only means it is no
longer available. An unknown result requires checking account communications before another
action and must never trigger an automatic second send. Other task requirements and account
pauses remain in force.

### Sélection durable et tâches de routage

`ConfirmDunningRecipients` v1 répond aussi à `dunning-recipient-routing`. Une réponse texte reste
acceptée pour préciser un nom ou une nouvelle adresse ; une clôture vide ne confirme personne.
Les destinataires sélectionnés doivent déjà être actifs et porter une adresse valide dans une
confirmation structurée. `confirmLargeSelection` reste obligatoire au-delà de cinq contacts.

La lecture d'un compte expose `dunningRecipientSelection` avec `contacts`, `confirmedAt` et
`decision` (nullable, puis `taskId`, `interactionId`, `userId`, `decidedAt`). Un ancien choix peut
ne pas avoir de provenance. Le champ `excludedFromDunning` d'un contact vaut `selection` ou
`null`, jamais une permission d'écrire : seul le serveur détermine ce motif. Les outils MCP
existants exposent les mêmes données, sans nouvel outil. Un email ponctuel demandé dans une
tâche ne change pas automatiquement les destinataires de la cadence.

La sélection de destinataires expose aussi `redirection` lorsqu'un débiteur a explicitement
transmis le suivi à son successeur : identifiant et date de l'email, ancien et nouveau
destinataires. Cette preuve système ne se présente pas comme une décision d'un utilisateur.

Une réponse à une tâche peut préciser l'horaire de la prochaine relance **automatique**, avec
un fuseau IANA explicite, par exemple « prochaine relance automatique le 1 juillet 2027 à
08:00, Europe/Paris ». La balance expose `nextCommunicationReminderRequest`, sa date cible,
son fuseau et sa source. Le reçu confirme l'enregistrement de la consigne, pas la livraison.
Une instruction visant seulement un brouillon ponctuel n'utilise pas cette capacité.

### Invoices affected by a condition

A `NeedUserInput` task can expose `conditionKind` and `affectedInvoices` in its
`extraData`. The latter contains two paired lists: `invoiceIds` for identities and
`invoiceNumbers` for display. An absent or null value preserves account-wide scope;
an empty list is invalid. These fields are system-owned. Their presence alone does
not prove that invoice holds have been applied: also read the holders in each invoice's
`dunningBlock`.

The MCP `get-account-task` tool exposes these two fields directly under `task`.

---

Billabex developer portal. OpenAPI specification: https://developer.billabex.com/openapi.json.
Agent instructions: https://developer.billabex.com/llms.txt. Complete documentation: https://developer.billabex.com/llms-full.txt.
