NewProvince is only required for addresses in Spain
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog

Branding and PDFs

Set the logo, template, colour and languages of a company's invoices, preview a draft, and download the final PDF without writing a polling loop.


Every company (NIF) carries its own branding: a logo, a PDF template, an accent colour, the language the invoice is printed in and the language of the emails that deliver it. There is no account-wide branding. Two companies of the same account look different if you set them up differently.

WhatOperation
Read the current brandingGET …/invoice-customization
Change template, colour or languagesPUT …/invoice-customization
Upload or replace the logoPUT …/logo
Remove the logoDELETE …/logo
List the templates with readable namesGET /v1/invoice-customization-options

The logo goes as multipart/form-data in a field named file. It must be JPEG or PNG, up to 1 MB. BeeL. resizes it to fit within 300×300 pixels, keeping its aspect ratio, stores it as a JPEG and replaces any previous logo. A 600×300 PNG comes back as a 300×150 JPEG.

curl -X PUT https://app.beel.es/api/v1/companies/{company_id}/logo \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -F "file=@logo.png"
{
  "success": true,
  "data": {
    "logo_url": "https://storage.example.com/beel-public/logos/9a1f.../profile-logo.jpg?v=1790235789896"
  },
  "meta": { "timestamp": "2026-09-24T10:00:00Z", "request_id": "4bf92f3577b34da6a3ce929d0e0e4736" }
}

A file that is too big or of the wrong type answers 422 FILE_TOO_LARGE or 422 INVALID_FILE_TYPE in error.code. The type is checked on the file itself, not its name: a GIF is refused, and so is a PNG uploaded as logo.txt. DELETE …/logo answers 204. It also answers 204 when there was no logo to delete.

Choose template, colour and languages

PUT …/invoice-customization is a partial update: only the properties you send change. The logo is not part of this body. It has its own sub-resource, shown above.

PropertyValues
invoice_template_typeMODERN_TABLE (a table of product/service lines) or PROFESSIONAL_SERVICE (a text-based layout)
invoice_accent_color#RRGGBB, upper or lower case, kept as sent. The short form #RGB is refused.
invoice_languagees, en or ca: the language the PDF is printed in
email_languagees, en or ca: the language of the emails that deliver the invoice
curl -X PUT https://app.beel.es/api/v1/companies/{company_id}/invoice-customization \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "invoice_template_type": "PROFESSIONAL_SERVICE",
    "invoice_accent_color": "#1d4ed8",
    "invoice_language": "en"
  }'

An invalid value answers 422 VALIDATION_ERROR. A property the body does not know, such as logo_url, is ignored. The response is the full customization, with email_language and logo_url unchanged:

{
  "success": true,
  "data": {
    "company_id": "9a1f2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
    "invoice_template_type": "PROFESSIONAL_SERVICE",
    "invoice_accent_color": "#1d4ed8",
    "invoice_language": "en",
    "email_language": "es",
    "logo_url": "https://storage.example.com/beel-public/logos/9a1f.../profile-logo.jpg?v=1790235789896"
  },
  "meta": { "timestamp": "2026-09-24T10:01:00Z", "request_id": "5c0a4e6f1d2b4c8e9f7a6b5c4d3e2f1a" }
}

Showing the templates to a person

The accepted template codes are the invoice_template_type enum. If you build a picker, GET /v1/invoice-customization-options gives each code a translated name and description, so you don't have to show MODERN_TABLE to a person. It is a closed catalogue: it is the same for every account, it takes no company_id and it is not paginated.

Branding and invoices you already issued

The PDF of an issued invoice is generated once, and under VeriFactu once the QR of its registration exists. It is never modified afterwards: a change of branding, of template or of BeeL.'s layout (such as where the VeriFactu QR is printed) applies to the invoices issued after it, and a customer who downloads an invoice issued last month keeps getting the document as it was delivered.

Voiding an invoice or issuing a corrective for it, including a TOTAL corrective, does not change its PDF and does not send invoice.pdf.generated. The invoice's status is in the API (status, and verifactu.submission_status for its registration) and in the app, not in the document. In the sandbox every PDF carries the test-invoice watermark, as part of the document issued there.

Preview a draft

A draft has no fiscal PDF yet. To see what it will look like, render it on the fly:

curl https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/pdf/preview \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -o draft.pdf

GET …/pdf/preview answers the PDF itself (application/pdf), not a URL. Nothing is stored and no number is used up, so every call shows the latest version of the draft with the branding set at that moment. That makes it the way to check a new template or colour before you issue anything. The document is marked as a draft in the invoice language (BORRADOR in Spanish, DRAFT in English) and has no VeriFactu QR code. A scheduled invoice can be previewed the same way.

An issued invoice answers 400 PREVIEW_DRAFT_ONLY here: it already has a stored PDF, which you download with the next operation.

There is also GET …/preview, which returns a short-lived URL to a WebP image of the invoice for showing inline, for example as a thumbnail in a list. It is not a PDF.

Download the final PDF

GET …/pdf returns a pre-signed download URL. The URL expires in five minutes and only allows GET. Ask for a new one each time you need it instead of storing it.

curl https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/pdf \
  -H "Authorization: Bearer $BEEL_API_KEY"
{
  "success": true,
  "data": {
    "download_url": "https://storage.example.com/beel-invoices/...&X-Amz-Signature=...",
    "expires_in_seconds": 300,
    "file_name": "factura_2026-0042.pdf"
  },
  "meta": { "timestamp": "2026-09-24T10:05:00Z", "request_id": "7d1e2f3a4b5c4d6e8f9a0b1c2d3e4f5a" }
}

The request waits for the PDF

A PDF is produced asynchronously after issuing. You don't need a polling loop: if the PDF is still being generated, the request waits for it, up to 10 seconds, and then answers 200.

  • 202 is the exception. It means the wait ran out with the PDF still in progress. There is no body. Ask again after the seconds given in Retry-After.
  • Prefer: wait=N limits the wait to N seconds. Prefer: wait=0 never waits: you get an immediate 202 while the PDF is in progress. A value above 10 is lowered to 10, and the 202 reports the wait actually used in Preference-Applied. A value that is not a non-negative integer is ignored and the default wait applies.
  • Drafts and scheduled invoices answer 400 INVOICE_NOT_ISSUED_NO_PDF straight away, without waiting: they will never have a fiscal PDF until they are issued. Use …/pdf/preview for them.
  • An invoice under VeriFactu that is not registered with the AEAT answers 400 INVOICE_NOT_REGISTERED_NO_PDF straight away: its PDF would carry the QR of a registration that does not exist, because it was rejected before reaching the AEAT or the invoice was voided without being registered. verifactu.error_message says why.
# Don't wait more than 3 seconds
curl https://app.beel.es/api/v1/companies/{company_id}/invoices/{invoice_id}/pdf \
  -H "Authorization: Bearer $BEEL_API_KEY" \
  -H "Prefer: wait=3"

Know when a PDF changes

The invoice.pdf.generated webhook is sent when BeeL. stores an invoice's PDF. For an issued invoice that is once, when its PDF is generated. A proforma sends it each time its PDF is generated: when it is created and after each edit. In an exceptional case BeeL. staff may regenerate the PDF of an issued invoice to fix a rendering defect; that sends the event again, and never emails the customer.

data.generation numbers the stored PDF of that invoice: 1 for the first one. Use it to discard an older generation that arrives late. A redelivery of the same generation is a retry, with the same BeeL-Event-Id. Events recorded before the field existed do not carry it: treat a missing value as unknown, not as 1.

{
  "id": "5c6d7e8f-9a0b-1c2d-3e4f-5a6b7c8d9e0f",
  "type": "invoice.pdf.generated",
  "created_at": "2026-09-24T10:04:58Z",
  "api_version": "2025-01",
  "livemode": true,
  "data": {
    "invoice_id": "550e8400-e29b-41d4-a716-446655440000",
    "invoice_number": "2026/0042",
    "generation": 1
  }
}

The payload has no URL, on purpose. A download URL lasts five minutes and a webhook can be retried for longer than that. When the event arrives, call GET …/pdf for a fresh URL and drop any copy you had. invoice_number is null for a proforma, which has no number yet.

White-label: invoices without BeeL. in sight

The API can stay invisible to the business you invoice for and to its customers, but BeeL.'s own PDF and email are not white-label. What shows BeeL. and what does not:

PieceShows BeeL.?
The API itself, its responses and webhooksOnly to you, the integrator
The QR and its qr_urlNo — the link points to AEAT's validation service, with the issuer's NIF
BeeL.'s PDFYes: a footer reading "Generated with BeeL." (in the invoice's language), with a link. It cannot be turned off; the logo, template and colour are the company's
BeeL.'s invoice emailYes: it is sent from a BeeL. address and ends with "Sent with BeeL.". The sender name is the company's trade name, and replies go to the company's email

Keeping BeeL. out of sight therefore means two things:

  1. Sending the email yourself. Nothing is emailed unless you ask for it — see Turning off BeeL.'s email.
  2. Rendering the invoice document yourself, with BeeL.'s invoice_number and the QR data the API returns.

Rendering the invoice yourself is a compliance decision, not only a design one

Under the criteria AEAT has published, software that prints the invoice or generates its QR can be a component of the billing system that needs its own declaración responsable (the producer's signed statement of compliance). BeeL.'s declaration covers BeeL., not the document your software renders. Check this architecture with your tax advisor before you build it — the rules are quoted in Software built on the API, and what applies to the QR in Rendering your own PDF.

Your own document does not change what is registered with AEAT: the record is the one BeeL. generated, with BeeL.'s number.

Gotchas

  • Branding belongs to the company. Setting it for one NIF doesn't set it for the others in the account.
  • PUT …/invoice-customization does not touch the logo. Upload and delete it through …/logo.
  • Download URLs expire in five minutes. Don't store them or send them in an email. Ask for a fresh one.
  • Check a new template on a draft. …/pdf/preview shows the current branding and changes nothing; issued invoices keep the PDF they were delivered with.
  • A 202 from …/pdf is not an error. Honour Retry-After and ask again.