NewStripe: a buyer with a NIF always gets a standard invoice, and the simplified threshold is deprecated
BeeL
Get startedMulti-NIFVeriFactuRulesStripeAPI referenceChangelog
Invoices

Create an invoice (deprecated)

Scopeinvoices:write

Creates an invoice, optionally numbered and issued in the same call with options.issue_directly: true.

Retires on 9 December 2026. See the migration guide for what moved where and what changes when you switch.


POST
/v1/invoices
AuthorizationBearer <token>

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

wait_for_pdf?boolean

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.

Defaultfalse

Header Parameters

BeeL-Active-Company?string

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.

Formatuuid
Idempotency-Key?string

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.

StatusCodeWhen
400INVALID_IDEMPOTENCY_KEYThe key breaks the format rules above.
409IDEMPOTENCY_KEY_PROCESSINGThe first request is still in flight. Wait for the Retry-After seconds (2) and retry with the same key.
409IDEMPOTENCY_KEY_MISMATCHThe key was already used with a different body. Use a new key.
Match^[a-zA-Z0-9_-]+$
Lengthlength <= 255
typestring
Value in"STANDARD" | "CORRECTIVE" | "SIMPLIFIED" | "PROFORMA"
series_id?string

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.

Formatuuid
operation_date?string

Date 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.

Formatdate
due_date?string

Payment due date. If not specified, calculated according to payment method. Must be the same as or after the issue date (today).

Formatdate
valid_until?string

Offer 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).

Formatdate
recipient

Invoice 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.

lines
Items1 <= items
payment_info?
notes?string
Lengthlength <= 1000
external_ref?string
metadata?

Your 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.

options?

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 options or set all to false
  • 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

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"
  }
}
{
  "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"
  }
}