# List invoice replies API Reference

Returns the replies of the customers of the company about its invoices, on every channel,
most recent first, each with a masked `excerpt`. Read one with its `text` in
`GET .../invoice-replies/{reply_id}`.


## GET /v1/companies/{company_id}/invoice-replies

**List invoice replies**

Returns the replies of the customers of the company about its invoices, on every channel,
most recent first, each with a masked `excerpt`. Read one with its `text` in
`GET .../invoice-replies/{reply_id}`.

### Authentication

Accepts any of:

- `ApiKeyAuth` (HTTP bearer, token format `beel_sk_*`)

### Parameters

- **company_id** (required) in path `string`: Unique identifier (UUID) of the company the operation acts on: its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the `BeeL-Active-Company` header plays no part. A company you do not reach answers `403`, and so does a company that does not exist, so the existence of a company in another account is never disclosed.
- **page** (optional) in query `integer` (default: 1): Page number, starting at 1. The response echoes it back as `pagination.current_page`.
- **limit** (optional) in query `integer` (default: 20): How many items to return per page. The response echoes it back as `pagination.items_per_page`.
- **invoice_id** (optional) in query `string`: Keep only the replies about this invoice.
- **connection_id** (optional) in query `string`: Keep only the replies that arrived through this connection. The replies registered with `POST .../invoice-replies` have no connection, so they never match.
- **customer_id** (optional) in query `string`: Keep only the replies about invoices of this customer.
- **channel** (optional) in query `array`: Keep only the replies received on these channels. Accepts a comma-separated list, for example `channel=WHATSAPP,SMS`. A value BeeL. does not know matches nothing.
- **from** (optional) in query `string`: Keep only the replies received at or after this instant.
- **to** (optional) in query `string`: Keep only the replies received at or before this instant.
- **sort_order** (optional) in query (default: "desc"): Direction of the sort by `received_at`. Ties are broken by `id`.

### Responses

#### 200: Replies of the company

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: true)
- **data** (required): The payload. An object for a single resource; an object holding the named collection (and its `pagination`) for a listing. Never a bare array at this level in v1.
- **meta** `ResponseMeta`
- **data** (required) `object`: No description
  - **replies** (required) `array[InvoiceReplySummary]`: No description
  - **pagination** (required) `Pagination`

**Example:**

```json
{
  "success": true,
  "data": {
    "replies": [
      {
        "id": "3f0c9a1e-6b2d-4c8f-a1e5-7d9b0c2e4f61",
        "channel": "EMAIL",
        "authenticated_by": "REPLY_TOKEN",
        "connection_id": "8c3e1f7a-2b4d-4e6f-9a1c-5d7b3e9f2a41",
        "invoice_id": "b71d5e2a-9c3f-4a7b-8e10-2f6d4c8a9b13",
        "invoice_number": "F2026-0142",
        "customer_id": "7a2c9e14-3b5d-4f6a-8c0e-1d2b3a4c5e6f",
        "status": "RECEIVED",
        "received_at": "2026-11-03T09:12:44Z",
        "sender": {
          "address": "[EMAIL]"
        },
        "subject": "RE: Factura F2026-0142",
        "excerpt": "Hola, el CIF que aparece no es el nuestro, es [NIF]. ¿Podéis corregirla?",
        "attachments": []
      }
    ],
    "pagination": {
      "current_page": 1,
      "total_pages": 1,
      "total_items": 1,
      "items_per_page": 20,
      "has_next": false,
      "has_previous": false
    }
  }
}
```

#### 400: The request target could not be read as declared, so nothing was looked up. Three causes,
all named in `error.details`: a path or query parameter whose value does not parse as its
type (`VALIDATION_ERROR` — a UUID that is not a UUID, an unknown enum value, an empty
element in a list such as `status=ISSUED,`), a required parameter that was not sent
(`MISSING_PARAMETER`), or a header that is meant to carry an identifier and does not
(`ACTIVE_COMPANY_HEADER_INVALID`). A list parameter sent entirely empty (`status=`) is
not an error: it is the same as omitting it.

The fault is in the target, not in the content — which is what `422` is defined over.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "The parameter 'invoice_id' has an invalid type. Expected: UUID.",
    "details": {
      "field": "invoice_id",
      "invalid_value": "deliveries",
      "expected_format": "UUID"
    }
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 401: Missing or invalid authentication. Like every other error, `message`/`detail` is
localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English when the
header is missing or asks for none of those.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Authentication is required to access this resource"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 403: One of:
- `ACTIVE_COMPANY_NOT_ACCESSIBLE`: your account does not own or manage this company.
- `INSUFFICIENT_SCOPE`: the credential lacks `invoice-replies:read`, a privileged scope
  that a key created without selecting scopes does not hold.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

#### 422: A parameter parsed as its declared type but its value is not one the operation admits:
a `limit` outside its range (`limit=0`, `limit=101`), a `page` under 1, a `cursor` that is
not one this API issued, more keys than the `metadata` filter accepts, a value outside the
supported set. The query was rejected whole and nothing was read.

Where a `400` says the target could not be understood, this says it was understood and
refused. Fix the value — retrying the same query answers the same way.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Validation constraint violation.",
    "details": {
      "limit": "must be greater than or equal to 1"
    }
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 429: Rate limit exceeded. `RATE_LIMIT_EXCEEDED` when the request rate of your credential is
over its limit; `TOO_MANY_REQUESTS` when an email the operation sends is held back because
too many went out to that recipient in a short time. Either way, retry after the
`Retry-After` seconds.

`QUOTA_EXCEEDED` is the other `429`: the operation would go over a quota of your plan
for the current period (issued invoices, for instance). Retrying does not help until the
quota resets or the plan changes. `error.details` carries `feature_code`,
`current_usage`, `quota_limit` and `reset_date`, and `RateLimit-Reset` the seconds until
the reset when it is known.


**Headers:**

- `Retry-After` `integer`: Seconds until the rate limit resets
- `RateLimit-Limit` `integer`: Maximum requests allowed in the window
- `RateLimit-Remaining` `integer`: Remaining requests in the current window
- `RateLimit-Reset` `integer`: Seconds until the current window resets

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests. Please try again in 60 seconds."
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### 500: Internal server error

**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Internal server error"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

#### default: Any status code the operation does not list above. Every operation declares it, so a
generated client always has a branch to fall into and never loses the cause of a failure
it did not anticipate.

This is where the transport-level answers land — `405`, `406`, `415` and `429` — together
with any status a future version of the API starts returning. All of them carry the same
error envelope as the codes listed explicitly, so `error.code` is what tells them apart:
switching on the status code alone is not enough. See «Transport-level errors» in the
API description for when each one is produced.

A `402` carrying `SUBSCRIPTION_REQUIRED` lands here when the account has no active
subscription. `error.details` says why and what to do: `current_status` (absent if it
never had one), `required_permission` and `action`.

A `503` carrying `SERVICE_UNAVAILABLE` lands here too, with a `Retry-After` header: a
dependency of BeeL itself (the database, the email provider) was momentarily
unavailable and nothing was done. Retry after those seconds.

A `502` carrying `EXTERNAL_SERVICE_ERROR` also lands here: an outbound integration the
operation depends on failed or did not answer in time. It is a transient condition — retry
with the same `Idempotency-Key` where the operation accepts one.

One exception to the envelope: a failure of the network edge that never reaches the
application (`502`, `503`, `504`, `524`) is generated by Cloudflare and its body is not
BeeL's — it may not even be JSON. Treat those as "no answer", and retry.


**Content `application/json`:**

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

**Example:**

```json
{
  "success": false,
  "error": {
    "code": "UNSUPPORTED_MEDIA_TYPE",
    "message": "Unsupported media type: text/plain. Supported: application/json"
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
```

---

# Related Schema Definitions

## SuccessResponse

The envelope every successful JSON response of the BeeL. API is wrapped in. There are no
bare resources in v1 and none are planned: the payload always hangs off `data`.

- A **single resource** is an object in `data`.
- A **collection** hangs off a named key inside `data`, together with its `pagination`,
  also inside `data` — `data: {invoices: [...], pagination: {...}}`.
- A collection carries `pagination` unless its operation declares itself a **closed
  catalogue**: a fixed, bounded list with nothing to page through. The declaration is
  explicit in the operation; a missing `pagination` is never something to infer.
- `GET /v1/accounts` pages by cursor (`data: {accounts: [...], next_cursor}`). It is a
  documented variant of pagination, not another envelope.

Putting the array straight into `data` with `pagination` as its sibling is the shape a v2
would adopt; v1 is not being flipped to it.

- **success** (required) `boolean`: No description (example: true)
- **data** (required): The payload. An object for a single resource; an object holding the named collection (and its `pagination`) for a listing. Never a bare array at this level in v1.
- **meta** `ResponseMeta`

## ResponseMeta

- **timestamp** `string` (date-time): No description (example: "2025-01-15T10:30:00Z")
- **request_id** `string`: No description (example: "4bf92f3577b34da6a3ce929d0e0e4736")

## InvoiceReplySummary

A reply as listed, with the same masking as the webhook: masked `excerpt`, `subject` and `sender.address`, no sender name, no `external_id`, and attachments without file names.

- **id** (required) `string` (uuid): No description (example: "3f0c9a1e-6b2d-4c8f-a1e5-7d9b0c2e4f61")
- **channel** (required) `InvoiceReplyChannel`: Where the customer wrote: the medium, not the `provider` of the connection that brought it (an email is `EMAIL` whatever received it). More values may be added: treat an unknown one like `OTHER`. - `EMAIL`: an email; `sender.address` is an email address. - `WHATSAPP`: `sender.address` is a phone number in E.164 format. - `TELEGRAM`: `sender.address` is the user identifier on Telegram. - `SLACK`: `sender.address` is the user identifier on Slack. - `SMS`: `sender.address` is a phone number in E.164 format. - `OTHER`: any other channel, such as the chat of your product.
- **authenticated_by** (required) `InvoiceReplyAuthentication`: What links the message to its invoice, never who wrote it (`sender` is not verified). More values may be added: treat an unknown one like `API_KEY`. - `REPLY_TOKEN`: an answer to the email of one invoice, sent to the address unique to that email. - `API_KEY`: registered by an integrator with `POST .../invoice-replies`, who chose the invoice.
- **connection_id** (required) `string` (uuid): The connection of the company the message arrived through (`.../connections/{connection_id}`). `null` when it was registered with `POST .../invoice-replies`: you received it, not BeeL. (example: "8c3e1f7a-2b4d-4e6f-9a1c-5d7b3e9f2a41")
- **invoice_id** (required) `string` (uuid): The invoice the message is about. Every reply has one today; channels added in the future may store a reply without it, so handle `null`. (example: "b71d5e2a-9c3f-4a7b-8e10-2f6d4c8a9b13")
- **invoice_number** (required) `string`: Number of that invoice; `null` when `invoice_id` is `null`. (example: "F2026-0142")
- **customer_id** (required) `string` (uuid): The customer of the invoice; `null` when the invoice has a one-off recipient or `invoice_id` is `null`. (example: "7a2c9e14-3b5d-4f6a-8c0e-1d2b3a4c5e6f")
- **status** (required) `InvoiceReplyStatus`: Where the reply is in its lifecycle. More values will be added: treat an unknown value as an open reply. - `RECEIVED`: the reply is stored.
- **received_at** (required) `string` (date-time): When the message was received, in UTC: when BeeL. received it, or the moment given when it was registered. (example: "2026-11-03T09:12:44Z")
- **subject** (required) `string`: Subject of the message, with tax IDs, IBANs, email addresses and phone numbers masked. `null` when the message has none, as on every channel but `EMAIL`, and once the reply is past its retention of 12 months after `received_at`. (example: "RE: Factura F2026-0142")
- **excerpt** (required) `string`: First paragraph of the text of the message, without the quoted original email, with tax IDs, IBANs, email addresses and phone numbers replaced by `[NIF]`, `[IBAN]`, `[EMAIL]` and `[PHONE]`. `null` when the message has no text, and once the reply is past its retention of 12 months after `received_at`. (example: "Hola, el CIF que aparece no es el nuestro, es [NIF]. ¿Podéis corregirla?")
- **sender** (required) `InvoiceReplySummarySender`: Who the message says it comes from, with the same masking as the webhook: the address only, masked by type. It is not verified: never use it alone to authorize a change. The name is in the detail.
- **attachments** (required) `array[InvoiceReplyAttachmentSummary]`: Attachments of the message. Empty when it has none; `null` once the reply is past its retention of 12 months after `received_at`.

## Pagination

- **current_page** (required) `integer`: No description (example: 1)
- **total_pages** (required) `integer`: How many pages the query has. An empty collection has **one** page — the first one, empty — so the smallest value a canonical route returns is `1`. The deprecated flat aliases (`/v1/invoices`, `/v1/customers`, …) answer `0` for that same empty collection, and `has_next`/`has_previous` follow suit. They were left as they were on purpose: a client that reads `total_pages == 0` as "no results" would break if it changed under it. Do not compare the two families of routes field by field; read `total_items` if what you want to know is whether anything came back. (example: 5)
- **total_items** (required) `integer`: No description (example: 87)
- **items_per_page** (required) `integer`: No description (example: 20)
- **has_next** `boolean`: No description
- **has_previous** `boolean`: No description

## ErrorResponse

Error response shared by all BeeL. APIs.

The payload carries **two contracts at once** (additive, non-breaking):

- **Legacy** (`success`, `error.{code,message,details}`, `meta`) — kept
  intact for existing consumers.
- **RFC 9457** (`type`, `title`, `detail`, `instance`) — new fields
  for integrators following Problem Details for HTTP APIs. The
  `type` URI is the stable, shareable link to the error's
  documentation page (e.g. `https://docs.beel.es/errors/{code}`).

Future migration: the legacy fields will be deprecated via
`Deprecation`/`Sunset` headers after a sufficient adoption window,
and the response Content-Type will move to
`application/problem+json`.

- **success** (required) `boolean`: No description (example: false)
- **error** (required) `ErrorDetail`
- **meta** `ResponseMeta`
- **type** `string` (uri): Stable URI that identifies the problem type and where the integrator will find its documentation. (example: "https://docs.beel.es/errors/INVOICE_NO_LINES")
- **title** `string`: Short summary of the problem type. Stable between occurrences of the same `type`. (example: "INVOICE_NO_LINES")
- **detail** `string`: Specific message for this occurrence, localized according to `Accept-Language` (`es`, `en`, `ca`); defaults to English. Matches the legacy `error.message`. (example: "The invoice must have at least one line")
- **instance** `string` (uri-reference): URI that identifies the specific occurrence of the problem — typically the path of the affected resource. (example: "/v1/invoices/abc-123")

## ErrorDetail

- **code** (required) `string`: No description (example: "VALIDATION_ERROR")
- **message** (required) `string`: No description (example: "The provided data is not valid")
- **details** `object`: No description (example: {"field":"specific error message"})

## InvoiceReplyBase

What every view of a reply carries. 12 months after `received_at`, its content, subject,
sender, attachments and `external_id` become `null`; the rest stays.

- **id** (required) `string` (uuid): No description (example: "3f0c9a1e-6b2d-4c8f-a1e5-7d9b0c2e4f61")
- **channel** (required) `InvoiceReplyChannel`: Where the customer wrote: the medium, not the `provider` of the connection that brought it (an email is `EMAIL` whatever received it). More values may be added: treat an unknown one like `OTHER`. - `EMAIL`: an email; `sender.address` is an email address. - `WHATSAPP`: `sender.address` is a phone number in E.164 format. - `TELEGRAM`: `sender.address` is the user identifier on Telegram. - `SLACK`: `sender.address` is the user identifier on Slack. - `SMS`: `sender.address` is a phone number in E.164 format. - `OTHER`: any other channel, such as the chat of your product.
- **authenticated_by** (required) `InvoiceReplyAuthentication`: What links the message to its invoice, never who wrote it (`sender` is not verified). More values may be added: treat an unknown one like `API_KEY`. - `REPLY_TOKEN`: an answer to the email of one invoice, sent to the address unique to that email. - `API_KEY`: registered by an integrator with `POST .../invoice-replies`, who chose the invoice.
- **connection_id** (required) `string` (uuid): The connection of the company the message arrived through (`.../connections/{connection_id}`). `null` when it was registered with `POST .../invoice-replies`: you received it, not BeeL. (example: "8c3e1f7a-2b4d-4e6f-9a1c-5d7b3e9f2a41")
- **invoice_id** (required) `string` (uuid): The invoice the message is about. Every reply has one today; channels added in the future may store a reply without it, so handle `null`. (example: "b71d5e2a-9c3f-4a7b-8e10-2f6d4c8a9b13")
- **invoice_number** (required) `string`: Number of that invoice; `null` when `invoice_id` is `null`. (example: "F2026-0142")
- **customer_id** (required) `string` (uuid): The customer of the invoice; `null` when the invoice has a one-off recipient or `invoice_id` is `null`. (example: "7a2c9e14-3b5d-4f6a-8c0e-1d2b3a4c5e6f")
- **status** (required) `InvoiceReplyStatus`: Where the reply is in its lifecycle. More values will be added: treat an unknown value as an open reply. - `RECEIVED`: the reply is stored.
- **received_at** (required) `string` (date-time): When the message was received, in UTC: when BeeL. received it, or the moment given when it was registered. (example: "2026-11-03T09:12:44Z")
- **subject** (required) `string`: Subject of the message, with tax IDs, IBANs, email addresses and phone numbers masked. `null` when the message has none, as on every channel but `EMAIL`, and once the reply is past its retention of 12 months after `received_at`. (example: "RE: Factura F2026-0142")
- **excerpt** (required) `string`: First paragraph of the text of the message, without the quoted original email, with tax IDs, IBANs, email addresses and phone numbers replaced by `[NIF]`, `[IBAN]`, `[EMAIL]` and `[PHONE]`. `null` when the message has no text, and once the reply is past its retention of 12 months after `received_at`. (example: "Hola, el CIF que aparece no es el nuestro, es [NIF]. ¿Podéis corregirla?")

## InvoiceReplyChannel

Where the customer wrote: the medium, not the `provider` of the connection that brought
it (an email is `EMAIL` whatever received it). More values may be added: treat an
unknown one like `OTHER`.

- `EMAIL`: an email; `sender.address` is an email address.
- `WHATSAPP`: `sender.address` is a phone number in E.164 format.
- `TELEGRAM`: `sender.address` is the user identifier on Telegram.
- `SLACK`: `sender.address` is the user identifier on Slack.
- `SMS`: `sender.address` is a phone number in E.164 format.
- `OTHER`: any other channel, such as the chat of your product.

Type: `string`

## InvoiceReplyAuthentication

What links the message to its invoice, never who wrote it (`sender` is not verified).
More values may be added: treat an unknown one like `API_KEY`.

- `REPLY_TOKEN`: an answer to the email of one invoice, sent to the address unique to
  that email.
- `API_KEY`: registered by an integrator with `POST .../invoice-replies`, who chose the
  invoice.

Type: `string`

## InvoiceReplyStatus

Where the reply is in its lifecycle. More values will be added: treat an unknown value as
an open reply.

- `RECEIVED`: the reply is stored.

Type: `string`

## InvoiceReplySummarySender

Who the message says it comes from, with the same masking as the webhook: the address only, masked by type. It is not verified: never use it alone to authorize a change. The name is in the detail.

- **address** (required) `string`: Masked as `[EMAIL]`, `[PHONE]` or `[ID]` according to `channel`. `null` once the reply is past its retention of 12 months after `received_at`. (example: "[EMAIL]")

## InvoiceReplyAttachmentSummary

An attachment of the reply. BeeL. does not store the file: a temporary download link is requested when you read the attachment at its `url`.

- **id** (required) `string` (uuid): Identifier of the attachment, stable across reads. (example: "5b0e2c7a-91d4-4f3e-8a6b-2c1d9e0f7a34")
- **url** (required) `string` (uri-reference): Relative URL of the attachment resource. Relative to the API base URL (e.g., https://app.beel.es/api). Note it is a link to a link: reading it returns a temporary download link, or `download_url: null` when there is no file or it has expired. (example: "/v1/companies/9f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f/invoice-replies/3f0c9a1e-6b2d-4c8f-a1e5-7d9b0c2e4f61/attachments/5b0e2c7a-91d4-4f3e-8a6b-2c1d9e0f7a34")
- **file_expires_at** (required) `string` (date-time): Estimate of when the file stops being available. `null` when the attachment has no downloadable file, as with those registered with `POST .../invoice-replies`. (example: "2026-11-18T10:15:00Z")
- **content_type** (required) `string`: Media type declared by the channel. (example: "application/pdf")
- **size_bytes** (required) `integer` (int64): Size of the file; `null` when the channel does not report it. (example: 48213)


---

Full OpenAPI spec: https://docs.beel.es/api/openapi