NewThree filters returned the wrong rows
BeeL
Get StartedMulti-NIFVeriFactuStripeAPI ReferenceChangelog
Customers

Create several customers

Scopecustomers:write

Creates up to 500 customers in a single call.

  • Deprecated: use POST /v1/companies/{company_id}/customers/bulk, which validates and creates the same way. A dry run still answers 200; an actual creation answers 201 instead of 200.
  • Atomic: if any customer fails validation the whole batch is rejected with 422 BULK_VALIDATION_ERROR and nothing is persisted.
  • dry_run: with true the batch is only validated — tax identifiers against the AEAT register, duplicates inside the batch and against the existing customers, field formats — and nothing is written. With false, the default, validation is followed by creation.
  • Report: both modes return the same per-record report, so a dry run and a real run are read the same way.

POST
/v1/customers/bulk
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

dry_run?boolean

Validate the batch without persisting it (true), or validate and create it (false, the default). Either way the batch is atomic.

Defaultfalse

Header Parameters

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

StatusCodeWhen
400INVALID_IDEMPOTENCY_KEYThe key breaks the format rules above.
409IDEMPOTENCY_KEY_PROCESSINGThe first request is still in flight. Wait 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
customers
Items1 <= items <= 500

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/customers/bulk" \  -H "Content-Type: application/json" \  -d '{    "customers": [      {        "legal_name": "string",        "address": {          "street": "Calle Mayor, 123",          "number": "123",          "postal_code": "28001",          "city": "Madrid",          "province": "Madrid"        }      }    ]  }'
{
  "success": true,
  "data": {
    "metadata": {
      "total_customers": 10,
      "is_dry_run": true,
      "processing_time_ms": 450,
      "source_type": "CSV_IMPORT",
      "filename": "clientes_enero.csv",
      "file_size_bytes": 1048576,
      "total_rows": 250
    },
    "customers_validation": [
      {
        "index": 0,
        "customer": {
          "legal_name": "Empresa Cliente SL",
          "trade_name": "EmpresaCliente",
          "nif": "12345678A",
          "alternative_id": {
            "type": "NIF_IVA",
            "number": "string",
            "country_code": "st"
          },
          "address": {
            "street": "Calle Mayor, 123",
            "number": "123",
            "floor": "2º A",
            "door": "A",
            "postal_code": "28001",
            "city": "Madrid",
            "province": "Madrid",
            "country": "España",
            "country_code": "ES"
          },
          "phone": "+34 612 345 678",
          "email": "user@example.com",
          "website": "string",
          "billing_emails": [
            "user@example.com"
          ],
          "contact_person": "María García",
          "notes": "string",
          "preferred_payment_method": {
            "method": "BANK_TRANSFER",
            "iban": "ES1234567890123456789012",
            "swift": "ABCDESMMXXX",
            "payment_term_days": 30
          },
          "general_discount": 100,
          "active": true
        },
        "customer_id": "550e8400-e29b-41d4-a716-446655440000",
        "status": "VALID",
        "errors": [
          {
            "field": "nif",
            "value": "12345678X",
            "message": "NIF not found in AEAT census",
            "code": "CUSTOMER_IDENTIFIER_REQUIRED"
          }
        ],
        "warnings": [
          {
            "field": "email",
            "message": "Email not provided, NIF will be used for invoicing"
          }
        ],
        "row_number": 15
      }
    ],
    "statistics": {
      "total_processed": 10,
      "valid": 6,
      "with_warnings": 2,
      "with_errors": 2,
      "duplicates": 1,
      "invalid_nifs": 1,
      "imported": 8,
      "success_rate": 0.8,
      "importable": 8,
      "not_importable": 2
    }
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": true,
  "data": {
    "metadata": {
      "total_customers": 10,
      "is_dry_run": true,
      "processing_time_ms": 450,
      "source_type": "CSV_IMPORT",
      "filename": "clientes_enero.csv",
      "file_size_bytes": 1048576,
      "total_rows": 250
    },
    "customers_validation": [
      {
        "index": 0,
        "customer": {
          "legal_name": "Empresa Cliente SL",
          "trade_name": "EmpresaCliente",
          "nif": "12345678A",
          "alternative_id": {
            "type": "NIF_IVA",
            "number": "string",
            "country_code": "st"
          },
          "address": {
            "street": "Calle Mayor, 123",
            "number": "123",
            "floor": "2º A",
            "door": "A",
            "postal_code": "28001",
            "city": "Madrid",
            "province": "Madrid",
            "country": "España",
            "country_code": "ES"
          },
          "phone": "+34 612 345 678",
          "email": "user@example.com",
          "website": "string",
          "billing_emails": [
            "user@example.com"
          ],
          "contact_person": "María García",
          "notes": "string",
          "preferred_payment_method": {
            "method": "BANK_TRANSFER",
            "iban": "ES1234567890123456789012",
            "swift": "ABCDESMMXXX",
            "payment_term_days": 30
          },
          "general_discount": 100,
          "active": true
        },
        "customer_id": "550e8400-e29b-41d4-a716-446655440000",
        "status": "VALID",
        "errors": [
          {
            "field": "nif",
            "value": "12345678X",
            "message": "NIF not found in AEAT census",
            "code": "CUSTOMER_IDENTIFIER_REQUIRED"
          }
        ],
        "warnings": [
          {
            "field": "email",
            "message": "Email not provided, NIF will be used for invoicing"
          }
        ],
        "row_number": 15
      }
    ],
    "statistics": {
      "total_processed": 10,
      "valid": 6,
      "with_warnings": 2,
      "with_errors": 2,
      "duplicates": 1,
      "invalid_nifs": 1,
      "imported": 8,
      "success_rate": 0.8,
      "importable": 8,
      "not_importable": 2
    }
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  }
}
{
  "success": false,
  "error": {
    "code": "BAD_REQUEST",
    "message": "Invalid request"
  },
  "meta": {
    "timestamp": "2025-01-15T10: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": "BULK_VALIDATION_ERROR",
    "message": "string",
    "details": {
      "field": "specific error message"
    },
    "errors": [
      {
        "index": 0,
        "field": "string",
        "message": "string"
      }
    ]
  },
  "meta": {
    "timestamp": "2025-01-15T10:30:00Z",
    "request_id": "4bf92f3577b34da6a3ce929d0e0e4736"
  },
  "type": "https://docs.beel.es/errors/INVOICE_NO_LINES",
  "title": "INVOICE_NO_LINES",
  "detail": "La factura debe tener al menos una línea",
  "instance": "/v1/invoices/abc-123"
}
{
  "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"
  }
}

Preview an import of customers from CSV POST

Parses a CSV of customers and returns every row with its validation outcome, the rejected ones included. - **Deprecated:** use `POST /v1/companies/{company_id}/customers/imports/preview`, which parses and validates the file without writing anything — what this route does by default. To actually import, use `POST /v1/companies/{company_id}/customers/imports`, where the origin of the file travels in the body as `source` and a boolean no longer decides between looking and writing. - **File:** must use the headers of the template served by `GET /v1/templates/customer-import`, and is limited to 5 MB and 1,000 rows. - **Validation:** each row is checked against the same rules as single-customer creation, plus duplicate detection within the file and against existing customers, and a NIF lookup in the AEAT register. - **`dry_run`:** at its default `true` nothing is persisted; `false` also persists the valid customers.

Import customers from a Holded contacts export POST

Parses a contacts export from Holded (`.xlsx`) as Holded produces it, maps each contact to a customer and returns every row with its validation outcome, as the CSV import does. - **Deprecated:** use `POST /v1/companies/{company_id}/customers/imports` with `source: holded`, which imports the same file. It answers `201` and requires the `Idempotency-Key` header. - **Mapping:** the contact name becomes `legal_name`, the Holded ID becomes the tax identifier, and mobile takes precedence over landline for the phone. - **File:** limited to 10 MB and 5,000 contacts. - **`preview`:** at its default `true` nothing is persisted; `false` also imports the mapped customers.