Create an invoice
Scopeinvoices:writeCreates an invoice, optionally numbered and issued in the same call with
options.issue_directly: true.
- Deprecated: use
POST /v1/companies/{company_id}/invoices, which behaves identically.
Keys are prefixed beel_sk_, and each one carries the scopes it was created with: a key
short of the scope an operation needs is answered 403. The scope an operation requires
is shown next to its title, and the full catalogue lives in the Scopes reference.
Keys are created from the BeeL dashboard. They are secret credentials: do not share them or commit them to source control.
In: header
Query Parameters
Same flag as options.wait_for_pdf, accepted here too so both routes to a PDF take it
in the same place — POST /v1/invoices/{invoice_id}/issue declares it as a query param
and this one used to accept it only inside the body.
Only applies when the invoice is issued in this call (options.issue_directly: true).
If true, waits for PDF generation and returns the URL in the response (~1-3s). It is
enough to ask for it in one of the two places.
falseHeader Parameters
Which company (tax ID) the request operates on.
An API key belongs to an account, and an account may hold several companies. Endpoints that read or write company-owned data — invoices, customers, products, series, tax and VeriFactu settings — resolve their target company from this header when the path does not already name one.
On a path that names the company, such as /v1/companies/{company_id}/invoices, the path
is the target and this header is not read at all: it neither switches the target nor makes
the request fail, so sending one that disagrees with the path is silently ignored rather
than rejected. Prefer those paths whenever you need to be explicit about which tax ID you
are operating on.
On an account holding a single company the header may be omitted — that company is used.
On an account holding several it is required: the request fails with
403 ACTIVE_COMPANY_REQUIRED otherwise. A company-owned record always belongs to one tax
ID, so operating on it without saying which one has no meaning.
The company does not have to belong to the API key's own account: one you manage works
too, which is what makes the company_id returned by POST /v1/accounts usable here to
invoice on a provisioned account's behalf. What you may do with it is then decided by your
access level over that account, and issuing also requires a signed fiscal representation.
This header is how you operate on a company, never how you find one: a value you do not
reach answers 403, the same as one that does not exist, so guessing reveals nothing. Get
the company_id from the response that created the company.
Requires the companies:read scope. Returns 403 if the company is neither yours nor one
you manage.
uuidIdempotency key to prevent duplicates in sensitive operations.
- Any unique client-generated string (e.g. an order id). A UUID also works but is not required
- Allowed characters: letters, digits,
_and-(max 255 chars) - If the same key is sent twice, the result of the first operation is returned
- Keys expire 24 hours after processing
The key is scoped per user and environment, and bound to the request body, so retrying after a network timeout replays the stored response instead of repeating the operation.
| Status | Code | When |
|---|---|---|
400 | INVALID_IDEMPOTENCY_KEY | The key breaks the format rules above. |
409 | IDEMPOTENCY_KEY_PROCESSING | The first request is still in flight. Wait and retry with the same key. |
409 | IDEMPOTENCY_KEY_MISMATCH | The key was already used with a different body. Use a new key. |
^[a-zA-Z0-9_-]+$length <= 255"STANDARD" | "CORRECTIVE" | "SIMPLIFIED" | "PROFORMA"Invoicing series ID (if not specified, uses default)
uuidDate when the operation actually occurred. Optional.
Use when invoicing for a past operation (e.g., services delivered last month but invoiced this month). Must be today or a past date.
If omitted, the operation date is assumed to be the same as the issue date (today).
The issue_date is always set automatically to today per Spanish anti-fraud law
(Ley Antifraude / VeriFactu). To issue an invoice on a future date, create a
draft and use POST /v1/invoices/{invoice_id}/schedule.
datePayment due date. If not specified, calculated according to payment method. Must be the same as or after the issue date (today).
dateOffer validity date. Only rendered on PROFORMA invoices; on any other
invoice type the field is inert (accepted and stored, but never shown on
the document). Optional and purely informational — nothing is triggered
automatically when it passes. Not to be confused with due_date (payment
due date).
date1 <= itemslength <= 1000Your own key/value pairs to cross-reference this invoice with records in your system (order ids, tenants, internal codes). Namespace them to avoid clashing with the system keys BeeL adds on payment-generated invoices.
Controls how the invoice is processed after creation.
All fields default to false if not specified, except verifactu_enabled,
which falls back to the company's declared preference (see its description).
Common combinations:
- Draft (default): omit
optionsor set all tofalse - Issue immediately:
{ issue_directly: true } - Issue + wait for PDF:
{ issue_directly: true, wait_for_pdf: true } - Issue + send email:
{ issue_directly: true, send_automatically: true } - Full automation:
{ issue_directly: true, wait_for_pdf: true, send_automatically: true, email_config: { ... } }
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://app.beel.es/api/v1/invoices" \ -H "Content-Type: application/json" \ -d '{ "type": "STANDARD", "recipient": { "customer_id": "123e4567-e89b-12d3-a456-426614174000" }, "lines": [ { "description": "Corporate website development", "quantity": 40, "unit": "hours", "unit_price": 37.5, "discount_percentage": 0, "main_tax": { "type": "IVA", "percentage": 21, "regime_key": "01" } } ], "payment_info": { "method": "BANK_TRANSFER", "iban": "ES9121000418450200051332", "payment_term_days": 30 }, "notes": "Payment via bank transfer" }'Invoice created successfully in DRAFT state with draft number and totals
{
"success": true,
"data": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"invoice_number": null,
"number": null,
"type": "STANDARD",
"status": "DRAFT",
"issue_date": "2025-01-20",
"issuer": {
"legal_name": "Tu Empresa SL",
"nif": "B12345674",
"address": {
"street": "Calle Ejemplo",
"number": "123",
"postal_code": "28001",
"city": "Madrid",
"province": "Madrid",
"country": "España"
}
},
"series": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"code": "A"
},
"recipient": {
"customer_id": "123e4567-e89b-12d3-a456-426614174000",
"legal_name": "Cliente Ejemplo SL",
"nif": "B87654321",
"email": "cliente@ejemplo.com",
"address": {
"street": "Avenida Cliente",
"number": "456",
"postal_code": "28013",
"city": "Madrid",
"province": "Madrid",
"country": "España"
}
},
"lines": [
{
"description": "Corporate website development",
"quantity": 40,
"unit": "hours",
"unit_price": 37.5,
"discount_percentage": 0,
"taxable_base": 1500,
"main_tax": {
"type": "IVA",
"percentage": 21,
"regime_key": "01"
},
"line_total": 1815
}
],
"totals": {
"taxable_base": 1500,
"total_vat": 315,
"total_irpf": 0,
"total_equivalence_surcharge": 0,
"vat_breakdown": [
{
"type": 21,
"base": 1500,
"amount": 315
}
],
"invoice_total": 1815
},
"payment_info": {
"method": "BANK_TRANSFER",
"payment_term_days": 30
},
"notes": "Payment via bank transfer",
"verifactu": {
"enabled": false
},
"created_at": "2025-01-20T10:30:00Z",
"updated_at": "2025-01-20T10:30:00Z"
},
"meta": {
"timestamp": "2025-01-20T10:30:00Z",
"request_id": "c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f"
}
}{
"success": false,
"error": {
"code": "INVALID_JSON_FORMAT",
"message": "The field 'due_date' has an invalid date format: '2026-03-04fds'. Expected format: YYYY-MM-DD.",
"details": {
"field": "due_date",
"invalid_value": "2026-03-04fds",
"expected_format": "YYYY-MM-DD"
}
},
"meta": {
"timestamp": "2026-03-05T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication is required to access this resource"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "FORBIDDEN",
"message": "You do not have permission to access this resource"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Duplicate resource exists (NIF, email, invoice number, series code)
{
"success": false,
"error": {
"code": "CONFLICT",
"message": "Resource already exists",
"details": {
"conflict_type": "DUPLICATE_NIF",
"field": "nif",
"value": "B12345674",
"existing_resource_id": "123e4567-e89b-12d3-a456-426614174000",
"message": "A customer with NIF B12345674 already exists"
}
},
"meta": {
"timestamp": "2025-01-20T10:00:00Z",
"request_id": "d4d4d4d4-0004-4000-a000-000000000004"
}
}Request data fails validation (missing fields, invalid format, business rules)
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": {
"errors": [
{
"field": "nif",
"message": "Invalid NIF format. Expected 9 characters (8 digits + letter or letter + 8 digits)",
"value": "B123INVALID"
},
{
"field": "lines",
"message": "At least one line item is required",
"value": []
},
{
"field": "recipient.address.postal_code",
"message": "Invalid postal code format. Expected 5 digits",
"value": "280"
},
{
"field": "lines[0].unit_price",
"message": "Unit price must be greater than or equal to 0",
"value": -10.5
}
]
}
},
"meta": {
"timestamp": "2025-01-20T10:00:00Z",
"request_id": "c3c3c3c3-0003-4000-a000-000000000003"
}
}{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please try again in 60 seconds."
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}{
"success": false,
"error": {
"code": "UNSUPPORTED_MEDIA_TYPE",
"message": "Unsupported media type: text/plain. Supported: application/json"
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
}
}Void an issued invoice POST
Voids an issued invoice of this company. The document is kept and its number is never reused. - **When to use it:** the operation never took place. If it did take place but with errors, issue a corrective invoice instead (`POST …/{invoice_id}/corrective`). - **`reason`:** required, at least 10 characters — it is fiscal data. - **VeriFactu:** when it is enabled for the invoice, a cancellation record is submitted to the AEAT. - **Proformas:** voiding an `ACTIVE` proforma is a plain status change with no fiscal effect — no corrective invoice, nothing submitted to the AEAT. The voided proforma is kept as the record of a rejected or withdrawn offer and stays listed.
Update an invoice PUT
Updates a draft invoice. Only invoices in `DRAFT` status can be modified. - **Deprecated:** use `PATCH /v1/companies/{company_id}/invoices/{invoice_id}`, the single update verb of the canonical form. - **Difference:** only the verb changes. Both update just the fields present in the body and leave every other one untouched.