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:
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:
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):
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:
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 naming status; an empty status= on its own
means no status filter.
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:
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:
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
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:
{ "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:
{ "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.
What metadata is not for
- Fiscal data — buyer NIF, address, tax classification all belong in
recipientandlines.*, never in metadata. AEAT never sees the metadata. - Free-text invoice memos — use the
notesfield (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
Fiscal summary
Read the VAT and IRPF totals of a company for any period of up to 365 days, plus an annual IRPF projection — a starting point for preparing tax returns.
Handling errors
Error response shape, status codes, retry rules with idempotency semantics, and how to consume BeeL. errors programmatically.