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/invoices" \
-H "Authorization: Bearer beel_sk_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"type": "STANDARD",
"issue_date": "2026-05-20",
"recipient": { "customer_id": "550e8400-e29b-41d4-a716-446655440000" },
"lines": [
{ "description": "Consulting", "quantity": 1, "unit_price": 500, "discount_percentage": 0 }
],
"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/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/invoices?metadata[external_order_id]=ORD-42" \
-H "Authorization: Bearer beel_sk_live_xxx"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/invoices?metadata[tenant]=acme&metadata[source]=shopify" \
-H "Authorization: Bearer beel_sk_live_xxx"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/invoices?metadata[tenant]=acme&status=ISSUED&sort_by=issue_date&sort_order=desc&limit=50" \
-H "Authorization: Bearer beel_sk_live_xxx"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/invoices?metadata[external_customer_id]=cus_ULGk8bzIr88aag" \
-H "Authorization: Bearer beel_sk_live_xxx"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/invoices?metadata[external_customer_id]=cus_ULGk8bzIr88aag&status=ISSUED&date_from=2026-04-01" \
-H "Authorization: Bearer beel_sk_live_xxx"A single payment
curl -g "https://app.beel.es/api/v1/invoices?metadata[external_payment_id]=pi_3OqXxYx2eZvKYlo0" \
-H "Authorization: Bearer beel_sk_live_xxx"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" } }This is also a natural idempotency key candidate (Idempotency-Key: ORD-2026-00042) so a retried webhook never duplicates the invoice.
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
- List invoices API reference — full query parameter schema
- Create invoice — where the
metadatafield lives in the create body - Idempotency — pair metadata with idempotency keys to safely retry from your system