NewCompany stats count voided invoices by how they were voided
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog

Custom fields

Define your own labels for a company's invoices (store, site, cost centre), fill them in, and filter and export by them.


A custom field is a label that belongs to one company: Store, Site, Cost centre, Project. You define it once, and every invoice of that company can carry a value for it. People see it on screen and, if you choose, on the PDF; you can filter the invoice list and the spreadsheet export by it.

BeeL. gives a field no meaning of its own. It never affects taxes, totals or numbering.

WhatOperation
Create or update a fieldPUT …/custom-fields/{key}
List the fields, with their optionsGET …/custom-fields
Get one fieldGET …/custom-fields/{key}
List the options of a fieldGET …/custom-fields/{key}/options
Add an option in advancePOST …/custom-fields/{key}/options
Rename, archive or reactivate an optionPATCH …/custom-fields/{key}/options/{option_id}

All paths hang off /v1/companies/{company_id}. Reading needs the custom-fields:read scope and writing needs custom-fields:write. Test and Live keep separate catalogues: a field created with a Test key does not exist in Live.

Metadata or custom fields

Both attach extra data to an invoice. Pick by who reads it:

metadatacustom_fields
ForTechnical identifiers of your integration: order id, tenant, source systemWhat a person sees, filters or exports: store, site, cost centre
Visible to peopleNoYes, on screen and optionally on the PDF
Defined beforehandNo, free key/value pairsYes, a catalogue per company
FilterExact match on any key, see Filtering by metadataExact match on the text saved on the invoice, which the options keep consistent

If a person will ever read the value, use a custom field. If only your code does, use metadata.

Create a field

The key is the path and it names the field for good: lowercase letters, digits and _, up to 64 characters. PUT creates the field or updates it, so you can run it on every deployment to seed the catalogue. It answers 201 when it creates the field and 200 when it already existed.

curl -X PUT "https://app.beel.es/api/v1/companies/{company_id}/custom-fields/store" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Store", "show_on_pdf": true }'

label and show_on_pdf are always replaced. suggest_values and is_archived keep their current value when you omit them, so repeating the request never undoes a change someone made in the app. A new field starts active and with suggest_values: true.

Use it on an invoice

Send custom_fields as a list of key and value, at most one value per key:

{
  "type": "STANDARD",
  "recipient": { "customer_id": "4f244735-980b-4d9c-80e8-6331fa0b1958" },
  "lines": [
    { "description": "Consulting", "quantity": 1, "unit_price": 500,
      "main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" } }
  ],
  "custom_fields": [
    { "key": "store", "value": "Gran Vía 12" }
  ]
}

The invoice answers with the same list, and each entry also carries the label the field had when the invoice was saved:

"custom_fields": [
  { "key": "store", "label": "Store", "value": "Gran Vía 12" }
]

The invoice keeps a copy of the label and the text. Renaming or archiving the field, or its options, later does not change any invoice already saved, and neither does it change the PDF when it is regenerated. An invoice with no custom fields answers [].

To change them, send custom_fields in PATCH …/invoices/{invoice_id}: the list replaces the current one as a whole, omitting it keeps the current ones, and null or [] removes them all. Only a draft, a scheduled invoice or an active proforma can change them.

Options fill themselves in

With suggest_values: true, the values already used become the options the app offers next time, per customer. When you save an invoice for a registered customer (recipient.customer_id) with a value that is new, BeeL. adds it as an option of that customer. With recipient data sent inline there is no customer to attach it to, so the text is stored and no option is created.

Before adding it, BeeL. checks whether an equivalent option already exists, among those of that customer and the common ones. Two values are equivalent when they match ignoring case, accents and repeated spaces, and ñ counts as n:

You sendExisting optionThe invoice stores
gran via 12Gran Vía 12Gran Vía 12
GRAN VÍA 12Gran Vía 12Gran Vía 12
PeñaPenaPena
Gran Vía 14none equivalentGran Vía 14, and a new option

So a typo in case or accents does not split your filter in two. An archived option that matches is reused with its text and stays archived.

To seed a catalogue before the first invoice, add options with POST …/custom-fields/{key}/options, for one customer or, without customer_id, common to all of them. It answers 201 when it creates the option and 200 with the existing one when an equivalent is already there.

Values that change on every invoice

For something unique per invoice, such as an order number, send "suggest_values": false. The invoice then stores the text as sent, without leading and trailing spaces, it is not compared with any option and it creates none. Turning the setting off deletes no option, and turning it on again offers them again.

Read the options for a customer

GET …/custom-fields returns each field with its common options. Add customer_id to get those plus the ones of that customer, which is what an invoice for that customer can offer. A customer_id that is not a customer of the company is not an error: only the common options come back. To page through every option of a field, use GET …/custom-fields/{key}/options.

Renaming an option changes only what is offered from then on. If another option in the same scope is equivalent to the new text, the rename answers 409 CUSTOM_FIELD_OPTION_DUPLICATE.

Archive, never delete

Fields and options are never deleted, and a key never changes. To stop using a field, archive it:

curl -X PUT "https://app.beel.es/api/v1/companies/{company_id}/custom-fields/store" \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "label": "Store", "show_on_pdf": true, "is_archived": true }'

An archived field cannot be added to new invoices, and invoices that already carry it keep it. Archived fields and options are left out of the listings unless you send include_archived=true.

Filter the list and export

GET …/invoices takes custom_fields[<key>]=<value>, the same bracket style as metadata. Several keys combine with AND, and also with the rest of the filters, such as customer_id. You can send up to 20 keys.

curl -g "https://app.beel.es/api/v1/companies/{company_id}/invoices?customer_id={customer_id}&custom_fields[store]=Gran%20V%C3%ADa%2012" \
  -H "Authorization: Bearer $BEEL_API_KEY"

The value is compared with the text saved on the invoice, exactly, case and accents included. Use the text the invoice carries, which is the one the option has. After you rename an option, invoices saved earlier are found by the old text. A key the company does not have matches no invoice.

The spreadsheet export takes the same filter inside filters:

{
  "filters": {
    "customer_id": "550e8400-e29b-41d4-a716-446655440010",
    "custom_fields": { "store": "Gran Vía 12" }
  }
}

Send it to POST …/invoices/exports. It behaves like the list filter. More than 20 keys is rejected with 422 CUSTOM_FIELD_FILTER_TOO_MANY_KEYS.

Recurring templates, corrective invoices and other copies

  • Recurring invoices. A template carries custom_fields and copies them to every invoice it generates. If you create a template from an invoice, it takes that invoice's fields. A field archived later is left out of the invoices generated from then on, and the template keeps generating.
  • Corrective invoices. A corrective carries the fields of the original, each with the label the field has now. A field archived since is left out.
  • Proforma to invoice. The new invoice copies the fields of the proforma, with the same rule.

On the PDF

With show_on_pdf: true, the PDF prints the field in a block of its own under the notes, with its label and value. An invoice keeps the setting it was saved with, so changing show_on_pdf affects invoices saved from then on.

Not a tax mention

Custom fields are informational. They are not part of the VeriFactu record sent to the AEAT and do not enter its hash. The mentions the regulations require (an exemption, the reverse charge) stay in their own fields of the invoice and cannot be written as a custom field.

Gotchas

CaseErrorStatusWhy
A key in custom_fields is not a field of this company in this environment. Create it with PUT …/custom-fields/{key} or fix the key; nothing is createdCUSTOM_FIELD_UNKNOWN422A key in custom_fields is not a custom field of this company in this environment.
A key in custom_fields names an archived field. Reactivate it with "is_archived": false, or send the list without it; nothing changesCUSTOM_FIELD_ARCHIVED409A key in custom_fields names a field the company has archived.
  • The same rule applies to the create and the update of invoices and recurring templates, even when a draft already carries a field that was archived afterwards.
  • Changing only the recipient of an invoice, with custom_fields omitted, carries the saved fields to the new customer. A field archived since is left out with no error.
  • A value is text of 1 to 255 characters once leading and trailing spaces are removed; an empty one answers 422.
  • A repeated key in the same list answers 422 VALIDATION_ERROR.