Create an invoice
Scopeinvoices:writeCreates an invoice for this company. The issuer data comes from the company in the path, and the document is created as a draft unless you ask for it to be issued.
- Issuing:
options.issue_directlynumbers and issues the invoice in the same call. Submission to the AEAT is asynchronous, soverifactu.submission_statuscomes back asPENDING: a 2xx means the invoice was accepted for submission, not that the AEAT has registered it. If the number the series would assign is already used by another invoice of the same company, in this series or in another one, it fails with400 SERIES_NUMBER_COLLISIONwithout issuing anything or consuming a number: the series needs review, so contact support. - Document type:
typechooses the document. APROFORMAis non-fiscal — it is bornACTIVE, numberedPRO-...from its own non-fiscal series, and ignoresissue_directly. - Related: to copy an existing invoice into a new draft, use
POST …/invoices/derivations, which carries neithertype, norrecipient, norlines.
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
Path Parameters
Unique identifier (UUID) of the company the operation acts on — its identifier, not its NIF. It is the only source of context: the account that owns it is derived from it, and the BeeL-Active-Company header plays no part. A company you do not reach answers 403, and so does a company that does not exist, so the existence of a company in another account is never disclosed.
uuidQuery Parameters
Same flag as options.wait_for_pdf. Only applies when the invoice is issued in this
call (options.issue_directly: true).
falseHeader Parameters
Idempotency 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) - Retrying with the same key replays the first response when it was a success (2xx) or a
server error (5xx): same status and body, plus the header
Idempotency-Replay: true. After a 5xx, check whether the operation took effect before retrying with a new key - A 4xx is not stored: the key is released, so the corrected request can reuse it
- Stored responses 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 for the Retry-After seconds (2) 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. It must be a series of the invoice's type: a series numbers only
documents of its own type (RD 1619/2012, arts. 6.1.a and 7.1.a), otherwise
422 SERIES_INCOMPATIBLE_DOC_TYPE. If omitted, the company's default series of that
type is used; the SIMPLIFIED one is created on first use if the company has none,
while a missing STANDARD default fails with 422 SERIES_DEFAULT_NOT_FOUND.
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, and not more than twenty
years before today (AEAT does not accept an older one): an older date answers
422 OPERATION_DATE_TOO_OLD, on creation and again on issue.
If omitted, the operation date is assumed to be the same as the issue date (today).
The issue_date is not an input: BeeL sets it to the day the invoice is issued.
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).
dateInvoice recipient: either a registered customer (customer_id) or the recipient's
data inline (legal_name, nif, address…), never both. Sending customer_id
together with any other recipient field returns 422
RECIPIENT_CUSTOMER_AND_DATA_EXCLUSIVE, on create and on edit.
All fields are optional at schema level, but for an ad-hoc recipient (no customer_id) on non-SIMPLIFIED invoices the API requires legal_name, address and nif (or alternative_id); omitting the address returns 422 RECIPIENT_ADDRESS_REQUIRED.
1 <= 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.
VeriFactu is not an option here: whether an invoice is registered with AEAT is a
fact of the tax identity (NIF x environment), resolved at issue time against the
company's regime. See verifactu.enabled in the invoice response for what was applied.
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/companies/497f6eca-6276-4993-bfeb-53cbbbba6f08/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": "VALIDATION_ERROR",
"message": "The provided data is not valid",
"details": {
"field": "specific error message"
}
},
"meta": {
"timestamp": "2025-01-15T10:30:00Z",
"request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
},
"type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
"title": "INVOICE_NO_LINES",
"detail": "The invoice must have at least one line",
"instance": "/v1/invoices/abc-123"
}An equivalent resource already exists. error.code names the clash (here a customer with the same identifier); details is an empty object.
{
"success": false,
"error": {
"code": "CLIENT_DUPLICATE",
"message": "A client with this NIF already exists",
"details": {}
},
"meta": {
"timestamp": "2025-01-20T10:00:00Z",
"request_id": "d4d4d4d4-0004-4000-a000-000000000004"
}
}The body parses but one or more values are not acceptable. details is a flat map from the offending property (snake_case, nested paths joined with a dot) to a message; one entry per property.
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "The provided data is not valid.",
"details": {
"nif": "The field 'nif' cannot be empty",
"lines": "The field 'lines' cannot be empty",
"recipient.address.postal_code": "Contains invalid characters."
}
},
"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"
}
}