# Filtering by metadata

Attach arbitrary key/value pairs to invoices and query them back with `?metadata[key]=value`.

Every invoice can carry a `metadata` object — an arbitrary string-to-string map you control. BeeL. stores it as-is, returns it on every read, and lets you filter the invoice list by any combination of keys. This is the right hook for **reconciling BeeL. invoices with records in your own system** (orders, shopping carts, internal IDs, tenants) without polluting the fiscal data.

## Attaching metadata at creation

Pass a `metadata` object inside the create body:

```bash
curl -X POST "https://app.beel.es/api/v1/companies/{company_id}/invoices" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "STANDARD",
    "operation_date": "2026-05-20",
    "recipient": { "customer_id": "550e8400-e29b-41d4-a716-446655440000" },
    "lines": [
      { "description": "Consulting", "quantity": 1, "unit_price": 500, "discount_percentage": 0,
        "main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" } }
    ],
    "metadata": {
      "external_order_id": "ORD-42",
      "tenant": "acme",
      "source": "shopify"
    }
  }'
```

Metadata is **opaque to BeeL** — it never affects taxes, totals, VeriFactu submission or PDF rendering. It travels with the invoice through every state transition and survives corrective invoices.

## Filtering the list

`GET /v1/companies/{company_id}/invoices` accepts a `metadata` query parameter using the **deepObject** style (the same style Stripe uses). Each key/value pair becomes a separate query parameter with bracket notation:

```bash
curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?metadata[external_order_id]=ORD-42" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

The API accepts the brackets unencoded. The `-g` (`--globoff`) flag is what lets `curl` send them literally — without it curl treats `[` and `]` as glob characters and fails with `curl: (3) bad range in URL`. HTTP client libraries and SDKs encode them for you automatically, so they need no special handling.

### Combining multiple keys (AND)

Repeat the parameter — the API requires **all** keys to match (logical AND):

```bash
curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?metadata[tenant]=acme&metadata[source]=shopify" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

There is no OR. If you need it, run separate requests and merge client-side.

### Combining with other filters

The `metadata` filter composes with every other query parameter — status, date ranges, sorting, pagination:

```bash
curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?metadata[tenant]=acme&status=ISSUED&sort_by=issue_date&sort_order=desc&limit=50" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

Unlike `metadata`, the `status` filter does accept several values: pass them
comma-separated and the list matches any of them, in one paginated response. An empty element
(`status=ISSUED,` or `status=DRAFT,,ISSUED`) is rejected with
`400` [`VALIDATION_ERROR`](/errors/VALIDATION_ERROR) naming `status`; an empty `status=` on its own
means no status filter.

```bash
curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?metadata[tenant]=acme&status=DRAFT,ISSUED" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

## Key and value rules

| Rule | Limit |
|---|---|
| Max key/value pairs per invoice | 50 |
| Max key/value pairs per **query** | 50 |
| Key format | Must match `^[A-Za-z0-9_\-.]{1,64}$` |
| Value type | Always string — numbers and booleans must be serialized |
| Matching | Exact match (no `LIKE`, no prefix, case-sensitive) |
| Storage encoding | UTF-8 |

Querying with an empty value (`?metadata[tenant]=`) returns invoices where the key is present with an empty string — not invoices where the key is absent.

## Common patterns

### Reconciling payment-platform invoices (Stripe, WooCommerce…)

Invoices that BeeL. auto-generates from a connected payment platform already carry a set of **system metadata keys** that BeeL. writes for you. You don't set these — but you can filter on them. The ones you'll reach for most:

| Key | Example | Identifies |
|---|---|---|
| `external_customer_id` | `cus_ULGk8bzIr88aag` | The payment-platform customer. Present whenever the payment carries a customer (Checkout, subscriptions, customer-attached PaymentIntents — including their refunds). Absent on flows with no customer, e.g. Stripe Terminal or payment links without customer collection. |
| `external_payment_id` | `pi_3Oq…` / `in_1Tb…` | Canonical payment reference (PaymentIntent, Charge or Stripe Invoice id). Unique per invoice. |
| `payment_intent_id` | `pi_3Oq…` | Stripe PaymentIntent, when the payment has one. |
| `charge_id` | `ch_3Oq…` | Stripe Charge, when the payment has one. |
| `payment_provider` | `STRIPE_CONNECT` | Origin platform. |

#### Every invoice for one customer

This is the usual ask — pull a customer's full invoice history in a single call by filtering on `external_customer_id`:

```bash
curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?metadata[external_customer_id]=cus_ULGk8bzIr88aag" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

`external_customer_id` is the key to use — it's normalized across providers and present whenever the payment has an associated customer (the usual case for Checkout, subscriptions and their refunds). A few flows attach no customer (Stripe Terminal, payment links without customer collection); those invoices simply won't carry it, so don't assume the result is exhaustive for a buyer who also paid through one of those.

Scope it further by composing with the regular filters — e.g. a customer's issued invoices from this quarter:

```bash
curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?metadata[external_customer_id]=cus_ULGk8bzIr88aag&status=ISSUED&date_from=2026-04-01" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

#### A single payment

```bash
curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?metadata[external_payment_id]=pi_3OqXxYx2eZvKYlo0" \
  -H "Authorization: Bearer $BEEL_API_KEY"
```

Beyond these system keys, an invoice also carries whatever metadata the integration passed through (for WooCommerce, keys like `order_id` or `site_url`). Those vary per integration and aren't guaranteed — `external_customer_id` and `external_payment_id` are the stable ones to build on.

### Multi-tenant SaaS

Tag every invoice with the customer/workspace identifier from your own system:

```json
{ "metadata": { "tenant": "acme-corp", "workspace_id": "ws_42" } }
```

Then surface each tenant's invoices in their own dashboard with a single filter.

### E-commerce order linking

Use your order ID as the cross-reference and the cart provider as a secondary tag:

```json
{ "metadata": { "external_order_id": "ORD-2026-00042", "source": "shopify" } }
```

To make sure a retried webhook never creates a second invoice for the same order, send the order ID as `external_ref` too — BeeL. rejects a second live invoice with the same reference. See [One invoice per order](/guides/idempotency#one-invoice-per-order).

## What metadata is *not* for

- **Fiscal data** — buyer NIF, address, tax classification all belong in `recipient` and `lines.*`, never in metadata. AEAT never sees the metadata.
- **Free-text invoice memos** — use the `notes` field (which is printed on the PDF). Metadata is invisible to the buyer.
- **Colliding with system keys** — on invoices auto-generated from a payment platform, BeeL. writes its own keys (`external_customer_id`, `external_payment_id`, `payment_provider`, …). On invoices you create via the API, BeeL. doesn't add anything. Either way, namespace your own keys (`internal_*`, `acme_*`) so they never clash with current or future system keys.
- **Sensitive data** — metadata is returned to anyone with the API key, included in the OpenAPI response schema, and persisted indefinitely. Don't store tokens, PII beyond identifiers, or anything you'd not put in a log line.

## Related

<Related>

- [List invoices API reference](/invoices/listCompanyInvoices) — full query parameter schema
- [Create invoice](/invoices/createCompanyInvoice) — where the `metadata` field lives in the create body
- [Idempotency](/guides/idempotency) — pair metadata with idempotency keys to safely retry from your system

</Related>

---

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