Create a customer for a company
Scopecustomers:writeCreates a new customer under this company.
Idempotency-Key: it identifies the same operation on the deprecated flat route, so a retry that switches route replays instead of creating twice.
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.
uuidHeader 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 <= 255Customer legal name (required).
When you also send a Spanish nif, how it is used depends on the customer:
for an individual the AEAT census matches NIF and name together, so a
name it does not recognise makes the customer invalid; for a company
the name is not verified and only the CIF decides.
^\S.*$1 <= length <= 120Customer trade name. Optional, but not empty by default: leave it out on creation
and it is filled with legal_name, which is what then shows as the recipient's trade
name on the invoice PDF. Send it explicitly if the two differ.
The default applies on creation only. A PUT replaces the customer whole, so
omitting trade_name there clears it instead of refilling it from legal_name.
length <= 120Alternative identifier for customers without Spanish Tax ID.
VeriFactu rules (checked when you send it, 422 on violation)
country_codeis required, except forPASSPORT(03) andNOT_REGISTERED(07), the two types AEAT accepts withES: omitted,ESapplies. Missing for any other type, the identifier is rejected withALTERNATIVE_ID_COUNTRY_REQUIRED, anderror.detailsnames the field where it was sent:alternative_id.country_codeon a customer,recipient.alternative_id.country_codeon an invoice recipient. Same code in both.- If
country_code = ES, thentypemust bePASSPORT(03) orNOT_REGISTERED(07) (ALTERNATIVE_ID_SPAIN_INVALID_TYPE). - If
type = NOT_REGISTERED(07), thencountry_codemust beES(ALTERNATIVE_ID_REQUIRES_SPAIN), andnumbermust be a Spanish DNI or NIE: a Spanish company is always registered (RECIPIENT_UNREGISTERED_ID_MUST_BE_DNI_OR_NIE). That DNI or NIE must not be one the AEAT census identifies: if it is, the identifier is rejected withALTERNATIVE_ID_ACTUALLY_IN_CENSUSand has to be sent asnif. A customer is checked when it is saved; an invoice recipient, when the invoice is issued with VeriFactu. If the census does not answer, the identifier is not rejected. - If
type = NIF_IVA(02),country_codemust be an EU member state other than Spain (ALTERNATIVE_ID_VAT_REQUIRES_EU_COUNTRY), andnumbermust have that country's EU VAT number structure as AEAT defines it: the country prefix (ELfor Greece) followed by the national number, e.g.FR40303265045,DE123456789,EL094014201(ALTERNATIVE_ID_VAT_INVALID_FORMAT). Lowercase letters are accepted and stored in uppercase. A customer from outside the EU is identified with another type, such asOTHER_DOCUMENTorCOUNTRY_ID.
A number that is blank once trimmed is rejected with ALTERNATIVE_ID_INVALID.
Well-formed is not the same as registered: an EU VAT number that is not in the VIES census is still rejected by VeriFactu after the invoice is issued.
An identifier returned in a response is the one stored. A customer saved before a rule existed keeps its identifier and can still be read; issuing an invoice to it with an identifier that breaks these rules is rejected with the same code, before a number is used.
Matrix of allowed combinations
type | country_code = ES | country_code ≠ ES |
|---|---|---|
NIF_IVA (02) | ✗ | EU member states only |
PASSPORT (03) | ✓ | ✓ |
COUNTRY_ID (04) | ✗ | ✓ |
RESIDENCE_CERTIFICATE (05) | ✗ | ✓ |
OTHER_DOCUMENT (06) | ✗ | ✓ |
NOT_REGISTERED (07) | ✓ | ✗ |
Address you send when you create or update a company, a customer or an onboarding.
A required field made only of whitespace passes the length checks below but leaves the
address without that part, so it is rejected with 422: STREET_EMPTY,
POSTAL_CODE_EMPTY, CITY_EMPTY or PROVINCE_EMPTY.
Addresses you read back are described by their own schema.
A phone number as this API accepts it: 9 to 20 characters, and only digits, spaces,
dashes, parentheses and an optional leading +. Every request that takes a phone number
uses this schema.
It is Phone plus the rules enforced on input. A value this schema accepts always
satisfies Phone, so anything you send here is something a response can return.
^[+]?[0-9\s\-\(\)]+$9 <= length <= 20Main contact address (minimum valid email is 5 chars, e.g. a@b.co). Invoice emails go
here only when the customer has no billing_emails.
email5 <= length <= 255Website URL
^(https?://.+|)$length <= 255Addresses that receive the customer's invoice emails. When the send request names no
recipients and the invoice has no email_config recipients, invoices go to these
addresses, and not to email; email is used only when billing_emails is empty.
Contact person name (optional)
length <= 200Additional notes about the customer (optional)
General discount percentage (optional)
0 <= value <= 100Response 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/customers" \ -H "Content-Type: application/json" \ -d '{ "legal_name": "Tech Solutions SL", "nif": "B12345674", "email": "admin@techsolutions.com", "phone": "+34912345678", "address": { "street": "Calle Mayor", "number": "123", "floor": "2º B", "postal_code": "28013", "city": "Madrid", "province": "Madrid", "country": "España" }, "notes": "Cliente prioritario - Pago puntual" }'Customer created successfully with generated ID and initial statistics
{
"success": true,
"data": {
"id": "123e4567-e89b-12d3-a456-426614174000",
"legal_name": "Tech Solutions SL",
"nif": "B12345674",
"email": "admin@techsolutions.com",
"phone": "+34912345678",
"address": {
"street": "Calle Mayor",
"number": "123",
"floor": "2º B",
"postal_code": "28013",
"city": "Madrid",
"province": "Madrid",
"country": "España"
},
"notes": "Cliente prioritario - Pago puntual",
"active": true,
"created_at": "2025-01-20T10:00:00Z",
"updated_at": "2025-01-20T10:00:00Z"
},
"meta": {
"timestamp": "2025-01-20T10:00:00Z",
"request_id": "c1c1c1c1-0001-4000-a000-000000000001"
}
}{
"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"
}
}Convert a proforma into an invoice (deprecated) POST
Converts an accepted proforma into a real invoice. - **Deprecated:** use `POST /v1/companies/{company_id}/invoices/{invoice_id}/convert-to-invoice`, which behaves identically. - **Result:** the proforma moves to the terminal status `CONVERTED` and a new `STANDARD` invoice is created in `DRAFT`. - **Issuing:** `issue: true` numbers and issues that new invoice in the same call. **Retires on 9 December 2026.** See the [migration guide](https://docs.beel.es/changelog/resources-under-the-nif) for what moved where and what changes when you switch.
Get a customer of a company GET
Retrieves the complete details of a customer of this company.