@beel_es/cli 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +132 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ npx @beel_es/cli --live invoices list # production
|
|
|
38
38
|
|
|
39
39
|
`--data` accepts inline JSON, `@file.json`, or `-` for stdin. It is only **required** for endpoints whose request body is mandatory (e.g. `invoices create`); for lifecycle actions with an optional body (e.g. `invoices mark-paid`) it is optional, and verbs with no body (e.g. `invoices issue`) don't expose it at all. Each command's `--help` shows whether `--data` is required or optional. Binary responses (PDF, ZIP, Excel) require `--output <path>`.
|
|
40
40
|
|
|
41
|
-
Discover everything with `--help` at any level: `beel --help`, `beel invoices --help`, `beel invoices list --help` (flags, enums and defaults come from the API spec).
|
|
41
|
+
Discover everything with `--help` at any level: `beel --help`, `beel invoices --help`, `beel invoices list --help` (flags, enums and defaults come from the API spec). For commands that take a `--data` body, `--help` also lists the top-level body fields (name, type, required, enums) derived from the spec, plus a pointer to `beel docs search <resource>` for the full nested schema.
|
|
42
42
|
|
|
43
43
|
### Docs search (no API key needed)
|
|
44
44
|
|
package/dist/index.js
CHANGED
|
@@ -10692,7 +10692,7 @@ var require_public_api = __commonJS({
|
|
|
10692
10692
|
}
|
|
10693
10693
|
return doc;
|
|
10694
10694
|
}
|
|
10695
|
-
function
|
|
10695
|
+
function parse3(src, reviver, options) {
|
|
10696
10696
|
let _reviver = void 0;
|
|
10697
10697
|
if (typeof reviver === "function") {
|
|
10698
10698
|
_reviver = reviver;
|
|
@@ -10733,7 +10733,7 @@ var require_public_api = __commonJS({
|
|
|
10733
10733
|
return value.toString(options);
|
|
10734
10734
|
return new Document.Document(value, _replacer, options).toString(options);
|
|
10735
10735
|
}
|
|
10736
|
-
exports.parse =
|
|
10736
|
+
exports.parse = parse3;
|
|
10737
10737
|
exports.parseAllDocuments = parseAllDocuments;
|
|
10738
10738
|
exports.parseDocument = parseDocument;
|
|
10739
10739
|
exports.stringify = stringify;
|
|
@@ -10797,7 +10797,7 @@ var require_package = __commonJS({
|
|
|
10797
10797
|
"package.json"(exports, module) {
|
|
10798
10798
|
module.exports = {
|
|
10799
10799
|
name: "@beel_es/cli",
|
|
10800
|
-
version: "0.1.
|
|
10800
|
+
version: "0.1.3",
|
|
10801
10801
|
description: "Agent-first CLI for the BeeL invoicing API. Commands are derived at startup from the embedded OpenAPI spec. Run with npx @beel_es/cli \u2014 no install needed.",
|
|
10802
10802
|
license: "MIT",
|
|
10803
10803
|
type: "module",
|
|
@@ -10860,7 +10860,7 @@ var {
|
|
|
10860
10860
|
} = import_index.default;
|
|
10861
10861
|
|
|
10862
10862
|
// src/index.ts
|
|
10863
|
-
var
|
|
10863
|
+
var import_yaml2 = __toESM(require_dist(), 1);
|
|
10864
10864
|
|
|
10865
10865
|
// openapi/public-api.yaml
|
|
10866
10866
|
var public_api_default = "openapi: 3.0.3\ninfo:\n title: BeeL. Public API\n description: |\n Public invoicing API for self-employed professionals in Spain with VeriFactu regulatory compliance.\n\n Authenticate using API Keys (`Authorization: Bearer <key>`). Keys are scoped and environment-aware:\n `beel_sk_live_*` targets production, `beel_sk_test_*` targets sandbox (no quota consumed, VeriFactu test mode).\n\n POST endpoints accept an `Idempotency-Key` header (UUID) to safely retry requests without creating duplicates.\n version: 1.0.1\n contact:\n name: BeeL. Technical Team\n email: it@beel.es\n url: https://beel.es\n license:\n name: Proprietary\n url: https://beel.es/license\nservers:\n - url: https://app.beel.es/api\n description: Production server\nsecurity:\n - ApiKeyAuth: []\ntags:\n - name: Invoices\n description: Invoice CRUD operations\n - name: InvoiceLifecycle\n description: Invoice state transitions and scheduling\n - name: InvoiceDelivery\n description: Invoice delivery, bulk operations and exports\n - name: Customers\n description: Customer management\n - name: Customer Import\n description: Bulk creation, CSV import, and Holded import operations\n - name: Customer Templates\n description: Templates and sample files for customer imports\n - name: Products\n description: Product and service catalog management\n - name: ConfigurationTax\n description: Tax configuration and tax type catalogs\n - name: InvoiceSeries\n description: Invoice series management\n - name: ConfigurationVeriFactu\n description: VeriFactu compliance configuration\n - name: ConfigurationPreferences\n description: User language and invoice customization preferences\n - name: NIF\n description: Tax ID validation against AEAT tax registry\n - name: PublicCompanies\n description: Company (sub-account) CRUD operations\n - name: PublicCompanyRepresentations\n description: |\n VeriFactu representation flow for digital signature and AEAT registration.\n\n **Complete SaaS integration flow:**\n 1. `POST /v1/companies` \u2014 Create company/NIF (sub-account)\n 2. `POST /v1/companies/{id}/representation/generate` \u2014 Generate unsigned PDF\n 3. `GET /v1/companies/{id}/representation/download` \u2014 Download PDF for signing\n 4. `POST /v1/companies/{id}/representation/submit` \u2014 Upload signed PDF\n 5. `GET /v1/companies/{id}/representation/status` \u2014 Check VeriFactu status\n - name: PublicCompanyApiKeys\n description: |\n Scoped API key management per company.\n\n Create API keys scoped to a specific company for SaaS integrations.\n Use master key + `X-Active-Profile: {company_id}` header to operate as that company.\n - name: RecurringInvoices\n description: |\n Recurring invoice template management and automated invoice generation.\n\n Create templates that automatically generate invoices on a schedule.\n Supports monthly recurrence, pause/resume, skip, manual generation,\n and invoice preview.\n - name: Webhooks\n description: |\n Outbound webhook subscriptions for real-time event notifications.\n\n Register HTTPS endpoints to receive event payloads when key actions occur\n (e.g. VeriFactu submission accepted/rejected by AEAT).\n\n All deliveries are signed with HMAC-SHA256 (`BeeL-Signature` header).\n Failed deliveries are retried up to 5 times with exponential backoff.\npaths:\n /v1/invoices:\n get:\n tags:\n - Invoices\n summary: List invoices\n description: Returns a paginated list of invoices with optional filters\n operationId: listInvoices\n parameters:\n - $ref: '#/components/parameters/PageParam'\n - $ref: '#/components/parameters/LimitParam'\n - name: search\n in: query\n description: Global search across invoice number, recipient name, recipient NIF, and series code (partial, case-insensitive)\n schema:\n type: string\n - name: status\n in: query\n description: Filter by invoice status\n schema:\n $ref: '#/components/schemas/InvoiceStatus'\n - name: type\n in: query\n description: Filter by invoice type\n schema:\n $ref: '#/components/schemas/InvoiceType'\n - name: customer_id\n in: query\n description: Filter by customer UUID\n schema:\n $ref: '#/components/schemas/UUID'\n - name: date_from\n in: query\n description: Issue date from (YYYY-MM-DD)\n schema:\n type: string\n format: date\n - name: date_to\n in: query\n description: Issue date to (YYYY-MM-DD)\n schema:\n type: string\n format: date\n - name: invoice_number\n in: query\n description: Search by invoice number (e.g., 2025/0001)\n schema:\n type: string\n - name: recipient_name\n in: query\n description: Filter by recipient's fiscal name (partial, case-insensitive search)\n schema:\n type: string\n - name: recipient_nif\n in: query\n description: Filter by recipient's NIF (partial search)\n schema:\n type: string\n - name: series_code\n in: query\n description: Filter by series code\n schema:\n type: string\n - name: taxable_base_min\n in: query\n description: Minimum taxable base\n schema:\n type: number\n format: double\n - name: taxable_base_max\n in: query\n description: Maximum taxable base\n schema:\n type: number\n format: double\n - name: total_min\n in: query\n description: Minimum invoice total\n schema:\n type: number\n format: double\n - name: total_max\n in: query\n description: Maximum invoice total\n schema:\n type: number\n format: double\n - name: verifactu_status\n in: query\n description: |\n Filter by VeriFactu status. Special values:\n - \"NO_VERIFACTU\": Invoices where VeriFactu is not enabled\n - \"PENDING\": Invoices with VeriFactu enabled pending submission\n - \"ACCEPTED\": Invoices submitted and accepted by AEAT\n - \"REJECTED\": Invoices submitted and rejected by AEAT\n schema:\n type: string\n enum:\n - NO_VERIFACTU\n - PENDING\n - ACCEPTED\n - REJECTED\n - name: sort_by\n in: query\n description: Field to sort by (e.g., issue_date, invoice_number, invoice_total)\n schema:\n type: string\n - name: sort_order\n in: query\n description: Sort direction\n schema:\n type: string\n enum:\n - asc\n - desc\n default: desc\n responses:\n '200':\n description: Invoice list retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n required:\n - invoices\n - pagination\n properties:\n invoices:\n type: array\n items:\n $ref: '#/components/schemas/Invoice'\n pagination:\n $ref: '#/components/schemas/Pagination'\n examples:\n list_success:\n $ref: '#/components/examples/list-invoices-success'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n post:\n tags:\n - Invoices\n summary: Create invoice\n description: |\n Creates a new invoice.\n\n **Idempotency:** Supports `Idempotency-Key` header to prevent duplicates.\n operationId: createInvoice\n parameters:\n - name: Idempotency-Key\n in: header\n description: UUID to prevent duplicates (recommended)\n required: false\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateInvoiceRequest'\n examples:\n basic_invoice:\n $ref: '#/components/examples/create-basic'\n adhoc_invoice:\n $ref: '#/components/examples/create-with-adhoc-customer'\n invoice_irpf:\n $ref: '#/components/examples/create-with-irpf'\n invoice_surcharge:\n $ref: '#/components/examples/create-with-equivalence-surcharge'\n invoice_igic:\n $ref: '#/components/examples/create-with-igic'\n invoice_foreign:\n $ref: '#/components/examples/create-foreign-customer'\n issue_directly:\n $ref: '#/components/examples/create-issue-directly'\n custom_email:\n $ref: '#/components/examples/create-with-custom-email'\n simplified_without_nif:\n $ref: '#/components/examples/simplified-without-nif'\n simplified_with_nif:\n $ref: '#/components/examples/simplified-with-nif'\n responses:\n '201':\n description: Invoice created successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n examples:\n created_success:\n $ref: '#/components/examples/created-success'\n '400':\n $ref: '#/components/responses/InvalidJsonFormat'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '409':\n description: Conflict - Idempotency key already processed or duplicate invoice number\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n examples:\n conflict:\n $ref: '#/components/examples/conflict-409'\n '422':\n description: Validation error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n examples:\n validation_error:\n $ref: '#/components/examples/validation-422'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}:\n get:\n tags:\n - Invoices\n summary: Get invoice by ID\n description: Retrieves complete details of a specific invoice\n operationId: getInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '200':\n description: Invoice retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n examples:\n get_success:\n $ref: '#/components/examples/get-invoice-success'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n put:\n tags:\n - Invoices\n summary: Update invoice\n description: |\n Updates an existing draft invoice.\n\n **Restriction:** Only invoices in `DRAFT` status can be modified.\n operationId: updateInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UpdateInvoiceRequest'\n examples:\n update_invoice:\n $ref: '#/components/examples/update-invoice'\n responses:\n '200':\n description: Invoice updated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n $ref: '#/components/responses/InvalidJsonFormat'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '422':\n $ref: '#/components/responses/ValidationError'\n '500':\n $ref: '#/components/responses/InternalServerError'\n delete:\n tags:\n - Invoices\n summary: Delete invoice\n description: |\n Deletes (marks as deleted) an invoice.\n\n **Soft delete:** The invoice is not physically deleted, only marked as deleted.\n\n **Restriction:** Only invoices in `DRAFT` status can be deleted.\n operationId: deleteInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '204':\n description: Invoice deleted successfully\n '400':\n description: Cannot delete (invoice already sent/paid)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/bulk/pdf:\n post:\n tags:\n - InvoiceDelivery\n summary: Download multiple invoice PDFs as ZIP\n description: |\n Downloads multiple invoice PDFs as a single ZIP file.\n\n **Limits:**\n - Maximum 20 invoices per request\n - Maximum 50MB total ZIP size\n\n **Partial success:** If some PDFs are not available (not generated, not found),\n the response will include a `failures` array with details. The ZIP will still\n be generated with the available PDFs.\n\n **Error handling:**\n - If NO PDFs are available, returns 400 Bad Request\n - If some PDFs are unavailable, returns 200 with partial ZIP + failures list\n operationId: downloadInvoicesPdfBulk\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - invoice_ids\n properties:\n invoice_ids:\n type: array\n description: List of invoice IDs to download\n minItems: 1\n maxItems: 500\n items:\n $ref: '#/components/schemas/UUID'\n examples:\n download_5_invoices:\n summary: Download 5 invoices\n value:\n invoice_ids:\n - 550e8400-e29b-41d4-a716-446655440001\n - 550e8400-e29b-41d4-a716-446655440002\n - 550e8400-e29b-41d4-a716-446655440003\n - 550e8400-e29b-41d4-a716-446655440004\n - 550e8400-e29b-41d4-a716-446655440005\n responses:\n '200':\n description: ZIP file with invoice PDFs\n headers:\n X-Bulk-Total:\n description: Total number of requested invoices\n schema:\n type: integer\n X-Bulk-Successful:\n description: Number of PDFs included in ZIP\n schema:\n type: integer\n X-Bulk-Failed:\n description: Number of PDFs that could not be included\n schema:\n type: integer\n X-Bulk-Failures:\n description: JSON array with failure details (base64 encoded if present)\n schema:\n type: string\n content:\n application/zip:\n schema:\n type: string\n format: binary\n '400':\n description: No valid PDFs available or invalid request\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n description: Validation error (too many invoices, empty list)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/bulk/send:\n post:\n tags:\n - InvoiceDelivery\n summary: Send multiple invoices by email\n description: |\n Sends multiple invoices by email to the specified recipients.\n\n **Limits:**\n - Maximum 20 invoices per request\n - Maximum 25MB total attachment size\n\n **Email behavior:**\n - Single email with all PDFs as attachments\n - Uses BULK_INVOICES template\n\n **Validations:**\n - Invoices must have status ISSUED, PAID, SENT or OVERDUE (not DRAFT or VOIDED)\n - At least one recipient required (from request or user's accountant email)\n\n **Partial success:** If some invoices cannot be attached, the email will\n still be sent with available PDFs. Response includes `failures` array.\n operationId: sendInvoicesBulkEmail\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - invoice_ids\n properties:\n invoice_ids:\n type: array\n description: List of invoice IDs to send\n minItems: 1\n maxItems: 20\n items:\n $ref: '#/components/schemas/UUID'\n recipients:\n type: array\n description: |\n Email recipients. If not provided, uses accountant email from user profile.\n items:\n $ref: '#/components/schemas/Email'\n cc:\n type: array\n description: CC recipients\n items:\n $ref: '#/components/schemas/Email'\n subject:\n type: string\n description: Custom email subject. If not provided, uses default template.\n maxLength: 200\n message:\n type: string\n description: Custom message body. If not provided, uses default template.\n maxLength: 2000\n language:\n allOf:\n - $ref: '#/components/schemas/Language'\n description: Email language. If not provided, uses user's language.\n examples:\n send_to_accountant:\n summary: Send to accountant\n value:\n invoice_ids:\n - 550e8400-e29b-41d4-a716-446655440001\n - 550e8400-e29b-41d4-a716-446655440002\n recipients:\n - accountant@example.com\n subject: Invoices for January 2025\n message: Please find attached the invoices from last month for your review.\n send_with_cc:\n summary: Send with CC\n value:\n invoice_ids:\n - 550e8400-e29b-41d4-a716-446655440001\n recipients:\n - accountant@example.com\n cc:\n - copy@example.com\n responses:\n '200':\n description: Email sent successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n required:\n - email_id\n - sent_to\n - sent_at\n - total_invoices\n - invoices_attached\n properties:\n email_id:\n $ref: '#/components/schemas/UUID'\n sent_to:\n type: array\n items:\n $ref: '#/components/schemas/Email'\n sent_at:\n type: string\n format: date-time\n total_invoices:\n type: integer\n description: Total number of requested invoices\n invoices_attached:\n type: integer\n description: Number of PDFs successfully attached\n failures:\n type: array\n description: List of invoices that could not be attached\n items:\n type: object\n properties:\n invoice_id:\n $ref: '#/components/schemas/UUID'\n error_code:\n type: string\n enum:\n - NOT_FOUND\n - PDF_NOT_GENERATED\n - INVALID_STATUS\n - INTERNAL_ERROR\n error_message:\n type: string\n '400':\n description: No valid invoices to send or no recipients specified\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n description: Validation error (too many invoices, invalid recipients)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/bulk/status:\n post:\n tags:\n - InvoiceDelivery\n summary: Change status of multiple invoices\n description: |\n Changes the status of multiple invoices in a single operation.\n\n **Supported status changes:**\n - **SENT:** Mark multiple invoices as sent (from ISSUED)\n - **PAID:** Mark multiple invoices as paid (from ISSUED, SENT, or OVERDUE)\n\n **Partial success:** If some invoices cannot be updated, the operation\n continues with the others. The response includes details of failures.\n\n **Limits:**\n - Maximum 50 invoices per request\n\n **Validations:**\n - All invoices must belong to the authenticated user\n - Each invoice must be in a valid status for the requested transition\n - For PAID: payment_date is required\n operationId: changeInvoicesStatusBulk\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - invoice_ids\n - new_status\n properties:\n invoice_ids:\n type: array\n description: List of invoice IDs to update\n minItems: 1\n maxItems: 50\n items:\n $ref: '#/components/schemas/UUID'\n new_status:\n type: string\n description: Target status for the invoices\n enum:\n - SENT\n - PAID\n payment_date:\n type: string\n format: date\n description: Payment date (required when new_status is PAID)\n example: '2025-01-15'\n examples:\n mark_as_sent:\n summary: Mark invoices as sent\n value:\n invoice_ids:\n - 550e8400-e29b-41d4-a716-446655440001\n - 550e8400-e29b-41d4-a716-446655440002\n new_status: SENT\n mark_as_paid:\n summary: Mark invoices as paid\n value:\n invoice_ids:\n - 550e8400-e29b-41d4-a716-446655440001\n - 550e8400-e29b-41d4-a716-446655440002\n new_status: PAID\n payment_date: '2025-01-15'\n responses:\n '200':\n description: Status change operation completed (may include partial failures)\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n required:\n - total\n - successful\n - failed\n properties:\n total:\n type: integer\n description: Total number of invoices processed\n example: 5\n successful:\n type: integer\n description: Number of invoices successfully updated\n example: 4\n failed:\n type: integer\n description: Number of invoices that failed to update\n example: 1\n failures:\n type: array\n description: Details of failed updates\n items:\n type: object\n required:\n - invoice_id\n - reason\n properties:\n invoice_id:\n $ref: '#/components/schemas/UUID'\n reason:\n type: string\n description: Reason for the failure\n example: Cannot change from PAID to SENT\n '400':\n description: Invalid request (empty list, missing payment_date for PAID)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n description: Validation error (unsupported status, too many invoices)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/pdf:\n get:\n tags:\n - InvoiceDelivery\n summary: Get invoice PDF download URL\n description: |\n Generates a temporary pre-signed URL to download the invoice PDF.\n\n **Security:**\n - URL expires automatically in 5 minutes\n - HMAC signature prevents tampering\n - Only allows download (GET), no modification\n\n **Benefits:**\n - Direct download from storage (bypasses backend)\n - Better performance and lower server load\n - Compatible with browser cache and CDN\n - Ideal for large files\n\n **Frontend usage:**\n ```javascript\n const response = await fetch('/v1/invoices/{id}/pdf');\n const { download_url } = await response.json();\n window.open(download_url, '_blank'); // Direct download\n ```\n\n **Format:** PDF is generated according to Spanish tax regulations.\n operationId: generateInvoicePdf\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '200':\n description: Download URL generated successfully\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/InvoicePdfResponse'\n '400':\n description: PDF has not been generated yet\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/pdf/preview:\n get:\n tags:\n - InvoiceDelivery\n summary: Preview draft invoice PDF\n description: |\n Generates a PDF preview for a draft invoice and returns the binary content directly.\n\n **Requirements:**\n - Invoice must be in BORRADOR (draft) status\n - The PDF shows \"BORRADOR\" as the invoice number\n - No VeriFactu QR code is included\n\n **Behavior:**\n - PDF is generated on-the-fly (not stored)\n - Each request regenerates the PDF (reflects latest draft changes)\n - Response is `application/pdf` for direct browser preview\n operationId: previewDraftInvoicePdf\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID (must be a draft)\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '200':\n description: PDF preview generated successfully\n content:\n application/pdf:\n schema:\n type: string\n format: binary\n '400':\n description: Invoice is not in draft status\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/send:\n post:\n tags:\n - InvoiceDelivery\n summary: Send invoice by email\n description: |\n Sends the invoice by email to the customer.\n\n **Email:** Sent to the billing emails configured for the customer.\n\n **Attachments:** Includes the invoice PDF.\n operationId: sendInvoiceEmail\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: false\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/SendEmailRequest'\n responses:\n '200':\n description: Email sent successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n email_id:\n $ref: '#/components/schemas/UUID'\n sent_to:\n type: array\n items:\n $ref: '#/components/schemas/Email'\n sent_at:\n type: string\n format: date-time\n '400':\n description: Customer has no configured emails\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/mark-paid:\n post:\n tags:\n - InvoiceLifecycle\n summary: Mark invoice as paid\n description: |\n Marks an invoice as paid.\n\n **Status change:** `SENT` \u2192 `PAID`\n\n **Note:** Send `Content-Type: application/json` even when body is empty.\n operationId: markInvoicePaid\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: false\n content:\n application/json:\n schema:\n type: object\n properties:\n payment_date:\n type: string\n format: date\n description: Payment date (defaults to today)\n example: '2025-01-15'\n payment_method:\n $ref: '#/components/schemas/PaymentInfo'\n description: |\n Payment details object (not a string enum). Example:\n `{ \"method\": \"BANK_TRANSFER\", \"iban\": \"ES9121000418450200051332\" }`\n responses:\n '200':\n description: Invoice marked as paid successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot mark as paid (invalid status)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/void:\n post:\n tags:\n - InvoiceLifecycle\n summary: Void invoice\n description: |\n Voids an invoice by changing its status to `VOIDED`.\n\n **Status change:** `ISSUED`, `SENT`, `OVERDUE`, `PAID` or `RECTIFIED` \u2192 `VOIDED`\n\n **Use when:** The operation never took place (invoice issued by mistake, duplicate, etc.)\n For invoices already submitted to VeriFactu, the cancellation is sent to AEAT automatically.\n\n **Note:** If the operation did take place but with errors, use the corrective invoice endpoint instead.\n operationId: voidInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - reason\n properties:\n reason:\n type: string\n minLength: 10\n maxLength: 500\n description: Void reason (minimum 10 characters)\n example: Invoice issued with incorrect customer data\n void_date:\n type: string\n format: date\n description: Void date (defaults to today)\n example: '2025-01-20'\n responses:\n '200':\n description: Invoice voided successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot void invoice (invalid status or incorrect data)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/corrective:\n post:\n tags:\n - InvoiceLifecycle\n summary: Create corrective invoice\n description: |\n Creates a corrective invoice to correct or void an issued invoice.\n\n **Rectification types:**\n - **TOTAL**: Completely voids the original invoice (original status \u2192 VOIDED)\n - **PARTIAL**: Partially corrects the original invoice (original status \u2192 RECTIFIED)\n\n **VeriFactu codes:**\n - R1: Legal error and Art. 80 One, Two and Six LIVA\n - R2: Article 80 Three LIVA (Bankruptcy)\n - R3: Article 80 Four LIVA (Bad debts)\n - R4: Other causes\n - R5: Simplified invoices (only for simplified)\n\n **Validations:**\n - Original invoice must be in status ISSUED, SENT, PAID, OVERDUE or RECTIFIED\n - Cannot rectify a DRAFT invoice (must be issued first)\n - Cannot rectify a VOIDED invoice (final status)\n - For TOTAL rectification: Another total corrective cannot already exist\n - For PARTIAL rectification: Multiple partial correctives are allowed\n\n **Lines:**\n - TOTAL: Optional (if not provided, original lines are copied with negated amounts)\n - PARTIAL: REQUIRED (adjustment lines with positive or negative amounts)\n operationId: createCorrectiveInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID to rectify\n schema:\n $ref: '#/components/schemas/UUID'\n - $ref: '#/components/parameters/IdempotencyKeyHeader'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateCorrectiveInvoiceRequest'\n examples:\n corrective_total_r1:\n $ref: '#/components/examples/corrective-total-r1'\n corrective_total_r4:\n $ref: '#/components/examples/corrective-total-r4'\n corrective_partial_r2:\n $ref: '#/components/examples/corrective-partial-r2'\n corrective_partial_r3:\n $ref: '#/components/examples/corrective-partial-r3'\n responses:\n '201':\n description: Corrective invoice created successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Invalid rectification data\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n description: Original invoice not found\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '409':\n description: A total corrective invoice already exists for this invoice\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '422':\n description: Original invoice cannot be rectified (invalid status)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/issue:\n post:\n tags:\n - InvoiceLifecycle\n summary: Issue invoice (DRAFT \u2192 ISSUED)\n description: |\n Finalizes a draft invoice and marks it as issued.\n\n **Status change:** `DRAFT` \u2192 `ISSUED`\n\n **Actions performed:**\n - Assigns definitive invoice number according to configured series\n - Marks invoice as finalized (not modifiable)\n - Prepares invoice for sending to customer\n\n **Validations:**\n - Invoice must be in DRAFT status\n - All invoice data must be complete and valid\n\n **Note:** After issuing, the invoice can be sent to the customer via\n the `/invoices/{id}/send` endpoint\n operationId: issueInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n - name: wait_for_pdf\n in: query\n required: false\n description: |\n If `true`, waits for PDF generation and returns the URL in the response.\n Adds ~1-2s latency but guarantees PDF is immediately available.\n If `false` (default), PDF is generated asynchronously.\n schema:\n type: boolean\n default: false\n responses:\n '200':\n description: Invoice issued successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot issue invoice (invalid status or incomplete data)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '422':\n description: Invoice cannot be issued (validation failed)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/mark-sent:\n post:\n tags:\n - InvoiceLifecycle\n summary: Mark invoice as sent\n description: |\n Marks an invoice as sent manually.\n\n **Use case:** When the invoice was sent via WhatsApp, paper, or any other\n channel outside of the email system.\n\n **Status change:** `ISSUED` \u2192 `SENT`\n\n **Records:** Sets `sent_at` timestamp for tracking.\n\n **Note:** Send `Content-Type: application/json` even when body is empty.\n operationId: markInvoiceSent\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: false\n content:\n application/json:\n schema:\n type: object\n properties:\n sent_at:\n type: string\n format: date-time\n description: |\n Custom timestamp for when the invoice was sent.\n If not provided, the current time will be used.\n example: '2025-01-29T18:45:00Z'\n responses:\n '200':\n description: Invoice marked as sent successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot mark as sent (invalid status - must be ISSUED)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/revert-to-issued:\n post:\n tags:\n - InvoiceLifecycle\n summary: Revert invoice to issued status\n description: |\n Reverts an invoice from SENT to ISSUED status.\n\n **Use case:** To correct errors when an invoice was marked as sent by mistake.\n\n **Status change:** `SENT` \u2192 `ISSUED`\n\n **Side effects:** Clears the `sent_at` timestamp.\n operationId: revertInvoiceToIssued\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '200':\n description: Invoice reverted to ISSUED successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot revert (invoice must be in SENT status)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/schedule:\n post:\n tags:\n - InvoiceLifecycle\n summary: Schedule invoice for future processing\n description: |\n Schedules a draft invoice to be automatically processed on a future date.\n\n **Status change:** `DRAFT` \u2192 `SCHEDULED`\n\n **Actions available:**\n - `DRAFT`: Convert to draft for manual review on the scheduled date\n - `ISSUE_AND_SEND`: Issue and send automatically on the scheduled date\n operationId: scheduleInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - scheduled_for\n properties:\n scheduled_for:\n type: string\n format: date\n description: Date when the invoice should be processed (must be today or future)\n example: '2025-02-15'\n action:\n $ref: '#/components/schemas/GenerationAction'\n responses:\n '200':\n description: Invoice scheduled successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot schedule (invalid date or invoice state)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/unschedule:\n post:\n tags:\n - InvoiceLifecycle\n summary: Cancel scheduled invoice\n description: |\n Cancels a scheduled invoice and converts it back to draft.\n\n **Status change:** `SCHEDULED` \u2192 `DRAFT`\n\n The invoice can then be edited or manually issued.\n operationId: unscheduleInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '200':\n description: Invoice unscheduled successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot unschedule (invoice must be in SCHEDULED status)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/reschedule:\n patch:\n tags:\n - InvoiceLifecycle\n summary: Reschedule invoice\n description: |\n Changes the scheduled date for a scheduled invoice.\n\n **Status:** Remains `SCHEDULED`\n\n The new date must be today or in the future.\n operationId: rescheduleInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: Invoice ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - scheduled_for\n properties:\n scheduled_for:\n type: string\n format: date\n description: New date when the invoice should be processed\n example: '2025-03-01'\n responses:\n '200':\n description: Invoice rescheduled successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot reschedule (invalid date or invoice state)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/{invoice_id}/duplicate:\n post:\n tags:\n - Invoices\n summary: Duplicate invoice as draft\n description: |\n Creates a copy of an existing invoice as a new draft.\n\n **Use case:** Quickly create a new invoice based on an existing one,\n useful for recurring invoices to the same customer.\n\n **What is copied:**\n - Customer data (recipient)\n - Invoice lines (descriptions, quantities, prices, taxes)\n - Payment method\n - Series (if specified, otherwise uses default)\n - Observations\n\n **What is NOT copied (reset to defaults):**\n - Invoice number (null - assigned when issued)\n - Status (always DRAFT)\n - Issue date (always set to today)\n - Due date (recalculated based on new issue date)\n - VeriFactu data\n - PDF\n - Payment date\n - Sent date\n\n **Result:** A new draft invoice ready for review and emission.\n\n **Note:** Send `Content-Type: application/json` even when body is empty.\n operationId: duplicateInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n description: ID of the invoice to duplicate\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: false\n content:\n application/json:\n schema:\n type: object\n properties:\n series_id:\n allOf:\n - $ref: '#/components/schemas/UUID'\n description: |\n Series ID for the new invoice.\n If not provided, uses the same series as the original.\n notes:\n type: string\n maxLength: 2000\n description: |\n Observations for the new invoice.\n If not provided, copies from the original invoice.\n examples:\n simple_duplicate:\n summary: Simple duplication (defaults)\n value: {}\n duplicate_other_series:\n summary: Duplication to different series\n value:\n series_id: 550e8400-e29b-41d4-a716-446655440099\n responses:\n '201':\n description: Invoice duplicated successfully as draft\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '400':\n description: Cannot duplicate invoice (e.g., invoice is deleted)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n description: Original invoice not found\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/invoices/export/excel:\n post:\n tags:\n - InvoiceDelivery\n summary: Export invoices to Excel\n description: |\n Exports invoices to an Excel file (XLSX format).\n\n **Two modes of operation:**\n - **By IDs:** Export specific invoices by providing `invoice_ids`\n - **By filters:** Export all invoices matching filter criteria\n\n **Two export formats (like Holded):**\n - **SUMMARY:** One row per invoice with aggregated totals (default)\n - **ITEMS:** One row per invoice line item, ideal for import compatibility\n\n **Columns for SUMMARY format:**\n - Date, Number, Due Date, Customer, NIF\n - Description, Subtotal, IVA, Withholding, Equivalence Surcharge\n - Total, Collected, Pending, Status, Payment Method\n\n **Columns for ITEMS format:**\n - Invoice Number, Date, Due Date, Draft, Contact, Contact NIF\n - Address, Postal Code, City, Province, Country\n - SKU, Item, Units, Unit Price, Discount %\n - Subtotal, Tax Type, Tax %, Tax Amount\n - Surcharge %, Surcharge Amount, IRPF %, IRPF Amount, Total\n\n **Limits:**\n - Maximum 5000 invoices per export\n - All invoices must belong to the authenticated user (RLS enforced)\n\n **Usage example (Frontend):**\n ```javascript\n const response = await fetch('/v1/invoices/export/excel', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ invoice_ids: [...], format: 'ITEMS' })\n });\n const blob = await response.blob();\n // Download the blob as file\n ```\n operationId: exportInvoicesExcel\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n properties:\n invoice_ids:\n type: array\n description: |\n List of specific invoice IDs to export.\n If provided, filters are ignored.\n items:\n $ref: '#/components/schemas/UUID'\n format:\n type: string\n description: |\n Export format.\n - **SUMMARY**: One row per invoice with totals (default)\n - **ITEMS**: One row per invoice line item\n enum:\n - SUMMARY\n - ITEMS\n default: SUMMARY\n status:\n $ref: '#/components/schemas/InvoiceStatus'\n type:\n $ref: '#/components/schemas/InvoiceType'\n date_from:\n type: string\n format: date\n description: Issue date from (YYYY-MM-DD)\n date_to:\n type: string\n format: date\n description: Issue date to (YYYY-MM-DD)\n customer_id:\n $ref: '#/components/schemas/UUID'\n recipient_name:\n type: string\n description: Filter by recipient name (partial match)\n recipient_nif:\n type: string\n description: Filter by recipient NIF (partial match)\n series_code:\n type: string\n description: Filter by series code\n examples:\n export_by_ids:\n summary: Export specific invoices (default format)\n value:\n invoice_ids:\n - 550e8400-e29b-41d4-a716-446655440001\n - 550e8400-e29b-41d4-a716-446655440002\n - 550e8400-e29b-41d4-a716-446655440003\n export_items_format:\n summary: Export as items (one row per line)\n value:\n invoice_ids:\n - 550e8400-e29b-41d4-a716-446655440001\n format: ITEMS\n export_q1_2025:\n summary: Export Q1 2025 invoices\n value:\n date_from: '2025-01-01'\n date_to: '2025-03-31'\n export_paid:\n summary: Export all paid invoices\n value:\n status: PAID\n responses:\n '200':\n description: Excel file generated successfully\n headers:\n Content-Disposition:\n description: Suggested filename for download\n schema:\n type: string\n example: attachment; filename=\"facturas_2025-01-15.xlsx\"\n X-Total-Invoices:\n description: Number of invoices included in the export\n schema:\n type: integer\n content:\n application/vnd.openxmlformats-officedocument.spreadsheetml.sheet:\n schema:\n type: string\n format: binary\n '400':\n description: Invalid request (must provide invoice_ids or filters)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n description: Validation error (too many invoices, invalid filters)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/recurring-invoices:\n post:\n tags:\n - RecurringInvoices\n summary: Create a recurring invoice from scratch\n description: Creates a new recurring invoice template with all template data (lines, recipient, series, payment) and recurrence configuration, without needing an existing invoice.\n operationId: createRecurringInvoice\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateRecurringInvoiceRequest'\n responses:\n '201':\n description: Recurring invoice created successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/RecurringInvoiceResponse'\n '422':\n $ref: '#/components/responses/ValidationError'\n get:\n tags:\n - RecurringInvoices\n summary: List recurring invoices\n description: Lists all recurring invoices for the authenticated user with filters and pagination.\n operationId: listRecurringInvoices\n parameters:\n - name: status\n in: query\n required: false\n schema:\n type: string\n enum:\n - ACTIVE\n - PAUSED\n - COMPLETED\n - name: customer_id\n in: query\n required: false\n schema:\n type: string\n format: uuid\n - $ref: '#/components/parameters/PageParam'\n - $ref: '#/components/parameters/LimitParam'\n - name: sortBy\n in: query\n required: false\n schema:\n type: string\n enum:\n - name\n - next_generation\n - status\n - created_at\n default: created_at\n - name: sortOrder\n in: query\n required: false\n schema:\n type: string\n enum:\n - asc\n - desc\n default: desc\n responses:\n '200':\n description: Recurring invoices list retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: array\n items:\n $ref: '#/components/schemas/RecurringInvoiceResponse'\n pagination:\n $ref: '#/components/schemas/Pagination'\n '401':\n $ref: '#/components/responses/Unauthorized'\n /v1/recurring-invoices/{recurring_invoice_id}:\n get:\n tags:\n - RecurringInvoices\n summary: Get a recurring invoice by ID\n description: Retrieves the full details of a recurring invoice including its schedule, template lines, and next generation date.\n operationId: getRecurringInvoice\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: Recurring invoice retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/RecurringInvoiceResponse'\n '404':\n $ref: '#/components/responses/NotFound'\n put:\n tags:\n - RecurringInvoices\n summary: Update a recurring invoice\n description: Updates the schedule, template lines, or recipient of a recurring invoice. Only allowed while the invoice is active or paused.\n operationId: updateRecurringInvoice\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UpdateRecurringInvoiceRequest'\n responses:\n '200':\n description: Recurring invoice updated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/RecurringInvoiceResponse'\n '404':\n $ref: '#/components/responses/NotFound'\n '422':\n $ref: '#/components/responses/ValidationError'\n delete:\n tags:\n - RecurringInvoices\n summary: Delete a recurring invoice\n description: Permanently deletes a recurring invoice and cancels any pending scheduled generations.\n operationId: deleteRecurringInvoice\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '204':\n description: Recurring invoice deleted successfully\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/recurring-invoices/{recurring_invoice_id}/pause:\n post:\n tags:\n - RecurringInvoices\n summary: Pause a recurring invoice\n description: Pauses automatic invoice generation. The recurring invoice can be resumed later without losing its schedule configuration.\n operationId: pauseRecurringInvoice\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: Recurring invoice paused successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/RecurringInvoiceResponse'\n '400':\n $ref: '#/components/responses/ValidationError'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/recurring-invoices/{recurring_invoice_id}/resume:\n post:\n tags:\n - RecurringInvoices\n summary: Resume a paused recurring invoice\n description: Resumes automatic invoice generation for a previously paused recurring invoice. The next generation date is recalculated from the current date.\n operationId: resumeRecurringInvoice\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: Recurring invoice resumed successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/RecurringInvoiceResponse'\n '400':\n $ref: '#/components/responses/ValidationError'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/recurring-invoices/{recurring_invoice_id}/generate:\n post:\n tags:\n - RecurringInvoices\n summary: Generate an invoice now from a recurring template\n description: Manually triggers invoice generation from a recurring template.\n operationId: generateInvoiceNow\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '201':\n description: Invoice generated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n invoice_id:\n type: string\n format: uuid\n '400':\n $ref: '#/components/responses/ValidationError'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/recurring-invoices/{recurring_invoice_id}/skip:\n post:\n tags:\n - RecurringInvoices\n summary: Skip the next generation\n description: Skips the next scheduled invoice generation and advances the generation date to the following period.\n operationId: skipNextGeneration\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: Next generation skipped successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/RecurringInvoiceResponse'\n '400':\n $ref: '#/components/responses/ValidationError'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/recurring-invoices/{recurring_invoice_id}/preview:\n get:\n tags:\n - RecurringInvoices\n summary: Preview next invoice from recurring template\n description: |\n Returns a computed preview of what the next invoice would look like when\n generated from this recurring template. Uses current issuer, recipient,\n and series data. Nothing is persisted.\n operationId: previewRecurringInvoice\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n description: UUID of the recurring invoice template\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: Invoice preview computed successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Invoice'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/recurring-invoices/{recurring_invoice_id}/history:\n get:\n tags:\n - RecurringInvoices\n summary: Get generation history for a recurring invoice\n description: Returns the list of invoices previously generated from this recurring template, including their status and generation dates.\n operationId: getRecurringHistory\n parameters:\n - name: recurring_invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: Generation history retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: array\n items:\n $ref: '#/components/schemas/GenerationHistoryResponse'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/invoices/{invoice_id}/create-recurring:\n post:\n tags:\n - RecurringInvoices\n summary: Create a recurring invoice from an existing invoice\n description: Creates a new recurring invoice template using the lines, recipient, and configuration from an existing invoice.\n operationId: createRecurringFromInvoice\n parameters:\n - name: invoice_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateRecurringFromInvoiceRequest'\n responses:\n '201':\n description: Recurring invoice created from existing invoice\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/RecurringInvoiceResponse'\n '403':\n $ref: '#/components/responses/Forbidden'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/customers:\n get:\n tags:\n - Customers\n summary: List customers\n description: Returns a paginated list of customers with optional filters\n operationId: listCustomers\n parameters:\n - $ref: '#/components/parameters/PageParam'\n - $ref: '#/components/parameters/LimitParam'\n - name: active\n in: query\n description: Filter by active/inactive status\n schema:\n type: boolean\n - name: search\n in: query\n description: Global search by name, NIF or email\n schema:\n type: string\n - name: legal_name\n in: query\n description: Filter by legal name (partial search case-insensitive)\n schema:\n type: string\n - name: nif\n in: query\n description: Filter by NIF (partial search)\n schema:\n type: string\n - name: email\n in: query\n description: Filter by email (partial search)\n schema:\n type: string\n - name: phone\n in: query\n description: Filter by phone (partial search)\n schema:\n type: string\n - name: city\n in: query\n description: Filter by city\n schema:\n type: string\n - name: province\n in: query\n description: Filter by province\n schema:\n type: string\n - name: sort_by\n in: query\n description: Field to sort by (eg. legal_name, nif, created_at)\n schema:\n type: string\n - name: sort_order\n in: query\n description: Sort order direction\n schema:\n type: string\n enum:\n - asc\n - desc\n default: asc\n responses:\n '200':\n description: Customer list retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n required:\n - customers\n - pagination\n properties:\n customers:\n type: array\n items:\n $ref: '#/components/schemas/Customer'\n pagination:\n $ref: '#/components/schemas/Pagination'\n examples:\n list_success:\n $ref: '#/components/examples/list-customers-success'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n post:\n tags:\n - Customers\n summary: Create customer\n description: Creates a new customer\n operationId: createCustomer\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateCustomerRequest'\n examples:\n cliente_empresa:\n $ref: '#/components/examples/company-customer'\n cliente_autonomo:\n $ref: '#/components/examples/freelancer-customer'\n cliente_extranjero_ue:\n $ref: '#/components/examples/eu_customer'\n cliente_extranjero_usa:\n $ref: '#/components/examples/us_customer'\n responses:\n '201':\n description: Customer created successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Customer'\n examples:\n created_success:\n $ref: '#/components/examples/created-success-2'\n '400':\n $ref: '#/components/responses/InvalidJsonFormat'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '409':\n description: Duplicate customer (same NIF)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n examples:\n conflict:\n $ref: '#/components/examples/conflict-409'\n '422':\n description: Validation error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n examples:\n validation_error:\n $ref: '#/components/examples/validation-422'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/customers/{customer_id}:\n get:\n tags:\n - Customers\n summary: Get customer by ID\n description: Retrieves complete details of a specific customer\n operationId: getCustomer\n parameters:\n - name: customer_id\n in: path\n required: true\n description: Customer ID\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '200':\n description: Customer retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Customer'\n examples:\n get_success:\n $ref: '#/components/examples/get-customer-success'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n put:\n tags:\n - Customers\n summary: Update customer\n description: Updates an existing customer\n operationId: updateCustomer\n parameters:\n - name: customer_id\n in: path\n required: true\n description: Customer ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UpdateCustomerRequest'\n responses:\n '200':\n description: Customer updated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Customer'\n '400':\n $ref: '#/components/responses/InvalidJsonFormat'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '422':\n $ref: '#/components/responses/ValidationError'\n '500':\n $ref: '#/components/responses/InternalServerError'\n delete:\n tags:\n - Customers\n summary: Deactivate customer\n description: |\n Deactivates (marks as deleted) a customer.\n\n **Soft delete:** The customer is not physically deleted, only marked as inactive.\n\n **Restriction:** Cannot deactivate a customer with associated invoices.\n operationId: deactivateCustomer\n parameters:\n - name: customer_id\n in: path\n required: true\n description: Customer ID\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '200':\n description: Customer deactivated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n required:\n - message\n properties:\n message:\n type: string\n example: Customer deactivated successfully\n '400':\n description: Cannot deactivate (has associated invoices)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/customers/bulk:\n post:\n tags:\n - Customer Import\n summary: Create or validate multiple customers\n description: |\n Creates multiple customers in a single operation or validates data without persisting.\n\n **Validation flow:**\n 1. `dry_run=true`: Full validation only (NIFs, duplicates, format) without persistence\n 2. `dry_run=false`: Validation + actual creation\n\n **Validations performed:**\n - Individual validation of each customer (same rules as single customer creation)\n - Detection of duplicate NIFs within the batch\n - Detection of duplicate NIFs against existing database\n - Parallel NIF validation against AEAT registry (VeriFactu)\n - Field format verification (email, phone, etc.)\n\n **Result statuses:**\n - `VALID`: Customer valid, ready to create\n - `WARNING`: Customer valid but with warnings (e.g., missing email)\n - `ERROR`: Customer invalid, cannot process\n - `DUPLICATE`: Duplicate NIF (in batch or existing DB)\n - `NIF_INVALID`: NIF not valid in AEAT registry\n\n **Atomicity:**\n - `dry_run=true`: Validation only, no persistence\n - `dry_run=false`: If any customer fails, ALL customers are rejected (full atomicity)\n operationId: createCustomersBulk\n parameters:\n - name: dry_run\n in: query\n description: |\n Operation mode:\n - `true`: Validation only without persistence (recommended for frontend preview)\n - `false`: Validation + actual creation (atomic operation)\n schema:\n type: boolean\n default: false\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - customers\n properties:\n customers:\n type: array\n minItems: 1\n maxItems: 500\n items:\n $ref: '#/components/schemas/CreateCustomerRequest'\n responses:\n '200':\n description: |\n Validation completed successfully (dry_run=true).\n Returns validation results without persisting data using unified format.\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/CustomerValidationUnifiedResult'\n '201':\n description: |\n Customers created successfully (dry_run=false).\n Atomic operation: all valid customers were created successfully.\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n customers:\n type: array\n items:\n $ref: '#/components/schemas/Customer'\n total_created:\n type: integer\n example: 5\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n description: Validation error in one or more customers\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/ErrorResponse'\n - type: object\n properties:\n error:\n type: object\n properties:\n code:\n type: string\n example: BULK_VALIDATION_ERROR\n message:\n type: string\n errors:\n type: array\n items:\n type: object\n properties:\n index:\n type: integer\n description: Index of the customer with error (0-based)\n field:\n type: string\n message:\n type: string\n '500':\n $ref: '#/components/responses/InternalServerError'\n delete:\n tags:\n - Customer Import\n summary: Delete multiple customers\n description: |\n Deactivates (marks as deleted) multiple customers in a single operation.\n\n **Soft delete:** Customers are not physically deleted, only marked as inactive.\n\n **Partial behavior:** Valid customers will be deactivated, failures are reported in the errors array.\n operationId: deactivateCustomersBulk\n parameters:\n - name: ids\n in: query\n required: true\n description: Comma-separated customer IDs\n schema:\n type: string\n example: 550e8400-e29b-41d4-a716-446655440000,550e8400-e29b-41d4-a716-446655440001\n responses:\n '200':\n description: Operation completed (may have partial errors)\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n required:\n - total\n - successful\n - failed\n - deactivated_ids\n properties:\n total:\n type: integer\n description: Total customers processed\n example: 10\n successful:\n type: integer\n description: Number of successful deactivations\n example: 8\n failed:\n type: integer\n description: Number of failed deactivations\n example: 2\n deactivated_ids:\n type: array\n description: IDs of successfully deactivated customers\n items:\n $ref: '#/components/schemas/UUID'\n errors:\n type: array\n description: Details of customers that could not be deactivated\n items:\n type: object\n required:\n - customer_id\n - error\n properties:\n customer_id:\n $ref: '#/components/schemas/UUID'\n error:\n type: object\n required:\n - code\n - message\n properties:\n code:\n type: string\n example: CLIENT_HAS_INVOICES\n message:\n type: string\n example: Cannot deactivate customer because it has associated invoices\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/customers/import-csv-preview:\n post:\n tags:\n - Customer Import\n summary: Import customers from CSV with structured preview\n description: |\n Imports multiple customers from a CSV file with full preview of all records.\n\n **Key features**:\n - **Structured preview**: Returns all parsed records (valid and with errors)\n - **Parallel validation**: NIFs validated concurrently against VeriFactu\n - **Granular feedback**: Customer-specific errors and warnings\n - **Editable data**: Structured JSON for editable table in frontend\n\n **Recommended flow**:\n 1. **Initial upload**: Call with `dry_run=true` to get preview\n 2. **Display table**: Frontend shows parsed data with errors/warnings\n 3. **Inline editing**: User corrects data directly in the table\n 4. **Final import**: Send corrected data with `dry_run=false`\n\n **Format**: Must follow CSV template format (downloadable from `/customers/template-csv`).\n\n **Security limits**:\n - Max size: 5MB\n - Max 1,000 records per file\n - Rate limiting: 3 imports per hour\n\n **Validation**:\n - Required headers per template\n - Full validation of each row with business rules\n - Parallel NIF validation against AEAT registry (VeriFactu)\n - Duplicate detection in CSV and database\n\n **Validation statuses**:\n - `VALID`: Customer valid, ready to import\n - `WARNING`: Customer valid but with warnings (eg: missing email)\n - `ERROR`: Customer invalid, cannot process\n - `DUPLICATE`: Duplicate customer (in CSV or DB)\n - `NIF_INVALID`: NIF invalid in AEAT registry\n operationId: importCustomersCsvPreview\n requestBody:\n required: true\n content:\n multipart/form-data:\n schema:\n type: object\n required:\n - file\n properties:\n file:\n type: string\n format: binary\n description: CSV file with customers to import\n dry_run:\n type: boolean\n default: true\n description: |\n If true (recommended initially): preview only without persistence.\n If false: preview + persistence of valid customers.\n responses:\n '200':\n description: CSV processing completed with structured preview using unified format\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/CustomerValidationUnifiedResult'\n '400':\n description: |\n Invalid file for one of the following reasons:\n - Format is not valid CSV\n - Encoding is not UTF-8\n - Required headers missing\n - Size exceeds 5MB\n - More than 1,000 records\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/ErrorResponse'\n - type: object\n properties:\n error:\n type: object\n properties:\n details:\n type: object\n properties:\n file_size_mb:\n type: number\n description: File size in MB\n record_count:\n type: integer\n description: Number of records found\n missing_headers:\n type: array\n items:\n type: string\n description: Required headers that are missing\n '401':\n $ref: '#/components/responses/Unauthorized'\n '413':\n description: File too large (>5MB)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '422':\n description: |\n Structural CSV validation error (corrupted format, invalid encoding)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '429':\n description: Rate limit exceeded (maximum 3 imports per hour)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/customers/import-holded-contacts:\n post:\n tags:\n - Customer Import\n summary: Import contacts from Holded Excel\n description: |\n Imports multiple customers from an Excel file exported from Holded.\n\n **Key features**:\n - **Specific parsing**: Reads exact structure of Holded contacts Excel (29 columns)\n - **Automatic mapping**: Converts Holded contacts to Customer entities automatically\n - **Full validation**: Validates NIFs, addresses and required data\n - **Preview mode**: Allows viewing parsed data before importing\n\n **Recommended flow**:\n 1. **Initial upload**: Call with `preview=true` to get preview\n 2. **Display data**: Frontend shows parsed Holded contacts\n 3. **Final import**: Call with `preview=false` to import definitively\n\n **Expected Excel structure** (contacts exported from Holded):\n - Sheet: \"Holded\"\n - Rows to skip: 2 (header + empty)\n - Headers in row 2: Created, Name, ID, Email, Phone, Mobile, etc. (29 columns)\n - Footer: \"Report created automatically with Holded...\"\n\n **Security limits**:\n - Max size: 10MB\n - Max 5,000 records per file\n - Rate limiting: 5 imports per hour\n\n **Automatic Holded \u2192 Customer mapping**:\n - Name \u2192 legal_name\n - ID \u2192 identifier (NIF/CIF)\n - Email \u2192 email\n - Phone/Mobile \u2192 phone (prioritizes mobile)\n - Full address \u2192 address\n - Tags \u2192 notes\n operationId: importHoldedContacts\n requestBody:\n required: true\n content:\n multipart/form-data:\n schema:\n type: object\n required:\n - file\n properties:\n file:\n type: string\n format: binary\n description: Excel file (.xlsx) of contacts exported from Holded\n preview:\n type: boolean\n default: true\n description: |\n If true: parsing and mapping only without importing to DB.\n If false: parsing + mapping + actual import.\n responses:\n '200':\n description: |\n Holded Excel processing completed with unified format.\n Returns parsed, validated and mapped customers using the same format as CSV import.\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/CustomerValidationUnifiedResult'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '413':\n description: File too large (>10MB)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '422':\n $ref: '#/components/responses/ValidationError'\n '429':\n description: Rate limit exceeded (maximum 5 imports per hour)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/customers/templates/csv:\n post:\n tags:\n - Customer Templates\n summary: Download CSV template for customers\n description: |\n Downloads a sample CSV file for customer import.\n\n **Format**: The file includes the required headers and example rows\n with UTF-8 BOM for Excel compatibility.\n operationId: downloadCustomerTemplateCsv\n responses:\n '200':\n description: CSV template downloaded successfully\n content:\n text/csv:\n schema:\n type: string\n format: binary\n example: |\n nombre_fiscal;nif;email;telefono;direccion_calle;direccion_numero;direccion_codigo_postal;direccion_poblacion;direccion_provincia\n \"Construcciones Garc\xEDa y Asociados SL\";B12345678;contacto@construccionesgarcia.es;915551234;\"Calle Gran V\xEDa\";45;28013;Madrid;Madrid\n headers:\n Content-Disposition:\n description: File name for download\n schema:\n type: string\n example: attachment; filename=\"template_clientes_beel.csv\"\n Content-Type:\n description: File MIME type\n schema:\n type: string\n example: text/csv; charset=utf-8\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/products:\n get:\n tags:\n - Products\n summary: List products/services from catalog\n description: Returns a paginated list of products/services with optional filters\n operationId: listProducts\n parameters:\n - $ref: '#/components/parameters/PageParam'\n - $ref: '#/components/parameters/LimitParam'\n - name: category\n in: query\n description: Filter by product category\n schema:\n $ref: '#/components/schemas/ProductCategory'\n - name: active\n in: query\n description: Filter by active/inactive status\n schema:\n type: boolean\n - name: search\n in: query\n description: Global search by name, code or description\n schema:\n type: string\n maxLength: 100\n - name: name\n in: query\n description: Filter by name (partial search case-insensitive)\n schema:\n type: string\n - name: code\n in: query\n description: Filter by code (partial search)\n schema:\n type: string\n - name: min_price\n in: query\n description: Minimum price\n schema:\n type: number\n minimum: 0\n - name: max_price\n in: query\n description: Maximum price\n schema:\n type: number\n minimum: 0\n - name: sort_by\n in: query\n description: Field to sort by\n schema:\n type: string\n enum:\n - name\n - code\n - category\n - default_price\n - created_at\n default: name\n - name: sort_order\n in: query\n description: Sort order direction\n schema:\n type: string\n enum:\n - asc\n - desc\n default: asc\n responses:\n '200':\n description: Product list retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n required:\n - products\n - pagination\n properties:\n products:\n type: array\n items:\n $ref: '#/components/schemas/Product'\n pagination:\n $ref: '#/components/schemas/Pagination'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n post:\n tags:\n - Products\n summary: Create new product/service\n description: Creates a new product or service in the catalog\n operationId: createProduct\n parameters:\n - name: Idempotency-Key\n in: header\n description: Idempotency key to prevent duplicates\n required: false\n schema:\n type: string\n format: uuid\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateProductRequest'\n responses:\n '201':\n description: Product created successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Product'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '409':\n description: Duplicate code or request already processed\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '422':\n $ref: '#/components/responses/UnprocessableEntity'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/products/{product_id}:\n parameters:\n - name: product_id\n in: path\n required: true\n description: Product unique UUID\n schema:\n type: string\n format: uuid\n get:\n tags:\n - Products\n summary: Get product by ID\n description: Retrieves details of a specific product\n operationId: getProduct\n responses:\n '200':\n description: Product retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Product'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n put:\n tags:\n - Products\n summary: Update product\n description: Updates an existing product\n operationId: updateProduct\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UpdateProductRequest'\n responses:\n '200':\n description: Product updated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Product'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '409':\n description: Duplicate code\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '422':\n $ref: '#/components/responses/UnprocessableEntity'\n '500':\n $ref: '#/components/responses/InternalServerError'\n delete:\n tags:\n - Products\n summary: Delete product\n description: Deletes a product from catalog\n operationId: deleteProduct\n responses:\n '204':\n description: Product deleted successfully\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/products/search:\n get:\n tags:\n - Products\n summary: Search and autocomplete\n description: Optimized endpoint for active product autocomplete\n operationId: searchProducts\n parameters:\n - name: q\n in: query\n description: Search term (empty to get recent products)\n required: false\n schema:\n type: string\n maxLength: 100\n - name: limit\n in: query\n description: Result limit (max 20)\n schema:\n type: integer\n minimum: 1\n maximum: 20\n default: 10\n responses:\n '200':\n description: Search results retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: array\n items:\n $ref: '#/components/schemas/Product'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/products/bulk:\n post:\n tags:\n - Products\n summary: Create multiple products\n description: Creates multiple products in one operation (max 100)\n operationId: createProductsBulk\n parameters:\n - name: Idempotency-Key\n in: header\n description: Idempotency key for the entire operation\n required: false\n schema:\n type: string\n format: uuid\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - products\n properties:\n products:\n type: array\n items:\n $ref: '#/components/schemas/CreateProductRequest'\n minItems: 1\n maxItems: 100\n responses:\n '200':\n description: Bulk operation completed\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n created_products:\n type: array\n items:\n $ref: '#/components/schemas/Product'\n errors:\n type: array\n items:\n type: object\n properties:\n index:\n type: integer\n code:\n type: string\n name:\n type: string\n error:\n type: string\n summary:\n type: object\n properties:\n total_processed:\n type: integer\n successful:\n type: integer\n failed:\n type: integer\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '413':\n description: Payload too large\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n delete:\n tags:\n - Products\n summary: Delete multiple products\n description: Deletes multiple products from catalog (max 100)\n operationId: deleteProductsBulk\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - product_ids\n properties:\n product_ids:\n type: array\n items:\n type: string\n format: uuid\n minItems: 1\n maxItems: 100\n responses:\n '200':\n description: Bulk delete operation completed\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n deleted_products:\n type: array\n items:\n type: string\n format: uuid\n errors:\n type: array\n items:\n type: object\n properties:\n product_id:\n type: string\n format: uuid\n error:\n type: string\n summary:\n type: object\n properties:\n total_processed:\n type: integer\n successful:\n type: integer\n failed:\n type: integer\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '413':\n description: Payload too large\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/configuration/verifactu:\n get:\n tags:\n - ConfigurationVeriFactu\n summary: Get VeriFactu configuration\n description: Retrieves the current VeriFactu configuration\n operationId: getVeriFactuConfiguration\n responses:\n '200':\n description: Configuration retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/VeriFactuConfiguration'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n put:\n tags:\n - ConfigurationVeriFactu\n summary: Update VeriFactu configuration\n description: |\n Updates the user's VeriFactu configuration.\n\n **Business rules:**\n - If `enabled` is false, `apply_by_default` must also be false\n - If `apply_by_default` is true, `enabled` must be true\n operationId: updateVeriFactuConfiguration\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/VeriFactuConfiguration'\n responses:\n '200':\n description: Configuration updated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/VeriFactuConfiguration'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n $ref: '#/components/responses/ValidationError'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/configuration/tax-types:\n get:\n tags:\n - ConfigurationTax\n summary: Get complete tax types catalog\n description: |\n Retrieves the complete catalog of available tax types with structured information\n for tax configuration in Spain:\n\n - **Tax regimes**: VAT (Peninsula), IGIC (Canary Islands), IPSI (Ceuta/Melilla), OTHERS\n - **Percentages per regime**: Valid percentages for each tax type\n - **Regime codes**: VeriFactu codes for each tax type\n - **IRPF**: Available withholding percentages\n - **Equivalence surcharge**: Automatic mappings based on VAT percentage\n - **Utilities**: Validation functions and geographic auto-detection\n\n This endpoint provides all necessary information for the frontend\n without duplicating tax validation logic.\n operationId: getTaxTypes\n responses:\n '200':\n description: Catalog retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n tax_regimes:\n type: array\n items:\n $ref: '#/components/schemas/TaxRegime'\n irpf_types:\n type: array\n items:\n $ref: '#/components/schemas/IrpfType'\n equivalence_surcharges:\n type: array\n items:\n $ref: '#/components/schemas/EquivalenceSurcharge'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/configuration/taxes:\n get:\n tags:\n - ConfigurationTax\n summary: Get user tax configuration\n description: |\n Retrieves the user's tax configuration, including:\n - Default tax regime (VAT, IGIC, IPSI, OTHERS)\n - Default main tax percentage\n - IRPF and equivalence surcharge configuration\n operationId: getTaxConfiguration\n responses:\n '200':\n description: Configuration retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/TaxConfiguration'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n put:\n tags:\n - ConfigurationTax\n summary: Update tax configuration\n description: Updates the user's tax configuration\n operationId: updateTaxConfiguration\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UpdateTaxConfigurationRequest'\n responses:\n '200':\n description: Configuration updated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/TaxConfiguration'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n $ref: '#/components/responses/ValidationError'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/configuration/series:\n get:\n tags:\n - InvoiceSeries\n summary: List invoice series\n description: Retrieves all invoice series for the user\n operationId: listSeries\n parameters:\n - name: active\n in: query\n description: Filter by active/inactive series\n schema:\n type: boolean\n responses:\n '200':\n description: Series retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: array\n items:\n $ref: '#/components/schemas/InvoiceSeries'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n post:\n tags:\n - InvoiceSeries\n summary: Create invoice series\n description: |\n Creates a new invoice series.\n\n **Note:** The first series created by a user is automatically marked as default.\n operationId: createSeries\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateSeriesRequest'\n responses:\n '201':\n description: Series created successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/InvoiceSeries'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '409':\n description: Duplicate series (same code)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '422':\n $ref: '#/components/responses/ValidationError'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/configuration/series/{series_id}:\n put:\n tags:\n - InvoiceSeries\n summary: Update invoice series\n description: |\n Updates an existing invoice series.\n\n Can only update:\n - Description\n - Active/inactive status\n - Whether it is default\n\n Cannot modify: code, prefix, number_format (to maintain consistency with existing invoices).\n\n Business rules:\n - Cannot deactivate a default series (must set another as default first)\n - Cannot mark an inactive series as default\n operationId: updateSeries\n parameters:\n - name: series_id\n in: path\n required: true\n description: Series ID\n schema:\n $ref: '#/components/schemas/UUID'\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UpdateSeriesRequest'\n responses:\n '200':\n description: Series updated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/InvoiceSeries'\n '400':\n description: Cannot update (has associated invoices)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '422':\n $ref: '#/components/responses/ValidationError'\n '500':\n $ref: '#/components/responses/InternalServerError'\n delete:\n tags:\n - InvoiceSeries\n summary: Delete series\n description: |\n Soft-deletes an invoice series. If the series is active, it is automatically\n deactivated before deletion. The series code becomes available for reuse.\n\n **Restriction:** Cannot delete the default series. Assign another series as default first.\n operationId: deleteSeries\n parameters:\n - name: series_id\n in: path\n required: true\n description: Series ID\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '204':\n description: Series deleted successfully\n '400':\n description: Cannot delete the default series\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/configuration/series/{series_id}/default:\n post:\n tags:\n - InvoiceSeries\n summary: Mark series as default\n description: |\n Marks an invoice series as the user's default.\n\n **Business rules:**\n - Only one series can be default per user\n - The series must be active (cannot mark an inactive series)\n - The previous default series is automatically unmarked\n\n **Note:** This operation is idempotent - calling it multiple times\n with the same series has no additional effects.\n operationId: setDefaultSeries\n parameters:\n - name: series_id\n in: path\n required: true\n description: Series ID to mark as default\n schema:\n $ref: '#/components/schemas/UUID'\n responses:\n '200':\n description: Series marked as default successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/InvoiceSeries'\n '400':\n description: Cannot mark as default (inactive series)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/configuration/language:\n put:\n tags:\n - ConfigurationPreferences\n summary: Update user preferred language\n description: |\n Updates the authenticated user's preferred language.\n\n This language is used for:\n - UI translations\n - Template names and colors in invoice customization\n - Emails sent to the user\n\n **Supported languages:**\n - `es` - Spanish (default)\n - `en` - English\n - `ca` - Catalan\n operationId: updateLanguage\n requestBody:\n required: true\n content:\n application/json:\n schema:\n type: object\n required:\n - language\n properties:\n language:\n $ref: '#/components/schemas/Language'\n example:\n language: ca\n responses:\n '200':\n description: Language updated successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n language:\n $ref: '#/components/schemas/Language'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n $ref: '#/components/responses/ValidationError'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/configuration/invoice-customization-options:\n get:\n tags:\n - ConfigurationPreferences\n summary: Get invoice customization options\n description: |\n Retrieves available option catalogs to customize invoices:\n\n **Template types:**\n - MODERN_TABLE: Structured table design, ideal for standard products/services\n - PROFESSIONAL_SERVICE: Text-based design, ideal for notaries/consultancies\n\n **Suggested color palette:**\n - BeeL default colors (orange, blue)\n - Professional palette (dark grays)\n - Creative palette (violet, pink, amber, emerald green)\n - Classic palette (blue, red, green, purple)\n\n **Usage:**\n User can choose a template and accent color to customize\n their invoices. These values are saved in their profile and automatically applied\n when generating new invoice PDFs.\n\n Template and color names are returned translated to the user's invoice language preference.\n operationId: getInvoiceCustomizationOptions\n responses:\n '200':\n description: Customization options retrieved successfully\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/InvoiceCustomizationOptionsResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/nif/validate:\n post:\n tags:\n - NIF\n summary: Validate NIF against AEAT registry\n description: |\n **Public endpoint** to validate a NIF/CIF against the AEAT registry using VeriFactu.\n\n This endpoint is useful for:\n - Pre-validation before registration (proactive feedback)\n - Validating customer NIFs before creating invoices\n - Verifying that a NIF exists in the registry\n\n **Behavior:**\n - **VALID**: The NIF exists in the AEAT registry. Returns the fiscal name.\n - **INVALID**: The NIF has correct format but doesn't exist in the registry.\n - **PENDING**: VeriFactu API is temporarily unavailable. The NIF has valid syntax but couldn't be verified against the registry.\n - **ERROR**: Technical error during validation (invalid syntax, timeout, etc.)\n\n **Note about PENDING:**\n If you use this endpoint for pre-validation during registration, a PENDING status\n indicates that the NIF has valid format and the user can continue. The system\n will validate the NIF automatically when the service becomes available.\n\n **Rate limiting:**\n - With API Key: 100 requests/hour\n - With Session Cookie: 1000 requests/hour\n\n **Authentication:**\n This endpoint accepts both API Keys and Session Cookies.\n operationId: validateNif\n security:\n - ApiKeyAuth: []\n - SessionCookie: []\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ValidateNifRequest'\n example:\n nif: B12345678\n responses:\n '200':\n description: Validation result\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/ValidateNifResponse'\n examples:\n valid:\n summary: Valid NIF (exists in AEAT registry)\n value:\n success: true\n data:\n valid: true\n status: VALID\n legal_name: JUAN PEREZ GARCIA\n message: NIF successfully validated in AEAT registry\n validated_at: '2025-01-15T10:30:00Z'\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 123e4567-e89b-12d3-a456-426614174000\n invalid:\n summary: Invalid NIF (not in AEAT registry)\n value:\n success: true\n data:\n valid: false\n status: INVALID\n legal_name: null\n message: NIF does not exist in AEAT registry\n validated_at: null\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 123e4567-e89b-12d3-a456-426614174000\n pending:\n summary: VeriFactu API unavailable (validation pending)\n value:\n success: true\n data:\n valid: false\n status: PENDING\n legal_name: null\n message: NIF validation is pending. The system will validate it automatically when the service is available.\n validated_at: null\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 123e4567-e89b-12d3-a456-426614174000\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n $ref: '#/components/responses/ValidationError'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies:\n post:\n operationId: createCompany\n summary: Create a company (sub-account)\n description: |\n Creates a new NIF/company under the authenticated account.\n Invoice series are created automatically with default values.\n tags:\n - PublicCompanies\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateCompanyRequest'\n responses:\n '201':\n description: Company created\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CompanyResponse201'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '409':\n $ref: '#/components/responses/Conflict'\n '422':\n $ref: '#/components/responses/UnprocessableEntity'\n '500':\n $ref: '#/components/responses/InternalServerError'\n get:\n operationId: listCompanies\n summary: List all companies\n description: Returns all companies (sub-accounts) under the authenticated account.\n tags:\n - PublicCompanies\n responses:\n '200':\n description: List of companies\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ListCompanies200Response'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies/{company_id}:\n parameters:\n - name: company_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n get:\n operationId: getCompany\n summary: Get company details\n description: Returns details of a specific company including VeriFactu status.\n tags:\n - PublicCompanies\n responses:\n '200':\n description: Company details\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CompanyResponse201'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n patch:\n operationId: updateCompany\n summary: Update company details\n description: |\n Updates editable fields of a company.\n NIF, legal name, and entity type are immutable after creation.\n tags:\n - PublicCompanies\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UpdateCompanyRequest'\n responses:\n '200':\n description: Company updated\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CompanyResponse201'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '422':\n $ref: '#/components/responses/UnprocessableEntity'\n '500':\n $ref: '#/components/responses/InternalServerError'\n delete:\n operationId: deleteCompany\n summary: Delete a company\n description: |\n Deletes a company (sub-account). Cannot delete the primary profile.\n tags:\n - PublicCompanies\n responses:\n '204':\n description: Company deleted\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies/{company_id}/representation/generate:\n parameters:\n - name: company_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n post:\n operationId: generateRepresentation\n summary: Generate unsigned representation PDF\n description: Generates the VeriFactu representation PDF for digital signature.\n tags:\n - PublicCompanyRepresentations\n responses:\n '200':\n description: PDF generated successfully\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/RepresentationActionResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies/{company_id}/representation/download:\n parameters:\n - name: company_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n get:\n operationId: downloadRepresentation\n summary: Download representation PDF\n description: Returns a presigned URL (5min) to download the representation PDF.\n tags:\n - PublicCompanyRepresentations\n responses:\n '200':\n description: Presigned download URL\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/RepresentationDownloadResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies/{company_id}/representation/submit:\n parameters:\n - name: company_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n post:\n operationId: submitRepresentation\n summary: Submit signed representation PDF\n description: |\n Uploads the digitally signed representation PDF.\n VeriFactu will validate the signature asynchronously.\n tags:\n - PublicCompanyRepresentations\n requestBody:\n required: true\n content:\n multipart/form-data:\n schema:\n type: object\n required:\n - file\n properties:\n file:\n type: string\n format: binary\n description: Signed PDF file\n responses:\n '200':\n description: PDF submitted for validation\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/RepresentationActionResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '413':\n $ref: '#/components/responses/PayloadTooLarge'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies/{company_id}/representation/status:\n parameters:\n - name: company_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n get:\n operationId: getRepresentationStatus\n summary: Get representation status\n description: Returns the current status of the VeriFactu representation process.\n tags:\n - PublicCompanyRepresentations\n responses:\n '200':\n description: Representation status\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/RepresentationStatusResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies/{company_id}/representation/cancel:\n parameters:\n - name: company_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n delete:\n operationId: cancelRepresentation\n summary: Cancel representation\n description: Cancels the current representation process.\n tags:\n - PublicCompanyRepresentations\n responses:\n '200':\n description: Representation cancelled\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/RepresentationActionResponse'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies/{company_id}/api-keys:\n parameters:\n - name: company_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n post:\n operationId: createCompanyApiKey\n summary: Create API key scoped to a company\n description: |\n Creates an API key that authenticates directly as this company.\n The created key cannot have more scopes than the parent key.\n tags:\n - PublicCompanyApiKeys\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateCompanyApiKeyRequest'\n responses:\n '201':\n description: API key created\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateCompanyApiKey201Response'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n get:\n operationId: listCompanyApiKeys\n summary: List API keys for a company\n description: Returns all API keys associated with this company.\n tags:\n - PublicCompanyApiKeys\n responses:\n '200':\n description: List of API keys\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ListCompanyApiKeys200Response'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/companies/{company_id}/api-keys/{key_id}:\n parameters:\n - name: company_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n - name: key_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n delete:\n operationId: revokeCompanyApiKey\n summary: Revoke an API key\n description: Revokes (deletes) an API key associated with this company.\n tags:\n - PublicCompanyApiKeys\n responses:\n '204':\n description: API key revoked\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '500':\n $ref: '#/components/responses/InternalServerError'\n /v1/webhooks:\n post:\n tags:\n - Webhooks\n summary: Create webhook subscription\n description: Register an HTTPS endpoint to receive real-time event notifications. Every delivery is signed with BeeL-Signature (HMAC-SHA256). The secret is returned once on creation \u2014 store it securely. Maximum 10 active webhook subscriptions per account.\n operationId: createWebhookSubscription\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/CreateWebhookSubscriptionRequest'\n example:\n url: https://yourapp.com/webhooks/beel\n events:\n - verifactu.status.updated\n callbacks:\n webhookEvent:\n '{$request.body#/url}':\n post:\n summary: Webhook event delivered to your endpoint\n description: |\n Beel sends HTTP POST to your URL when a subscribed event occurs.\n Your endpoint must respond with HTTP 2xx within 10 seconds.\n Failed deliveries are retried up to 5 times with exponential backoff.\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/WebhookEvent'\n examples:\n verifactuAccepted:\n summary: VeriFactu submission accepted by AEAT\n value:\n id: 3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c\n type: verifactu.status.updated\n created_at: '2026-03-07T16:20:26Z'\n api_version: YYYY-MM\n livemode: true\n data:\n invoice_id: 550e8400-e29b-41d4-a716-446655440000\n invoice_number: 2025-001\n verifactu_registration_id: 660e8400-e29b-41d4-a716-446655440001\n previous_status: PENDING\n new_status: ACCEPTED\n qr_url: https://sede.agenciatributaria.gob.es/Sede/verifactu?id=ABC123XYZ\n qr_base64: iVBORw0KGgoAAAANSUhEUgAAAMg...\n invoice_hash: B11F3A015173AD99075E2720F61E2DE1FF08CBFEDD85C6F73C77AD835301B2A3\n error_code: null\n error_message: null\n verifactuRejected:\n summary: VeriFactu submission rejected by AEAT\n value:\n id: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n type: verifactu.status.updated\n created_at: '2026-03-07T16:22:10Z'\n api_version: YYYY-MM\n livemode: true\n data:\n invoice_id: 550e8400-e29b-41d4-a716-446655440000\n invoice_number: 2025-001\n verifactu_registration_id: 660e8400-e29b-41d4-a716-446655440001\n previous_status: PENDING\n new_status: REJECTED\n qr_url: null\n qr_base64: null\n invoice_hash: null\n error_code: '1105'\n error_message: NIF del emisor no registrado en VeriFactu\n responses:\n 2XX:\n description: Your endpoint acknowledged the event successfully.\n 4XX:\n description: Client error \u2014 delivery will NOT be retried.\n 5XX:\n description: Server error \u2014 delivery will be retried with exponential backoff.\n headers:\n BeeL-Signature:\n required: true\n schema:\n type: string\n example: t=1741362026,v1=3c4f7a2e1b9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f\n BeeL-Event:\n required: true\n schema:\n type: string\n example: verifactu.status.updated\n BeeL-Event-Id:\n required: true\n description: UUID del evento webhook \u2014 id\xE9ntico en todos los reintentos del mismo evento l\xF3gico. Coincide con el campo `id` del payload.\n schema:\n type: string\n format: uuid\n BeeL-Delivery-Id:\n required: true\n description: UUID del intento de entrega \u2014 \xFAnico por cada llamada HTTP, incluyendo reintentos. Coincide con el `id` del log de entrega en el dashboard.\n schema:\n type: string\n format: uuid\n Idempotency-Key:\n required: true\n description: Mismo UUID que `BeeL-Event-Id`. Permite al receptor deduplicar entregas autom\xE1ticamente.\n schema:\n type: string\n format: uuid\n responses:\n '201':\n description: Webhook subscription created\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/WebhookSubscriptionWithSecret'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '422':\n $ref: '#/components/responses/ValidationError'\n get:\n tags:\n - Webhooks\n summary: List webhook subscriptions\n description: Returns all webhook subscriptions for the authenticated account.\n operationId: listWebhookSubscriptions\n parameters:\n - $ref: '#/components/parameters/PageParam'\n - $ref: '#/components/parameters/LimitParam'\n responses:\n '200':\n description: List of webhook subscriptions\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n webhooks:\n type: array\n items:\n $ref: '#/components/schemas/WebhookSubscription'\n pagination:\n $ref: '#/components/schemas/Pagination'\n '401':\n $ref: '#/components/responses/Unauthorized'\n /v1/webhooks/{webhook_id}:\n patch:\n tags:\n - Webhooks\n summary: Update webhook subscription\n description: |\n Partially updates a webhook subscription. Only provided fields are updated.\n\n Use this endpoint to:\n - **Change the endpoint URL** (`url`)\n - **Change subscribed events** (`events`)\n - **Enable or disable** the subscription (`active`)\n operationId: updateWebhookSubscription\n parameters:\n - name: webhook_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n requestBody:\n required: true\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/UpdateWebhookSubscriptionRequest'\n responses:\n '200':\n description: Webhook subscription updated\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/WebhookSubscription'\n '400':\n $ref: '#/components/responses/BadRequest'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n '422':\n $ref: '#/components/responses/ValidationError'\n delete:\n tags:\n - Webhooks\n summary: Delete webhook subscription\n description: Permanently deletes a webhook subscription. No further events will be delivered.\n operationId: deleteWebhookSubscription\n parameters:\n - name: webhook_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '204':\n description: Subscription deleted\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/webhooks/{webhook_id}/deliveries:\n get:\n tags:\n - Webhooks\n summary: List webhook delivery logs\n description: Returns the last 50 delivery attempts for a webhook subscription.\n operationId: listWebhookDeliveries\n parameters:\n - name: webhook_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: List of delivery logs\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: array\n items:\n $ref: '#/components/schemas/WebhookDeliveryLog'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/webhooks/{webhook_id}/deliveries/{delivery_id}/retry:\n post:\n tags:\n - Webhooks\n summary: Retry a failed webhook delivery\n description: |\n Immediately retries a webhook delivery using the original payload.\n Creates a new delivery log entry with the result.\n operationId: retryWebhookDelivery\n parameters:\n - name: webhook_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n - name: delivery_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: Retry result\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/WebhookDeliveryLog'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\n /v1/webhooks/{webhook_id}/secret:\n post:\n tags:\n - Webhooks\n summary: Rotate webhook secret\n description: |\n Generates a new HMAC secret for a webhook subscription.\n The old secret is **immediately invalidated** \u2014 update your signature verification\n logic before rotating to avoid missing events during the transition.\n The new secret is only returned **once** in this response.\n operationId: rotateWebhookSecret\n parameters:\n - name: webhook_id\n in: path\n required: true\n schema:\n type: string\n format: uuid\n responses:\n '200':\n description: Secret rotated \u2014 new secret returned (only shown once)\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/WebhookSubscriptionWithSecret'\n '401':\n $ref: '#/components/responses/Unauthorized'\n '404':\n $ref: '#/components/responses/NotFound'\ncomponents:\n parameters:\n IdempotencyKeyHeader:\n name: Idempotency-Key\n in: header\n description: |\n Idempotency key to prevent duplicates in sensitive operations.\n\n - Must be a unique UUID generated by the client\n - If the same key is sent twice, the result of the first operation is returned\n - Keys expire after processing\n required: false\n schema:\n type: string\n format: uuid\n example: 550e8400-e29b-41d4-a716-446655440000\n LocaleHeader:\n name: X-Locale\n in: header\n description: |\n Preferred language for response messages and localized content.\n\n Supported values: es (Spanish), en (English), ca (Catalan)\n Falls back to Accept-Language header if not provided.\n required: false\n schema:\n type: string\n enum:\n - es\n - en\n - ca\n default: es\n example: es\n PageParam:\n name: page\n in: query\n description: Page number (starts at 1)\n schema:\n type: integer\n minimum: 1\n default: 1\n example: 1\n LimitParam:\n name: limit\n in: query\n description: Number of items per page\n schema:\n type: integer\n minimum: 1\n maximum: 100\n default: 20\n example: 20\n schemas:\n SuccessResponse:\n type: object\n required:\n - success\n - data\n properties:\n success:\n type: boolean\n example: true\n data:\n description: Response data (type varies by endpoint - can be object or array)\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n ErrorResponse:\n type: object\n required:\n - success\n - error\n properties:\n success:\n type: boolean\n example: false\n error:\n $ref: '#/components/schemas/ErrorDetail'\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n ErrorDetail:\n type: object\n required:\n - code\n - message\n properties:\n code:\n type: string\n example: VALIDATION_ERROR\n message:\n type: string\n example: The provided data is not valid\n details:\n type: object\n additionalProperties: true\n example:\n field: specific error message\n ResponseMeta:\n type: object\n properties:\n timestamp:\n type: string\n format: date-time\n example: '2025-01-15T10:30:00Z'\n request_id:\n type: string\n example: 4bf92f3577b34da6a3ce929d0e0e4736\n Pagination:\n type: object\n required:\n - current_page\n - total_pages\n - total_items\n - items_per_page\n properties:\n current_page:\n type: integer\n example: 1\n total_pages:\n type: integer\n example: 5\n total_items:\n type: integer\n example: 87\n items_per_page:\n type: integer\n example: 20\n has_next:\n type: boolean\n has_previous:\n type: boolean\n LegalRepresentative:\n type: object\n required:\n - full_name\n - nif\n - address\n description: |\n Legal representative data for a legal entity.\n Only used when entity_type = LEGAL_ENTITY.\n properties:\n full_name:\n type: string\n minLength: 1\n maxLength: 255\n description: Full name of the legal representative\n example: Mar\xEDa Garc\xEDa L\xF3pez\n nif:\n type: string\n pattern: ^(\\d{8}[A-Z]|[ABCDEFGHJKLMNPQRSUVW]\\d{7}[A-Z0-9]|[XYZ]\\d{7}[A-Z])$\n minLength: 9\n maxLength: 9\n description: Tax ID of the legal representative (DNI/CIF/NIE)\n example: 12345678A\n address:\n allOf:\n - $ref: '#/components/schemas/Address'\n - description: Address of the legal representative\n Address:\n type: object\n required:\n - street\n - number\n - postal_code\n - city\n - province\n - country\n properties:\n street:\n type: string\n minLength: 1\n maxLength: 255\n pattern: ^[a-zA-Z0-9\xC0-\xFF\\u0100-\\u017F\\u00B7\\s\\.,\\-\\/'\xBA\xAA\xB0:;\"()&#]+$\n description: Full address (street, number, floor, etc.) - Latin characters only\n example: Calle Mayor, 123\n number:\n type: string\n minLength: 1\n maxLength: 20\n description: Street number\n example: '123'\n floor:\n type: string\n maxLength: 10\n description: Floor or level\n example: 2\xBA A\n door:\n type: string\n maxLength: 10\n description: Door or apartment\n example: A\n postal_code:\n type: string\n minLength: 1\n maxLength: 20\n description: Postal code (5 digits for Spain, free format for other countries)\n example: '28001'\n city:\n type: string\n minLength: 1\n maxLength: 100\n pattern: ^[a-zA-Z0-9\xC0-\xFF\\u0100-\\u017F\\u00B7\\u2018\\u2019\\u0060\\u00B4\\s\\.,\\-\\/'\xBA\xAA()]+$\n description: City or town - Latin characters only\n example: Madrid\n province:\n type: string\n minLength: 1\n maxLength: 100\n pattern: ^[a-zA-Z0-9\xC0-\xFF\\u0100-\\u017F\\u00B7\\u2018\\u2019\\u0060\\u00B4\\s\\.,\\-\\/'\xBA\xAA]+$\n description: Province or state - Latin characters only\n example: Madrid\n country:\n type: string\n minLength: 1\n maxLength: 100\n pattern: ^[a-zA-Z0-9\xC0-\xFF\\u0100-\\u017F\\u00B7\\u2018\\u2019\\u0060\\u00B4\\s\\.,\\-\\/'\xBA\xAA]+$\n default: Espa\xF1a\n description: Country - Latin characters only\n example: Espa\xF1a\n country_code:\n type: string\n pattern: ^[A-Z]{2}$\n minLength: 2\n maxLength: 2\n default: ES\n description: ISO 3166-1 alpha-2 country code\n example: ES\n PaymentMethod:\n type: string\n description: |\n Payment method for invoices and recurring invoices.\n NONE means no payment information will be shown.\n enum:\n - NONE\n - BANK_TRANSFER\n - CARD\n - CASH\n - CHECK\n - DIRECT_DEBIT\n - OTHER\n example: BANK_TRANSFER\n EntityType:\n type: string\n description: |\n Taxpayer type.\n INDIVIDUAL: Natural person (individual self-employed).\n LEGAL_ENTITY: Legal entity (company with legal form: SL, SA, etc.).\n enum:\n - INDIVIDUAL\n - LEGAL_ENTITY\n example: INDIVIDUAL\n Language:\n type: string\n description: Supported languages\n enum:\n - es\n - en\n - ca\n default: es\n example: es\n VatCategory:\n type: string\n description: VAT type\n enum:\n - GENERAL\n - REDUCED\n - SUPER_REDUCED\n - EXEMPT\n example: GENERAL\n IrpfCategory:\n type: string\n description: Personal income tax/withholding type\n enum:\n - PROFESSIONAL_15\n - PROFESSIONAL_7\n - AGRICULTURAL_2\n - LIVESTOCK_2\n - FORESTRY_2\n - BUSINESS_ACTIVITIES_1\n - EXEMPT\n example: PROFESSIONAL_15\n CommonTaxRegime:\n $ref: '#/components/schemas/schemas-TaxRegime'\n CommonVatPercentage:\n $ref: '#/components/schemas/VatPercentage'\n IrpfPercentage:\n type: integer\n enum:\n - 0\n - 1\n - 2\n - 7\n - 15\n - 19\n - 24\n description: |\n Personal income tax/withholding percentage in integer format.\n Allowed values: 0 (exempt), 1 (agricultural/livestock/forestry), 2 (reduced for modules), 7, 15, 19, 24 (non-residents).\n example: 15\n EquivalenceSurchargePercentage:\n type: number\n enum:\n - 0\n - 0.5\n - 1.4\n - 5.2\n description: |\n Equivalence surcharge percentage in decimal format.\n The backend automatically normalizes equivalent formats (5.20 \u2192 5.2).\n example: 5.2\n NifValidationStatus:\n type: string\n description: |\n Tax ID validation status against the AEAT tax registry.\n\n - PENDING: Not yet validated or VeriFactu API temporarily unavailable\n - VALID: Successfully validated against the AEAT tax registry\n - INVALID: Invalid tax ID or does not exist in the AEAT tax registry\n - ERROR: Technical error after exhausting retries\n enum:\n - PENDING\n - VALID\n - INVALID\n - ERROR\n example: VALID\n UUID:\n type: string\n format: uuid\n description: Universally Unique Identifier (UUID v4)\n example: 550e8400-e29b-41d4-a716-446655440000\n NIF:\n type: string\n pattern: ^(\\d{8}[A-Z]|[ABCDEFGHJKLMNPQRSUVW]\\d{7}[A-Z0-9]|[XYZ]\\d{7}[A-Z])$\n minLength: 9\n maxLength: 9\n description: |\n Spanish Tax ID (9 characters, uppercase only). Structural validation:\n - DNI: 8 digits + 1 letter (e.g., 12345678A)\n - NIE: X/Y/Z + 7 digits + 1 letter (e.g., X1234567A)\n - CIF: Organization letter + 7 digits + 1 control digit/letter (e.g., B12345678)\n example: 12345678A\n Email:\n type: string\n format: email\n minLength: 5\n maxLength: 255\n description: Email address (minimum valid email is 5 chars, e.g. a@b.co)\n example: user@example.com\n Phone:\n type: string\n minLength: 9\n maxLength: 20\n pattern: ^[+]?[0-9\\s\\-\\(\\)]+$\n description: Phone number. Allows digits, spaces, dashes, parentheses, and optional leading +\n example: +34 612 345 678\n IBAN:\n type: string\n pattern: ^[A-Z]{2}\\d{2}[A-Z0-9]{1,30}$\n minLength: 15\n maxLength: 34\n description: |\n IBAN (International Bank Account Number).\n Required when payment method is BANK_TRANSFER.\n example: ES1234567890123456789012\n SWIFT:\n type: string\n pattern: ^[A-Z]{6}[A-Z0-9]{2}([A-Z0-9]{3})?$\n minLength: 8\n maxLength: 11\n description: SWIFT/BIC code\n example: ABCDESMMXXX\n Invoice:\n type: object\n required:\n - id\n - series\n - type\n - status\n - issue_date\n - issuer\n - recipient\n - lines\n - totals\n - created_at\n - updated_at\n properties:\n id:\n type: string\n format: uuid\n description: Unique invoice UUID\n example: 550e8400-e29b-41d4-a716-446655440000\n invoice_number:\n type: string\n nullable: true\n example: 2025/0001\n description: |\n Complete invoice number (series + sequential).\n **Null for draft invoices** \u2014 assigned automatically when issued.\n series:\n $ref: '#/components/schemas/SeriesInfo'\n number:\n type: integer\n nullable: true\n example: 1\n description: |\n Sequential number within the series.\n **Null for draft invoices** \u2014 assigned automatically when issued.\n type:\n $ref: '#/components/schemas/InvoiceType'\n status:\n $ref: '#/components/schemas/InvoiceStatus'\n issue_date:\n type: string\n format: date\n description: |\n Invoice issue date. Always set to the current date when the invoice is created.\n If the operation occurred on a different date, use `operation_date`.\n example: '2025-01-15'\n operation_date:\n type: string\n format: date\n nullable: true\n description: |\n Date when the operation actually occurred. Used when invoicing for a past operation.\n If null, the operation date is the same as the issue date.\n example: '2025-01-10'\n due_date:\n type: string\n format: date\n description: Payment due date (must be the same as or after `issue_date`)\n example: '2025-02-14'\n payment_date:\n type: string\n format: date\n description: |\n Business date when the payment was received (e.g., the date on the bank statement).\n Set by the user when marking the invoice as paid. Only present when status is PAID.\n Contrast with `paid_at`, which is the system timestamp of when the status change was recorded.\n example: '2025-01-20'\n sent_at:\n type: string\n format: date-time\n nullable: true\n description: |\n System timestamp when the invoice was marked as sent.\n Present when status is SENT or later.\n example: '2025-01-29T18:45:00Z'\n paid_at:\n type: string\n format: date-time\n nullable: true\n description: |\n System timestamp when the payment was recorded in the system.\n Automatically set when the invoice status changes to PAID.\n Contrast with `payment_date`, which is the business date chosen by the user.\n example: '2025-02-05T10:30:00Z'\n auto_emit_after:\n type: string\n format: date\n nullable: true\n description: |\n Date when this draft will be auto-emitted if not manually issued.\n Only present for drafts created from recurring invoices with preview_days > 0.\n example: '2025-03-20'\n scheduled_for:\n type: string\n format: date\n nullable: true\n description: |\n Date when the invoice should be automatically processed.\n Only present when status is SCHEDULED.\n example: '2025-02-15'\n scheduled_action:\n $ref: '#/components/schemas/GenerationAction'\n issuer:\n $ref: '#/components/schemas/IssuerData'\n recipient:\n $ref: '#/components/schemas/RecipientData'\n lines:\n type: array\n items:\n $ref: '#/components/schemas/InvoiceLine'\n minItems: 1\n totals:\n $ref: '#/components/schemas/InvoiceTotals'\n payment_info:\n $ref: '#/components/schemas/PaymentInfo'\n notes:\n type: string\n maxLength: 1000\n description: Additional observations or notes\n rectified_invoice_id:\n type: string\n format: uuid\n description: UUID of the invoice being rectified (only for corrective invoices)\n rectification_reason:\n type: string\n maxLength: 500\n description: Reason for rectification (only for corrective invoices)\n recurring_invoice_id:\n type: string\n format: uuid\n nullable: true\n description: UUID of the recurring invoice that generated this invoice (if any)\n recurring_invoice_name:\n type: string\n nullable: true\n description: Name of the recurring invoice (denormalized for display)\n rectification_type:\n $ref: '#/components/schemas/RectificationType'\n rectification_code:\n $ref: '#/components/schemas/VeriFactuRectificationCode'\n metadata:\n type: object\n additionalProperties: true\n description: |\n Additional metadata in key-value format. Useful examples:\n - stripe_payment_id: Stripe payment ID\n - stripe_charge_id: Stripe charge ID\n - external_order_id: External order ID\n - payment_platform: Payment platform used\n - internal_notes: Internal notes not visible to customer\n example:\n stripe_payment_id: pi_3NqFGb2eZvKYlo2C0z1234AB\n stripe_charge_id: ch_3NqFGb2eZvKYlo2C1234CDEF\n external_order_id: ORD-2025-0042\n payment_platform: stripe\n pdf_download_url:\n type: string\n nullable: true\n description: |\n Relative URL to download the PDF via the API endpoint.\n Path is relative to the API base URL (e.g., https://api.beel.es/api).\n Only present when the invoice has a generated PDF.\n example: /v1/invoices/550e8400-e29b-41d4-a716-446655440000/pdf\n verifactu:\n $ref: '#/components/schemas/VeriFactu'\n attachments:\n type: array\n items:\n type: object\n properties:\n id:\n type: string\n format: uuid\n name:\n type: string\n url:\n type: string\n type:\n type: string\n sending_history:\n type: array\n items:\n type: object\n properties:\n id:\n type: string\n format: uuid\n date:\n type: string\n format: date-time\n recipient:\n $ref: '#/components/schemas/Email'\n status:\n type: string\n enum:\n - SENT\n - FAILED\n error:\n type: string\n created_at:\n type: string\n format: date-time\n updated_at:\n type: string\n format: date-time\n deleted_at:\n type: string\n format: date-time\n nullable: true\n SeriesInfo:\n type: object\n required:\n - id\n - code\n properties:\n id:\n type: string\n format: uuid\n description: Invoice series UUID\n example: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n code:\n type: string\n description: Alphanumeric series code\n example: FAC\n pattern: ^[A-Z0-9\\-_]{1,50}$\n InvoiceType:\n type: string\n enum:\n - STANDARD\n - CORRECTIVE\n - SIMPLIFIED\n description: |\n - STANDARD: Standard invoice\n - CORRECTIVE: Corrects or cancels a previous invoice\n - SIMPLIFIED: Simplified invoice without all requirements (up to 400\u20AC, or 3,000\u20AC with NIF)\n InvoiceStatus:\n type: string\n enum:\n - SCHEDULED\n - DRAFT\n - ISSUED\n - SENT\n - PAID\n - OVERDUE\n - RECTIFIED\n - VOIDED\n description: |\n - SCHEDULED: Scheduled invoice to be issued automatically on a future date\n - DRAFT: Draft invoice not sent yet (modifiable)\n - ISSUED: Finalized invoice with definitive number but not sent\n - SENT: Invoice sent to customer\n - PAID: Invoice paid\n - OVERDUE: Overdue invoice (not paid after due date)\n - RECTIFIED: Partially corrected invoice (one or more PARTIAL corrective invoices)\n - VOIDED: Completely cancelled invoice (TOTAL corrective invoice)\n RectificationType:\n type: string\n enum:\n - TOTAL\n - PARTIAL\n description: |\n Type of rectification applied to a corrective invoice:\n - TOTAL: Completely cancels the original invoice (status \u2192 VOIDED)\n - PARTIAL: Partially corrects the original invoice (status \u2192 RECTIFIED)\n VeriFactuRectificationCode:\n type: string\n enum:\n - R1\n - R2\n - R3\n - R4\n - R5\n description: |\n Rectification codes according to VeriFactu regulations (AEAT):\n - R1: Error founded in law and Art. 80 One, Two and Six LIVA\n - R2: Article 80 Three LIVA (Bankruptcy proceedings)\n - R3: Article 80 Four LIVA (Uncollectable debts)\n - R4: Other causes\n - R5: Simplified invoices (Art. 80 One and Two LIVA) - ONLY for simplified invoices\n GenerationAction:\n type: string\n enum:\n - DRAFT\n - ISSUE_AND_SEND\n description: |\n Action to perform when processing a scheduled invoice:\n - DRAFT: Create as draft for manual review\n - ISSUE_AND_SEND: Issue and send automatically via email\n IssuerData:\n type: object\n required:\n - legal_name\n - nif\n - address\n properties:\n legal_name:\n type: string\n minLength: 1\n maxLength: 255\n description: Issuer legal name\n example: Juan P\xE9rez Garc\xEDa\n trade_name:\n type: string\n minLength: 1\n maxLength: 255\n nullable: true\n description: Issuer trade name (optional)\n example: JP Web Development\n nif:\n type: string\n pattern: ^[A-Za-z0-9]{9}$\n minLength: 9\n maxLength: 9\n description: |\n Spanish Tax ID (9 alphanumeric characters).\n Valid formats:\n - DNI: 8 digits + letter (e.g., 12345678A)\n - NIE: X/Y/Z + 7 digits + letter (e.g., X1234567A)\n - CIF: Letter + 7 digits + digit/letter (e.g., B12345678)\n example: 12345678A\n address:\n $ref: '#/components/schemas/Address'\n phone:\n $ref: '#/components/schemas/Phone'\n email:\n $ref: '#/components/schemas/Email'\n web:\n type: string\n minLength: 1\n maxLength: 500\n nullable: true\n description: Issuer website (optional)\n example: https://beel.es\n logo_url:\n type: string\n minLength: 1\n maxLength: 500\n nullable: true\n description: Issuer logo URL (optional)\n additional_info:\n type: string\n maxLength: 500\n nullable: true\n description: |\n Additional issuer information (collegiate number, professional registration, etc.)\n example: 'N\xBA Colegiado: 12345'\n RecipientData:\n type: object\n required:\n - legal_name\n description: |\n Recipient data in the invoice response.\n Note: For simplified invoices, only legal_name may be present.\n NIF and address are optional for SIMPLIFIED type invoices.\n properties:\n customer_id:\n type: string\n format: uuid\n nullable: true\n description: Customer UUID in the system (optional)\n legal_name:\n type: string\n minLength: 1\n maxLength: 255\n description: Recipient legal name\n example: Empresa SL\n trade_name:\n type: string\n minLength: 1\n maxLength: 255\n nullable: true\n description: Recipient trade name (optional)\n example: Empresa\n nif:\n type: string\n pattern: ^[A-Za-z0-9]{9}$\n minLength: 9\n maxLength: 9\n nullable: true\n description: |\n Spanish Tax ID (9 alphanumeric characters).\n Optional for simplified invoices (SIMPLIFIED type).\n Valid formats:\n - DNI: 8 digits + letter (e.g., 12345678A)\n - NIE: X/Y/Z + 7 digits + letter (e.g., X1234567A)\n - CIF: Letter + 7 digits + digit/letter (e.g., B12345678)\n example: 12345678A\n alternative_id:\n allOf:\n - $ref: '#/components/schemas/AlternativeIdentifier'\n - description: Alternative identifier for foreign customers (mutually exclusive with nif)\n address:\n allOf:\n - $ref: '#/components/schemas/Address'\n description: Address (optional for simplified invoices)\n phone:\n $ref: '#/components/schemas/Phone'\n email:\n $ref: '#/components/schemas/Email'\n InvoiceLine:\n type: object\n required:\n - description\n - quantity\n - unit_price\n - line_total\n properties:\n description:\n type: string\n minLength: 1\n maxLength: 500\n description: Description of the invoiced concept\n example: Web application development\n quantity:\n type: number\n description: Product/service quantity (can be negative in corrective invoices)\n example: 40\n unit:\n type: string\n default: hours\n example: hours\n unit_price:\n type: number\n minimum: 0\n exclusiveMinimum: true\n maximum: 999999.9999\n example: 50\n description: |\n Unit price before taxes.\n Supports up to 4 decimal places for micro-pricing (e.g., \u20AC0.0897/unit for labels, packaging).\n Final amounts are always rounded to 2 decimals.\n discount_percentage:\n type: number\n minimum: 0\n maximum: 100\n default: 0\n example: 10\n description: Discount percentage applied (0-100)\n main_tax:\n $ref: '#/components/schemas/TaxInfo'\n equivalence_surcharge_rate:\n $ref: '#/components/schemas/EquivalenceSurchargePercentage'\n irpf_rate:\n $ref: '#/components/schemas/IrpfPercentage'\n exemption_reason:\n $ref: '#/components/schemas/ExemptionReason'\n nullable: true\n exemption_reason_text:\n type: string\n nullable: true\n maxLength: 500\n description: Custom exemption text. Only used when exemption_reason is OTRO.\n taxable_base:\n type: number\n example: 1800\n description: Line taxable base (after discount, can be negative in corrective invoices)\n line_total:\n type: number\n example: 2178\n description: Line total with taxes (can be negative in corrective invoices)\n InvoiceTotals:\n type: object\n required:\n - taxable_base\n - total_vat\n - total_equivalence_surcharge\n - total_irpf\n - invoice_total\n properties:\n taxable_base:\n type: number\n example: 2000\n description: Total taxable base (can be negative in corrective invoices)\n total_discounts:\n type: number\n minimum: 0\n default: 0\n example: 0\n vat_breakdown:\n type: array\n items:\n type: object\n required:\n - type\n - base\n - amount\n properties:\n type:\n type: number\n example: 21\n base:\n type: number\n example: 2000\n amount:\n type: number\n example: 420\n total_vat:\n type: number\n example: 420\n description: Total VAT (can be negative in corrective invoices)\n surcharge_breakdown:\n type: array\n items:\n type: object\n required:\n - type\n - base\n - amount\n properties:\n type:\n type: number\n base:\n type: number\n amount:\n type: number\n total_equivalence_surcharge:\n type: number\n default: 0\n example: 0\n description: Total equivalence surcharge (can be negative in corrective invoices)\n irpf_breakdown:\n type: array\n items:\n type: object\n required:\n - type\n - base\n - amount\n properties:\n type:\n type: number\n base:\n type: number\n amount:\n type: number\n total_irpf:\n type: number\n default: 0\n example: 300\n description: Total personal income tax withheld (can be negative in corrective invoices)\n invoice_total:\n type: number\n example: 2120\n description: Total amount to pay (base + VAT + RE - IRPF, can be negative in corrective invoices)\n PaymentInfo:\n type: object\n properties:\n method:\n allOf:\n - $ref: '#/components/schemas/PaymentMethod'\n default: BANK_TRANSFER\n description: |\n Preferred payment method.\n If NONE is selected, no payment information will be shown on the invoice.\n iban:\n $ref: '#/components/schemas/IBAN'\n swift:\n $ref: '#/components/schemas/SWIFT'\n payment_term_days:\n type: integer\n minimum: 0\n maximum: 365\n default: 30\n nullable: true\n description: Payment term in days (optional, default 30)\n example: 30\n VeriFactu:\n type: object\n description: |\n VeriFactu compliance information (optional).\n Some self-employed workers may be required to use this AEAT system,\n while others may choose not to.\n properties:\n enabled:\n type: boolean\n default: false\n description: Whether VeriFactu is enabled for this invoice\n invoice_hash:\n type: string\n description: Invoice SHA-256 hash according to VeriFactu regulations\n example: 3A5B7C9D1E2F3A4B5C6D7E8F9A0B1C2D3E4F5A6B7C8D9E0F1A2B3C4D5E6F7A8B\n chaining_hash:\n type: string\n description: Chaining hash with previous invoice\n example: 7F8E9D0C1B2A3F4E5D6C7B8A9F0E1D2C3B4A5F6E7D8C9B0A1F2E3D4C5B6A7F8\n registration_number:\n type: string\n description: Registration number in the VeriFactu system\n example: VERIFACTU2025000001\n qr_url:\n type: string\n description: QR code URL for verification\n example: https://verifactu.agenciatributaria.gob.es/v?id=ABC123XYZ\n qr_base64:\n type: string\n nullable: true\n description: |\n QR code as base64-encoded PNG for embedding in custom PDFs.\n Only present when submission_status = ACCEPTED.\n example: iVBORw0KGgoAAAANSUhEUgAAAMgAAADI...\n registration_date:\n type: string\n format: date-time\n description: VeriFactu registration date and time\n submission_status:\n type: string\n enum:\n - PENDING\n - SENT\n - ACCEPTED\n - VOIDED\n - REJECTED\n description: Submission status to AEAT\n error_code:\n type: string\n nullable: true\n description: Error code returned by AEAT. Present when submission_status is REJECTED.\n example: '3000'\n error_message:\n type: string\n nullable: true\n description: Human-readable error description returned by AEAT. Present when submission_status is REJECTED.\n example: Factura ya existe en el sistema\n Recipient:\n type: object\n properties:\n customer_id:\n type: string\n format: uuid\n description: |\n UUID of a registered customer. If present, the invoice uses the customer's\n stored data and all other recipient fields are ignored.\n example: 4f244735-980b-8d9c-80e8-6331fa0b1958\n legal_name:\n type: string\n minLength: 1\n maxLength: 255\n description: |\n Recipient legal name. Required when customer_id is not provided\n (except for SIMPLIFIED invoices where all fields are optional).\n example: Tech Solutions SL\n trade_name:\n type: string\n minLength: 1\n maxLength: 255\n nullable: true\n description: Recipient trade name (optional)\n example: TechSol\n nif:\n type: string\n pattern: ^[A-Za-z0-9]{9}$\n minLength: 9\n maxLength: 9\n description: |\n Spanish Tax ID (9 alphanumeric characters).\n Required when customer_id is not provided and alternative_id is absent.\n Optional for SIMPLIFIED invoices (with NIF: limit 3000\u20AC, without: limit 400\u20AC).\n example: B12345678\n alternative_id:\n allOf:\n - $ref: '#/components/schemas/AlternativeIdentifier'\n - description: Alternative identifier for foreign customers (mutually exclusive with nif)\n address:\n $ref: '#/components/schemas/Address'\n phone:\n $ref: '#/components/schemas/Phone'\n email:\n $ref: '#/components/schemas/Email'\n example:\n customer_id: 4f244735-980b-8d9c-80e8-6331fa0b1958\n CreateInvoiceRequest:\n type: object\n required:\n - type\n - recipient\n - lines\n properties:\n type:\n $ref: '#/components/schemas/InvoiceType'\n series_id:\n type: string\n format: uuid\n description: Invoicing series ID (if not specified, uses default)\n example: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n operation_date:\n type: string\n format: date\n description: |\n Date when the operation actually occurred. Optional.\n\n Use when invoicing for a past operation (e.g., services delivered last month\n but invoiced this month). **Must be today or a past date.**\n\n If omitted, the operation date is assumed to be the same as the issue date (today).\n\n The `issue_date` is always set automatically to today per Spanish anti-fraud law\n (Ley Antifraude / VeriFactu). To issue an invoice on a future date, create a\n draft and use `POST /v1/invoices/{id}/schedule`.\n example: '2025-01-10'\n due_date:\n type: string\n format: date\n description: |\n Payment due date. If not specified, calculated according to payment method.\n **Must be the same as or after the issue date (today).**\n example: '2025-02-14'\n recipient:\n $ref: '#/components/schemas/Recipient'\n lines:\n type: array\n items:\n type: object\n required:\n - description\n - quantity\n - unit_price\n properties:\n description:\n type: string\n minLength: 1\n maxLength: 500\n description: Description of invoiced concept\n example: Web application development - Sprint 1\n quantity:\n type: number\n description: Product/service quantity (can be negative for franchises or discounts)\n example: 40\n unit:\n type: string\n example: hours\n unit_price:\n type: number\n minimum: 0\n exclusiveMinimum: true\n maximum: 999999.9999\n description: |\n Unit price before taxes.\n Supports up to 4 decimal places for micro-pricing (e.g., \u20AC0.0897/unit for labels, packaging).\n example: 50\n discount_percentage:\n type: number\n minimum: 0\n maximum: 100\n default: 0\n example: 10\n description: Discount percentage applied (0-100)\n main_tax:\n $ref: '#/components/schemas/TaxInfo'\n equivalence_surcharge_rate:\n $ref: '#/components/schemas/EquivalenceSurchargePercentage'\n irpf_rate:\n $ref: '#/components/schemas/IrpfPercentage'\n exemption_reason:\n $ref: '#/components/schemas/ExemptionReason'\n nullable: true\n exemption_reason_text:\n type: string\n nullable: true\n maxLength: 500\n description: Custom exemption text. Only used when exemption_reason is OTRO.\n minItems: 1\n example:\n - description: Web application development - Sprint 1\n quantity: 40\n unit: hours\n unit_price: 50\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n irpf_rate: 15\n payment_info:\n $ref: '#/components/schemas/PaymentInfo'\n notes:\n type: string\n maxLength: 1000\n example: Payment by bank transfer. Includes technical support for 30 days.\n rectified_invoice_id:\n type: string\n format: uuid\n description: Required if type is \"CORRECTIVE\"\n example: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n rectification_reason:\n type: string\n maxLength: 500\n description: Required if type is \"CORRECTIVE\"\n example: Amount correction due to calculation error in hours\n metadata:\n type: object\n additionalProperties: true\n description: |\n Additional metadata in key-value format. Useful for storing references\n to external systems like Stripe, order IDs, etc.\n example:\n stripe_payment_id: pi_3NqFGb2eZvKYlo2C0z1234AB\n external_order_id: ORD-2025-0042\n project_code: PROJ-123\n options:\n $ref: '#/components/schemas/InvoiceProcessingOptions'\n example:\n type: STANDARD\n series_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n issue_date: '2025-01-15'\n due_date: '2025-02-14'\n recipient:\n customer_id: 4f244735-980b-8d9c-80e8-6331fa0b1958\n lines:\n - description: Web application development - Sprint 1\n quantity: 40\n unit: hours\n unit_price: 50\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n irpf_rate: 15\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n notes: Payment by bank transfer. Includes technical support for 30 days.\n metadata:\n project_code: PROJ-123\n client_reference: REF-2025-001\n options:\n verifactu_enabled: false\n issue_directly: true\n wait_for_pdf: false\n send_automatically: false\n InvoiceProcessingOptions:\n type: object\n description: |\n Controls how the invoice is processed after creation.\n All fields default to `false` if not specified.\n\n **Common combinations:**\n - Draft (default): omit `options` or set all to `false`\n - Issue immediately: `{ issue_directly: true }`\n - Issue + wait for PDF: `{ issue_directly: true, wait_for_pdf: true }`\n - Issue + send email: `{ issue_directly: true, send_automatically: true }`\n - Full automation: `{ issue_directly: true, wait_for_pdf: true, send_automatically: true, email_config: { ... } }`\n properties:\n verifactu_enabled:\n type: boolean\n default: false\n description: Whether VeriFactu information should be generated for this invoice\n issue_directly:\n type: boolean\n default: false\n description: |\n If `true`, creates the invoice directly as **ISSUED** with a definitive number and PDF.\n If `false` (default), creates as **DRAFT** without number (editable, no PDF).\n wait_for_pdf:\n type: boolean\n default: false\n description: |\n Only applies when `issue_directly` is `true`.\n If `true`, waits for PDF generation before returning the response (~1-3s).\n If `false` (default), PDF is generated asynchronously in the background.\n send_automatically:\n type: boolean\n default: false\n description: |\n Only applies when `issue_directly` is `true`.\n If `true`, sends the invoice by email with PDF attachment after issuing.\n Email is sent asynchronously via the outbox pattern.\n email_config:\n allOf:\n - $ref: '#/components/schemas/EmailConfiguration'\n description: |\n Only applies when `send_automatically` is `true`.\n Overrides default email settings. If not provided, uses the recipient's email.\n example:\n verifactu_enabled: false\n issue_directly: true\n wait_for_pdf: false\n send_automatically: false\n UpdateInvoiceRequest:\n type: object\n properties:\n series_id:\n allOf:\n - $ref: '#/components/schemas/UUID'\n description: |\n Series ID. If provided, changes the invoice series (only for DRAFT invoices).\n The invoice number will be reassigned from the new series when issued.\n operation_date:\n type: string\n format: date\n nullable: true\n description: |\n Date when the operation occurred. **Must be today or a past date.**\n Set to null to clear (operation date = issue date).\n If not provided, keeps the existing value.\n due_date:\n type: string\n format: date\n description: New due date. Must be the same as or after `issue_date`.\n recipient:\n $ref: '#/components/schemas/Recipient'\n lines:\n type: array\n items:\n type: object\n required:\n - description\n - quantity\n - unit_price\n properties:\n description:\n type: string\n minLength: 1\n maxLength: 500\n quantity:\n type: number\n unit:\n type: string\n unit_price:\n type: number\n minimum: 0\n exclusiveMinimum: true\n maximum: 999999.9999\n description: |\n Unit price before taxes.\n Supports up to 4 decimal places for micro-pricing (e.g., \u20AC0.0897/unit for labels, packaging).\n Final amounts are always rounded to 2 decimals.\n discount_percentage:\n type: number\n main_tax:\n $ref: '#/components/schemas/TaxInfo'\n equivalence_surcharge_rate:\n $ref: '#/components/schemas/EquivalenceSurchargePercentage'\n irpf_rate:\n $ref: '#/components/schemas/IrpfPercentage'\n exemption_reason:\n $ref: '#/components/schemas/ExemptionReason'\n nullable: true\n exemption_reason_text:\n type: string\n nullable: true\n maxLength: 500\n payment_info:\n $ref: '#/components/schemas/PaymentInfo'\n notes:\n type: string\n EmailConfiguration:\n type: object\n required:\n - recipients\n properties:\n recipients:\n type: array\n items:\n $ref: '#/components/schemas/Email'\n minItems: 1\n description: List of recipient emails (at least 1 required)\n example:\n - client@example.com\n cc:\n type: array\n items:\n $ref: '#/components/schemas/Email'\n description: List of CC emails (optional)\n example:\n - copy@example.com\n subject:\n type: string\n minLength: 1\n maxLength: 200\n description: Custom email subject (optional, if not specified uses a default)\n example: Invoice 2025/0001 - Web development services\n message:\n type: string\n minLength: 1\n maxLength: 2000\n description: Custom message (optional, added to email body)\n example: Dear customer, please find attached the invoice for the services provided. Thank you for your trust.\n example:\n recipients:\n - client@example.com\n cc:\n - accounting@example.com\n subject: Invoice 2025/0001 - Development services\n message: Please find attached the requested invoice. We remain at your disposal for any clarification.\n SendEmailRequest:\n type: object\n properties:\n recipients:\n type: array\n items:\n $ref: '#/components/schemas/Email'\n description: If not specified, uses the customer's email\n cc:\n type: array\n items:\n $ref: '#/components/schemas/Email'\n subject:\n type: string\n minLength: 1\n maxLength: 200\n description: Email subject (optional, if not specified uses a default)\n message:\n type: string\n minLength: 1\n maxLength: 2000\n description: Custom message (optional, added before standard message)\n attach_pdf:\n type: boolean\n default: true\n language:\n $ref: '#/components/schemas/Language'\n CreateCorrectiveInvoiceRequest:\n type: object\n required:\n - rectification_type\n - rectification_code\n - reason\n properties:\n rectification_type:\n $ref: '#/components/schemas/RectificationType'\n rectification_code:\n $ref: '#/components/schemas/VeriFactuRectificationCode'\n reason:\n type: string\n minLength: 10\n maxLength: 1000\n description: Detailed reason for rectification (minimum 10 characters)\n example: Amount correction due to calculation error in hours worked during the project\n lines:\n type: array\n items:\n type: object\n required:\n - description\n - quantity\n - unit_price\n properties:\n description:\n type: string\n minLength: 1\n maxLength: 500\n description: Concept description\n example: Adjustment for incorrectly invoiced hours\n quantity:\n type: number\n description: Quantity (can be negative for corrective invoices)\n example: -10\n unit:\n type: string\n example: hours\n unit_price:\n type: number\n maximum: 999999.9999\n description: |\n Unit price before taxes (can be negative in corrective invoices).\n Supports up to 4 decimal places for micro-pricing (e.g., \u20AC0.0897/unit for labels, packaging).\n Final amounts are always rounded to 2 decimals.\n example: 50\n discount_percentage:\n type: number\n minimum: 0\n maximum: 100\n default: 0\n main_tax:\n $ref: '#/components/schemas/TaxInfo'\n equivalence_surcharge_rate:\n $ref: '#/components/schemas/EquivalenceSurchargePercentage'\n irpf_rate:\n $ref: '#/components/schemas/IrpfPercentage'\n exemption_reason:\n $ref: '#/components/schemas/ExemptionReason'\n nullable: true\n exemption_reason_text:\n type: string\n nullable: true\n maxLength: 500\n description: |\n **TOTAL**: Optional (if not sent, original invoice lines are copied negated)\n **PARTIAL**: REQUIRED (adjustment lines with positive or negative amounts)\n notes:\n type: string\n maxLength: 1000\n description: Additional observations about the rectification\n example: Rectification requested by the customer due to quantity error\n series_id:\n type: string\n format: uuid\n description: Series for the corrective invoice (optional, if not specified uses the original invoice's series)\n example: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n options:\n $ref: '#/components/schemas/InvoiceProcessingOptions'\n example:\n rectification_type: PARTIAL\n rectification_code: R4\n reason: Amount correction due to calculation error in hours worked during the project\n lines:\n - description: Adjustment for hours error - Sprint 1\n quantity: -5\n unit: hours\n unit_price: 50\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n irpf_rate: 15\n notes: Rectification agreed with the customer on 2025-01-20\n options:\n verifactu_enabled: false\n issue_directly: true\n send_automatically: false\n InvoicePdfResponse:\n type: object\n required:\n - success\n - data\n - meta\n properties:\n success:\n type: boolean\n example: true\n data:\n type: object\n required:\n - download_url\n - expires_in_seconds\n - file_name\n properties:\n download_url:\n type: string\n format: uri\n description: Pre-signed URL to download the PDF (valid for 5 minutes)\n example: https://minio.beel.es/beel-invoices/invoices/user-uuid/factura-uuid.pdf?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...\n expires_in_seconds:\n type: integer\n description: Seconds until the URL expires\n example: 300\n minimum: 1\n file_name:\n type: string\n description: Suggested filename for download\n example: factura_2025-001.pdf\n pattern: ^factura_.+\\.pdf$\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n Customer:\n type: object\n required:\n - id\n - legal_name\n - address\n - created_at\n properties:\n id:\n type: string\n format: uuid\n example: 123e4567-e89b-12d3-a456-426614174000\n legal_name:\n type: string\n example: Empresa Cliente SL\n trade_name:\n type: string\n example: EmpresaCliente\n nif:\n $ref: '#/components/schemas/NIF'\n address:\n $ref: '#/components/schemas/Address'\n phone:\n $ref: '#/components/schemas/Phone'\n email:\n type: string\n format: email\n minLength: 5\n maxLength: 255\n nullable: true\n description: Email address (minimum valid email is 5 chars, e.g. a@b.co)\n web:\n type: string\n maxLength: 255\n nullable: true\n description: Website URL\n pattern: ^(https?://.+|)$\n billing_emails:\n type: array\n items:\n $ref: '#/components/schemas/Email'\n description: Additional emails for invoice delivery\n contact_person:\n type: string\n example: Mar\xEDa Garc\xEDa\n notes:\n type: string\n maxLength: 1000\n preferred_payment_method:\n $ref: '#/components/schemas/PaymentInfo'\n general_discount:\n type: number\n minimum: 0\n maximum: 100\n default: 0\n active:\n type: boolean\n default: true\n created_at:\n type: string\n format: date-time\n updated_at:\n type: string\n format: date-time\n alternative_id:\n $ref: '#/components/schemas/AlternativeIdentifier'\n AlternativeIdentifier:\n type: object\n nullable: true\n required:\n - type\n - number\n description: Alternative identifier for customers without Spanish Tax ID\n properties:\n type:\n type: string\n enum:\n - '02'\n - '03'\n - '04'\n - '05'\n - '06'\n - '07'\n description: |\n - 02: VAT-ID (intra-community)\n - 03: Passport\n - 04: Country of residence ID\n - 05: Residence certificate\n - 06: Other document\n - 07: Not registered\n number:\n type: string\n minLength: 1\n maxLength: 20\n country_code:\n type: string\n pattern: ^[A-Z]{2}$\n minLength: 2\n maxLength: 2\n description: ISO 3166-1 alpha-2 country code\n CreateCustomerRequest:\n type: object\n required:\n - legal_name\n - address\n properties:\n legal_name:\n type: string\n minLength: 1\n maxLength: 120\n pattern: ^\\S.*$\n description: Customer legal name (required)\n trade_name:\n type: string\n maxLength: 120\n nullable: true\n description: Customer trade name (optional)\n nif:\n allOf:\n - $ref: '#/components/schemas/NIF'\n description: Spanish Tax ID (required if id_otro is not provided)\n alternative_id:\n $ref: '#/components/schemas/AlternativeIdentifier'\n address:\n $ref: '#/components/schemas/Address'\n phone:\n $ref: '#/components/schemas/Phone'\n email:\n type: string\n format: email\n minLength: 5\n maxLength: 255\n nullable: true\n description: Email address (minimum valid email is 5 chars, e.g. a@b.co)\n web:\n type: string\n maxLength: 255\n nullable: true\n description: Website URL\n pattern: ^(https?://.+|)$\n billing_emails:\n type: array\n nullable: true\n items:\n $ref: '#/components/schemas/Email'\n description: Additional emails for invoice delivery (optional)\n contact_person:\n type: string\n maxLength: 200\n nullable: true\n description: Contact person name (optional)\n notes:\n type: string\n nullable: true\n description: Additional notes about the customer (optional)\n preferred_payment_method:\n $ref: '#/components/schemas/PaymentInfo'\n general_discount:\n type: number\n minimum: 0\n maximum: 100\n nullable: true\n description: General discount percentage (optional)\n UpdateCustomerRequest:\n type: object\n description: |\n Request to update an existing customer.\n NOTE: The tax identifier (NIF/id_otro) CANNOT be modified once the customer is created.\n properties:\n legal_name:\n type: string\n minLength: 1\n maxLength: 120\n description: Customer legal name\n trade_name:\n type: string\n maxLength: 120\n nullable: true\n description: Customer trade name (optional)\n address:\n $ref: '#/components/schemas/Address'\n phone:\n $ref: '#/components/schemas/Phone'\n email:\n type: string\n format: email\n minLength: 5\n maxLength: 255\n nullable: true\n description: Email address (minimum valid email is 5 chars, e.g. a@b.co)\n web:\n type: string\n maxLength: 255\n nullable: true\n description: Website URL\n pattern: ^(https?://.+|)$\n billing_emails:\n type: array\n nullable: true\n items:\n $ref: '#/components/schemas/Email'\n description: Additional emails for invoice delivery (optional)\n contact_person:\n type: string\n maxLength: 200\n nullable: true\n description: Contact person name (optional)\n notes:\n type: string\n nullable: true\n description: Additional notes about the customer (optional)\n preferred_payment_method:\n $ref: '#/components/schemas/PaymentInfo'\n general_discount:\n type: number\n minimum: 0\n maximum: 100\n nullable: true\n description: General discount percentage (optional)\n active:\n type: boolean\n description: Whether the customer is active or inactive\n CsvImportResult:\n type: object\n required:\n - total_processed\n - successful\n - failed\n - duplicates_skipped\n properties:\n total_processed:\n type: integer\n minimum: 0\n description: Total records processed from CSV\n example: 150\n successful:\n type: integer\n minimum: 0\n description: Number of customers successfully created\n example: 145\n failed:\n type: integer\n minimum: 0\n description: Number of records that failed\n example: 5\n duplicates_skipped:\n type: integer\n minimum: 0\n description: Number of records skipped as duplicates (same Tax ID)\n example: 3\n created_customers:\n type: array\n items:\n $ref: '#/components/schemas/Customer'\n description: List of successfully created customers\n errors:\n type: array\n items:\n $ref: '#/components/schemas/CsvValidationError'\n description: Error details by row\n warnings:\n type: array\n items:\n type: string\n description: Non-critical warnings (missing optional fields)\n example:\n - 'Row 2: Email not provided for customer ''TechSolutions Barcelona SA'''\n - 'Row 5: Invalid phone number for customer ''Mar\xEDa L\xF3pez Fern\xE1ndez'''\n CsvValidationError:\n type: object\n required:\n - row\n - field\n - value\n - error\n properties:\n row:\n type: integer\n minimum: 1\n description: Row number with error (1-based, excluding header)\n example: 12\n field:\n type: string\n description: Field name with error\n example: nif\n value:\n type: string\n description: Value that caused the error\n example: 12345678X\n error:\n type: string\n description: Detailed error message\n example: 'Invalid Tax ID: incorrect format'\n TemplateCsvInfo:\n type: object\n required:\n - filename\n - headers\n - example_rows\n properties:\n filename:\n type: string\n description: Template file name\n example: template_customers_example.csv\n headers:\n type: array\n items:\n type: string\n description: Required CSV headers\n example:\n - legal_name\n - nif\n - email\n - phone\n - address_street\n - address_number\n - address_postal_code\n - address_city\n - address_province\n example_rows:\n type: integer\n description: Number of example rows in the template\n example: 4\n max_records:\n type: integer\n description: Maximum number of allowed records\n example: 1000\n max_file_size_mb:\n type: number\n description: Maximum file size in MB\n example: 5\n CsvImportPreviewResult:\n type: object\n required:\n - metadata\n - customers_preview\n - statistics\n description: |\n Complete result of a CSV import with structured preview.\n Contains all parsed records, statistics and processing metadata.\n properties:\n metadata:\n $ref: '#/components/schemas/CsvImportMetadata'\n customers_preview:\n type: array\n items:\n $ref: '#/components/schemas/CsvCustomerPreview'\n description: Complete list of parsed customers with their validation status\n statistics:\n $ref: '#/components/schemas/CsvImportStatistics'\n CsvImportMetadata:\n type: object\n required:\n - filename\n - file_size_bytes\n - total_rows\n - is_dry_run\n - processing_time_ms\n description: Metadata about the processed CSV file\n properties:\n filename:\n type: string\n description: Original file name\n example: customers_january.csv\n file_size_bytes:\n type: integer\n description: File size in bytes\n example: 1048576\n total_rows:\n type: integer\n description: Total rows processed (excluding headers)\n example: 250\n is_dry_run:\n type: boolean\n description: Whether it was a test processing without persistence\n example: true\n processing_time_ms:\n type: integer\n description: Processing time in milliseconds\n example: 3450\n CsvCustomerPreview:\n type: object\n required:\n - row_number\n - customer\n - status\n - errors\n - warnings\n description: |\n Preview of an individual customer parsed from CSV.\n Includes the parsed customer, validation status and specific errors/warnings.\n properties:\n row_number:\n type: integer\n description: Row number in CSV (starting from 1)\n example: 15\n customer:\n type: object\n nullable: true\n allOf:\n - $ref: '#/components/schemas/Customer'\n description: Parsed customer (null if there are critical parsing errors)\n status:\n $ref: '#/components/schemas/ValidationStatus'\n errors:\n type: array\n items:\n $ref: '#/components/schemas/CsvValidationError'\n description: List of validation errors for this customer\n warnings:\n type: array\n items:\n type: string\n description: List of warnings (customer processable but with observations)\n example:\n - 'Row 15: Email not provided for customer ''Empresa ABC'''\n ValidationStatus:\n type: string\n enum:\n - VALID\n - WARNING\n - ERROR\n - DUPLICATE\n - NIF_INVALID\n description: |\n Validation status of the parsed customer:\n - VALID: Valid customer, ready to import\n - WARNING: Valid customer but with non-critical warnings\n - ERROR: Invalid customer, cannot be processed\n - DUPLICATE: Duplicate customer (in CSV or database)\n - NIF_INVALID: Tax ID not valid according to AEAT registry (VeriFactu)\n CsvImportStatistics:\n type: object\n required:\n - total_processed\n - successful\n - with_warnings\n - failed\n - duplicates\n - invalid_nifs\n - success_rate\n description: Calculated statistics from CSV processing\n properties:\n total_processed:\n type: integer\n description: Total records processed\n example: 250\n successful:\n type: integer\n description: Valid customers without warnings\n example: 180\n with_warnings:\n type: integer\n description: Valid customers but with warnings\n example: 45\n failed:\n type: integer\n description: Customers with validation errors\n example: 15\n duplicates:\n type: integer\n description: Duplicate customers (in CSV or DB)\n example: 8\n invalid_nifs:\n type: integer\n description: Customers with invalid Tax IDs in AEAT\n example: 2\n success_rate:\n type: number\n format: float\n minimum: 0\n maximum: 1\n description: Success rate (successful + with_warnings) / total\n example: 0.9\n Product:\n type: object\n required:\n - id\n - name\n - category\n - main_tax\n - active\n - created_at\n - updated_at\n properties:\n id:\n type: string\n format: uuid\n description: Unique UUID of the product/service\n example: 550e8400-e29b-41d4-a716-446655440000\n code:\n type: string\n maxLength: 50\n pattern: ^[a-zA-Z0-9_-]*$\n nullable: true\n description: Unique alphanumeric product code (optional)\n example: SERV-001\n name:\n type: string\n maxLength: 255\n description: Product/service name\n example: Technical consulting\n description:\n type: string\n description: Detailed product/service description (optional)\n example: Specialized technical consulting services in web development\n category:\n $ref: '#/components/schemas/ProductCategory'\n default_price:\n type: number\n minimum: 0\n multipleOf: 0.0001\n description: Suggested default price (optional)\n example: 85.5\n unit:\n type: string\n maxLength: 50\n description: Unit of measure (optional)\n example: hours\n main_tax:\n $ref: '#/components/schemas/TaxInfo'\n equivalence_surcharge:\n type: number\n minimum: 0\n maximum: 100\n multipleOf: 0.01\n description: Equivalence surcharge percentage (optional)\n example: 5.2\n irpf:\n type: number\n minimum: 0\n maximum: 100\n multipleOf: 0.01\n description: IRPF withholding percentage (optional)\n example: 15\n active:\n type: boolean\n description: Indicates whether the product is active\n example: true\n created_at:\n type: string\n format: date-time\n description: Creation date and time\n example: '2025-01-18T10:30:00Z'\n updated_at:\n type: string\n format: date-time\n description: Last update date and time\n example: '2025-01-18T15:45:30Z'\n ProductCategory:\n type: string\n enum:\n - PRODUCT\n - SERVICE\n - CONSULTING\n - SOFTWARE\n - TRAINING\n - OTHER\n description: |\n Product/service category:\n * PRODUCT - Physical, tangible products\n * SERVICE - General services\n * CONSULTING - Consulting and advisory services\n * SOFTWARE - Development, licenses, SaaS\n * TRAINING - Courses, workshops, training\n * OTHER - Other unclassified types\n example: CONSULTING\n CreateProductRequest:\n type: object\n required:\n - name\n properties:\n code:\n type: string\n maxLength: 50\n pattern: ^[a-zA-Z0-9_-]*$\n nullable: true\n description: Unique alphanumeric product code (optional)\n example: SERV-001\n name:\n type: string\n maxLength: 255\n description: Product/service name\n example: Technical consulting\n description:\n type: string\n description: Detailed description (optional)\n example: Specialized technical consulting services\n category:\n $ref: '#/components/schemas/ProductCategory'\n default_price:\n type: number\n minimum: 0\n multipleOf: 0.0001\n description: Suggested default price (optional)\n example: 85.5\n unit:\n type: string\n maxLength: 50\n description: Unit of measure (optional)\n example: hours\n main_tax:\n $ref: '#/components/schemas/TaxInfo'\n equivalence_surcharge:\n type: number\n minimum: 0\n maximum: 100\n multipleOf: 0.01\n description: Equivalence surcharge percentage (optional)\n example: 5.2\n irpf:\n type: number\n minimum: 0\n maximum: 100\n multipleOf: 0.01\n description: IRPF withholding percentage (optional)\n example: 15\n UpdateProductRequest:\n type: object\n properties:\n code:\n type: string\n maxLength: 50\n pattern: ^[a-zA-Z0-9_-]*$\n nullable: true\n description: Unique alphanumeric product code (optional)\n example: SERV-001\n name:\n type: string\n maxLength: 255\n description: Product/service name\n example: Technical consulting\n description:\n type: string\n description: Detailed description (optional)\n example: Specialized technical consulting services\n category:\n $ref: '#/components/schemas/ProductCategory'\n default_price:\n type: number\n minimum: 0\n multipleOf: 0.0001\n description: Suggested default price (optional)\n example: 85.5\n unit:\n type: string\n maxLength: 50\n description: Unit of measure (optional)\n example: hours\n main_tax:\n $ref: '#/components/schemas/TaxInfo'\n equivalence_surcharge:\n type: number\n minimum: 0\n maximum: 100\n multipleOf: 0.01\n description: Equivalence surcharge percentage (optional)\n example: 5.2\n irpf:\n type: number\n minimum: 0\n maximum: 100\n multipleOf: 0.01\n description: IRPF withholding percentage (optional)\n example: 15\n active:\n type: boolean\n description: Indicates whether the product is active\n example: true\n ProductResponse:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n $ref: '#/components/schemas/Product'\n ProductsListResponse:\n allOf:\n - $ref: '#/components/schemas/SuccessResponse'\n - type: object\n properties:\n data:\n type: object\n properties:\n products:\n type: array\n items:\n $ref: '#/components/schemas/Product'\n pagination:\n $ref: '#/components/schemas/Pagination'\n ProductQueryParams:\n type: object\n properties:\n category:\n $ref: '#/components/schemas/ProductCategory'\n active:\n type: boolean\n description: Filter by active/inactive status\n example: true\n search:\n type: string\n maxLength: 100\n description: Search in name, description or code\n example: consulting\n page:\n type: integer\n minimum: 0\n default: 0\n description: Page number (0-indexed)\n example: 0\n size:\n type: integer\n minimum: 1\n maximum: 100\n default: 20\n description: Page size\n example: 20\n SeriesFormat:\n type: string\n minLength: 1\n maxLength: 255\n pattern: ^[A-Z0-9\\-_/{}:]*$\n example: '{CODIGO}-{YYYY}-{NUM:4}'\n description: |\n Format template with available variables (UPPERCASE ONLY):\n - {CODIGO}: Series code (e.g., \"FAC\")\n - {YYYY}: Year with 4 digits (e.g., \"2025\")\n - {YY}: Year with 2 digits (e.g., \"25\")\n - {MM}: Month with 2 digits (e.g., \"01\")\n - {NUM}: Sequential number without padding (e.g., \"1\")\n - {NUM:X}: Sequential number with padding (e.g., {NUM:4} \u2192 \"0001\")\n\n **REQUIRED**: Must contain at least {NUM} or {NUM:X}\n **IMPORTANT**: Only uppercase (rejects {yy}, {mm}, {codigo}, etc.)\n\n Valid examples:\n - \"{CODIGO}-{YYYY}-{NUM:4}\" \u2192 \"FAC-2025-0001\"\n - \"{CODIGO}/{NUM:6}\" \u2192 \"FAC/000001\"\n - \"{YYYY}{MM}-{NUM:3}\" \u2192 \"202501-001\"\n CounterReset:\n type: string\n enum:\n - NEVER\n - ANNUAL\n - MONTHLY\n default: ANNUAL\n description: |\n Counter reset policy:\n - NEVER: Counter never resets (continuous numbering)\n - ANNUAL: Counter resets yearly\n - MONTHLY: Counter resets monthly\n SeriesCode:\n type: string\n pattern: ^[A-Z0-9\\-_]{1,50}$\n minLength: 1\n maxLength: 50\n example: FAC\n description: |\n Alphanumeric series code (used in {CODIGO} variable).\n Allows uppercase letters, numbers, hyphens and underscores.\n VeriFactuConfiguration:\n type: object\n required:\n - enabled\n - apply_by_default\n properties:\n enabled:\n type: boolean\n default: false\n description: |\n Whether VeriFactu is enabled for this user.\n When enabled, the user can submit invoices to AEAT.\n Freelancers who don't need to submit invoices to AEAT can leave it disabled.\n apply_by_default:\n type: boolean\n default: false\n description: |\n Whether VeriFactu should be automatically applied to new invoices.\n Requires 'enabled' to be true.\n nif_status:\n type: string\n nullable: true\n enum:\n - PENDING\n - ACTIVATED\n - DEACTIVATED\n - ERROR\n description: |\n VeriFactu registration status (can be null if VeriFactu was never enabled):\n - PENDING: Initial registration in progress\n - ACTIVATED: NIF validated and user created in the corresponding environment\n - DEACTIVATED: Deactivated (data retained for 30 days)\n - ERROR: Some step of the process failed\n nif_registered_at:\n type: string\n format: date-time\n nullable: true\n description: Successful activation date in VeriFactu production\n TaxConfiguration:\n type: object\n required:\n - default_main_tax\n - apply_equivalence_surcharge\n - apply_irpf\n properties:\n default_main_tax:\n allOf:\n - $ref: '#/components/schemas/TaxInfo'\n - description: |\n Default main tax configuration for new invoices.\n Includes tax regime type (IVA/IGIC/IPSI/OTHER), percentage and regime key.\n\n Common examples:\n - Standard VAT: { type: \"IVA\", percentage: 21, regime_key: \"01\" }\n - Special Reduced IGIC: { type: \"IGIC\", percentage: 7, regime_key: \"01\" }\n - Standard IPSI: { type: \"IPSI\", percentage: 10, regime_key: \"01\" }\n - default:\n type: IVA\n percentage: 21\n regime_key: '01'\n apply_equivalence_surcharge:\n type: boolean\n default: false\n description: |\n Whether the freelancer is under the equivalence surcharge regime.\n\n **Business rule**: If `true`, the `default_equivalence_surcharge` field is REQUIRED.\n default_equivalence_surcharge:\n allOf:\n - $ref: '#/components/schemas/EquivalenceSurchargePercentage'\n description: |\n Default equivalence surcharge.\n\n **Business rule**: REQUIRED if `apply_equivalence_surcharge` is `true`.\n apply_irpf:\n type: boolean\n default: true\n description: |\n Whether IRPF withholding should be applied to invoices.\n\n **Business rule**: If `true` and `irpf_exempt` is `false`,\n the `default_irpf_rate` field is REQUIRED.\n default_irpf_rate:\n allOf:\n - $ref: '#/components/schemas/IrpfPercentage'\n description: |\n Default IRPF for new invoices.\n\n **Business rule**: REQUIRED if `apply_irpf` is `true` and `irpf_exempt` is `false`.\n irpf_exempt:\n type: boolean\n default: false\n description: |\n Whether the freelancer is exempt from IRPF withholding (e.g., business start).\n\n **Note**: When `true`, any provided `default_irpf_rate` is saved but not applied to invoices.\n This allows pre-configuring the rate for when the exemption ends.\n default_payment_method:\n allOf:\n - $ref: '#/components/schemas/PaymentMethod'\n default: BANK_TRANSFER\n description: |\n Default payment method for new invoices.\n If NONE is selected, no payment information will be shown on the invoice.\n payment_term_days:\n type: integer\n nullable: true\n minimum: 0\n maximum: 365\n description: Default payment term in days (0-365). Null means no default term.\n InvoiceSeries:\n type: object\n required:\n - id\n - name\n - code\n - format\n - counter_reset\n - active\n - default_series\n properties:\n id:\n type: string\n format: uuid\n description: Unique series ID\n name:\n type: string\n minLength: 1\n maxLength: 100\n example: Main Series\n description: Descriptive name of the series\n code:\n $ref: '#/components/schemas/SeriesCode'\n description:\n type: string\n maxLength: 1000\n example: Series for standard invoices\n description: Optional series description\n format:\n $ref: '#/components/schemas/SeriesFormat'\n counter_reset:\n $ref: '#/components/schemas/CounterReset'\n initial_number:\n type: integer\n format: int64\n minimum: 1\n maximum: 999999\n default: 1\n example: 1\n description: |\n Initial number configured for this series counter.\n Defines from which number invoice numbering will start.\n active:\n type: boolean\n default: true\n description: Indicates whether the series is active\n default_series:\n type: boolean\n default: false\n description: Indicates whether this is the user's default series (only one can be)\n created_at:\n type: string\n format: date-time\n description: Creation date\n next_number:\n type: integer\n format: int64\n readOnly: true\n description: Next invoice number that will be assigned for this series\n updated_at:\n type: string\n format: date-time\n description: Last update date\n TaxRegime:\n type: object\n required:\n - code\n - name\n - description\n - tax_rates\n - applies_equivalence_surcharge\n - regime_keys\n properties:\n code:\n type: string\n description: Tax regime code\n enum:\n - IVA\n - IGIC\n - IPSI\n - OTHER\n example: IVA\n name:\n type: string\n description: Tax regime name\n example: IVA\n description:\n type: string\n description: Detailed description of the tax regime\n example: Value Added Tax\n tax_rates:\n type: array\n items:\n $ref: '#/components/schemas/TaxPercentage'\n description: Available percentages in this regime\n applies_equivalence_surcharge:\n type: boolean\n description: Whether this regime applies equivalence surcharge\n regime_keys:\n type: array\n items:\n $ref: '#/components/schemas/VeriFactuRegimeKey'\n description: Valid VeriFactu regime keys for this tax type\n VeriFactuRegimeKey:\n type: object\n required:\n - code\n - description\n properties:\n code:\n type: string\n description: VeriFactu regime code\n pattern: ^[0-9]{2}$\n example: '01'\n description:\n type: string\n description: Regime key description\n example: General regime operation\n TaxPercentage:\n type: object\n required:\n - percentage\n - description\n - active\n properties:\n percentage:\n type: number\n description: Tax percentage\n example: 21\n description:\n type: string\n description: Tax type description\n example: IVA (21%)\n active:\n type: boolean\n description: Indicates whether the type is active\n default: true\n associated_equivalence_surcharge:\n type: number\n description: Associated equivalence surcharge percentage (only for VAT)\n minimum: 0\n example: 5.2\n nullable: true\n VatType:\n type: object\n required:\n - percentage\n - description\n - verifactu_code\n - active\n properties:\n percentage:\n type: number\n description: VAT percentage\n example: 21\n description:\n type: string\n description: VAT type description\n example: IVA General (21%)\n verifactu_code:\n type: string\n description: VAT type code according to VeriFactu regulations\n example: S1\n active:\n type: boolean\n description: Whether the VAT type is active\n default: true\n IrpfType:\n type: object\n required:\n - percentage\n - description\n - active\n properties:\n percentage:\n type: number\n description: IRPF withholding percentage\n example: 15\n description:\n type: string\n description: IRPF type description\n example: IRPF Profesional (15%)\n active:\n type: boolean\n description: Whether the IRPF type is active\n default: true\n EquivalenceSurcharge:\n type: object\n required:\n - percentage\n - associated_vat\n - description\n - active\n properties:\n percentage:\n type: number\n description: Equivalence surcharge percentage\n example: 5.2\n associated_vat:\n type: number\n description: VAT percentage to which this surcharge is associated\n example: 21\n description:\n type: string\n description: Equivalence surcharge description\n example: RE 5.2% (IVA 21%)\n active:\n type: boolean\n description: Whether the surcharge is active\n default: true\n CreateSeriesRequest:\n type: object\n required:\n - name\n - code\n - format\n - counter_reset\n properties:\n name:\n type: string\n minLength: 1\n maxLength: 100\n example: Main Series\n description: Descriptive name of the series\n code:\n $ref: '#/components/schemas/SeriesCode'\n description:\n type: string\n maxLength: 1000\n nullable: true\n example: Series for standard invoices\n description: Optional series description\n format:\n $ref: '#/components/schemas/SeriesFormat'\n counter_reset:\n $ref: '#/components/schemas/CounterReset'\n initial_number:\n type: integer\n format: int64\n minimum: 1\n maximum: 999999\n default: 1\n example: 1\n description: |\n Initial number for this series counter.\n Useful when migrating from another system and wanting to continue existing numbering.\n For example, if the last invoices were 2024-0150, you can set initial_number=151.\n Default value is 1.\n active:\n type: boolean\n default: true\n description: Whether the series is active\n default_series:\n type: boolean\n default: false\n description: Whether this is the default series\n UpdateSeriesRequest:\n type: object\n description: |\n Request to update an invoice series.\n\n **IMPORTANT:** All fields are optional, but can only be modified\n when the series **has NO associated invoices**. If the series already has issued invoices,\n no fields can be updated to maintain numbering integrity.\n properties:\n name:\n type: string\n minLength: 1\n maxLength: 100\n example: Main Series\n description: Descriptive name of the series\n code:\n $ref: '#/components/schemas/SeriesCode'\n description:\n type: string\n maxLength: 1000\n nullable: true\n example: Series for standard invoices\n description: Optional series description\n format:\n $ref: '#/components/schemas/SeriesFormat'\n counter_reset:\n $ref: '#/components/schemas/CounterReset'\n initial_number:\n type: integer\n format: int64\n minimum: 1\n maximum: 999999\n example: 54\n description: |\n Initial number for this series counter.\n Useful for adjusting numbering or resetting the counter.\n For example, if you want the next invoice to be 2025-0054, set initial_number=54.\n active:\n type: boolean\n description: |\n Whether the series is active.\n\n **Restriction:** A default series cannot be deactivated\n (another must be set as default first).\n default_series:\n type: boolean\n description: |\n Whether this is the default series.\n\n **Restriction:** An inactive series cannot be marked as default.\n UpdateTaxConfigurationRequest:\n type: object\n properties:\n default_main_tax:\n allOf:\n - $ref: '#/components/schemas/TaxInfo'\n description: |\n Default main tax configuration.\n If provided, completely replaces the current configuration.\n apply_equivalence_surcharge:\n type: boolean\n default: false\n description: Whether the freelancer is under the equivalence surcharge regime\n default_equivalence_surcharge:\n $ref: '#/components/schemas/EquivalenceSurchargePercentage'\n apply_irpf:\n type: boolean\n default: true\n description: Whether IRPF withholding should be applied\n default_irpf_rate:\n $ref: '#/components/schemas/IrpfPercentage'\n irpf_exempt:\n type: boolean\n default: false\n description: Whether the freelancer is exempt from IRPF withholding\n default_payment_method:\n type: string\n nullable: true\n allOf:\n - $ref: '#/components/schemas/PaymentMethod'\n description: |\n Default payment method for new invoices.\n If NONE is selected, no payment information will be shown on the invoice.\n payment_term_days:\n type: integer\n nullable: true\n minimum: 0\n maximum: 365\n description: Default payment term in days (0-365)\n ValidateNifRequest:\n type: object\n required:\n - nif\n properties:\n nif:\n allOf:\n - $ref: '#/components/schemas/NIF'\n - description: |\n NIF/CIF to validate against the AEAT census.\n Must be 9 alphanumeric characters.\n legal_name:\n type: string\n nullable: true\n minLength: 1\n maxLength: 255\n description: |\n Last name and first name (individual) or business name (legal entity).\n\n **Important**:\n - REQUIRED for individuals (NIFs starting with a number)\n - OPTIONAL for legal entities (NIFs starting with a letter)\n\n AEAT validates that the provided name matches the one registered in the census.\n If it doesn't match, validation will return INVALID.\n example: JUAN PEREZ GARCIA\n ValidateNifResponse:\n type: object\n required:\n - valid\n - status\n - message\n properties:\n valid:\n type: boolean\n description: |\n true if the NIF is validated against the AEAT census (status = VALID).\n false otherwise.\n example: true\n status:\n allOf:\n - $ref: '#/components/schemas/NifValidationStatus'\n - description: Validation status\n legal_name:\n type: string\n nullable: true\n description: |\n Legal name obtained from the AEAT census.\n Only available if status = VALID.\n example: JUAN PEREZ GARCIA\n message:\n type: string\n description: |\n Descriptive message of the validation result.\n\n Examples:\n - VALID: \"NIF successfully validated against the AEAT census\"\n - INVALID: \"NIF does not exist in the AEAT census\"\n - PENDING: \"NIF validation is pending. The system will automatically validate it when the service is available.\"\n - ERROR: \"Error validating NIF: VeriFactu API timeout\"\n example: NIF successfully validated against the AEAT census\n validated_at:\n type: string\n format: date-time\n nullable: true\n description: |\n Timestamp of when the NIF was successfully validated.\n Only available if status = VALID.\n example: '2025-01-15T10:30:00Z'\n InvoiceTemplateOption:\n type: object\n required:\n - code\n - name\n - description\n - features\n properties:\n code:\n type: string\n enum:\n - MODERN_TABLE\n - PROFESSIONAL_SERVICE\n description: Unique template code\n example: MODERN_TABLE\n name:\n type: string\n description: Descriptive template name\n example: Modern Table\n description:\n type: string\n description: Detailed template description\n example: Design with structured table, ideal for standard products and services\n preview_url:\n type: string\n nullable: true\n description: URL to a template preview image\n example: /assets/previews/modern-table.png\n features:\n type: array\n description: List of template features\n items:\n type: string\n example:\n - Structured table with columns\n - Quantity, unit price and totals\n - Clean and minimal design\n ColorOption:\n type: object\n required:\n - hex\n - name\n - category\n properties:\n hex:\n type: string\n pattern: ^#[0-9A-Fa-f]{6}$\n description: Color in hexadecimal format\n example: '#fc481d'\n name:\n type: string\n description: Descriptive color name\n example: BeeL Orange\n category:\n type: string\n enum:\n - DEFAULT\n - PROFESSIONAL\n - CREATIVE\n - CLASSIC\n description: |\n Color category:\n - DEFAULT: BeeL brand colors\n - PROFESSIONAL: Professional tones (grays, dark blues)\n - CREATIVE: Striking colors (violet, pink, amber)\n - CLASSIC: Classic colors (blue, red, green, purple)\n example: DEFAULT\n InvoiceCustomizationOptionsResponse:\n type: object\n required:\n - template_types\n - suggested_colors\n properties:\n template_types:\n type: array\n description: Available template types\n items:\n $ref: '#/components/schemas/InvoiceTemplateOption'\n suggested_colors:\n type: array\n description: Suggested color palette\n items:\n $ref: '#/components/schemas/ColorOption'\n TaxType:\n type: string\n enum:\n - IVA\n - IGIC\n - IPSI\n - OTHER\n description: |\n Tax type by territory:\n - IVA: Iberian Peninsula and Balearic Islands (0%, 4%, 10%, 21%)\n - IGIC: Canary Islands (0%, 3%, 5%, 7%, 9.5%, 15%, 20%)\n - IPSI: Ceuta and Melilla (0.5%, 1%, 2%, 4%, 8%, 10%)\n - OTHER: Configurable 0%-100%\n example: IVA\n RegimeKey:\n type: string\n enum:\n - '01'\n - '02'\n - '03'\n - '04'\n - '05'\n - '06'\n - '07'\n - '08'\n - '09'\n - '10'\n - '11'\n - '14'\n - '15'\n - '17'\n - '18'\n - '19'\n - '20'\n default: '01'\n description: |\n Regime key according to VeriFactu regulations:\n - 01: General regime operation\n - 02: Export\n - 03: Used goods, art, antiques\n - 04: Investment gold\n - 05: Travel agencies\n - 06: Group of entities\n - 07: Cash basis\n - 08: IPSI/IVA/IGIC operations\n - 09: Mediating agencies\n - 10: Third-party collections\n - 11: Local rental\n - 14: VAT pending in certifications\n - 15: VAT pending successive tract\n - 17: OSS and IOSS\n - 18: Equivalence surcharge\n - 19: REAGYP\n - 20: Simplified regime\n example: '01'\n TaxInfo:\n type: object\n required:\n - type\n - percentage\n properties:\n type:\n $ref: '#/components/schemas/TaxType'\n percentage:\n type: number\n minimum: 0\n maximum: 100\n description: Tax percentage\n example: 21\n regime_key:\n $ref: '#/components/schemas/RegimeKey'\n description: |\n Complete tax information with cross-validations:\n - IVA: only percentages 0, 4, 10, 21\n - IGIC: only percentages 0, 3, 5, 7, 9.5, 15, 20\n - IPSI: only percentages 0.5, 1, 2, 4, 8, 10\n - OTHER: any percentage between 0 and 100\n example:\n type: IVA\n percentage: 21\n regime_key: '01'\n IgicPercentage:\n type: number\n description: |\n Applied IGIC percentage.\n Valid value validation is performed in the backend according to the configured tax regime.\n Common IGIC values (Canary Islands):\n - 0%: Exempt\n - 3%: Reduced\n - 5%: Special reduced (energy drinks)\n - 7%: Standard\n - 9.5%: Increased\n - 15%: Higher increased\n - 20%: Special\n minimum: 0\n maximum: 100\n example: 7\n IpsiPercentage:\n type: number\n description: |\n Applied IPSI percentage.\n Valid value validation is performed in the backend according to the configured tax regime.\n Common IPSI values (Ceuta and Melilla):\n - 0.5%: Super-reduced (social housing, games, advertising)\n - 1%: Reduced (taxis, bars, electricity)\n - 2%: Intermediate (restaurants, hospitality)\n - 4%: Medium (renovations, real estate)\n - 8%: High (telecommunications, electronic services)\n - 10%: Standard (construction)\n minimum: 0\n maximum: 100\n example: 10\n OtherTaxPercentage:\n type: number\n minimum: 0\n maximum: 100\n description: |\n Customizable percentage for OTHER.\n Allows any value between 0% and 100%.\n example: 5\n CustomerValidationUnifiedResult:\n type: object\n required:\n - metadata\n - customers_validation\n - statistics\n description: |\n Unified format for customer validation results.\n Used for bulk validation (JSON), CSV import and Holded Excel import.\n\n Combines the functionality of CrearClientesBulk200Response, CsvImportPreviewResult\n and HoldedImportResult into a consistent structure that simplifies the frontend.\n properties:\n metadata:\n $ref: '#/components/schemas/CustomerValidationMetadata'\n customers_validation:\n type: array\n items:\n $ref: '#/components/schemas/CustomerValidationItem'\n description: List of processed customers with their validation status\n statistics:\n $ref: '#/components/schemas/CustomerValidationStatistics'\n CustomerValidationMetadata:\n type: object\n required:\n - total_customers\n - is_dry_run\n - processing_time_ms\n - source_type\n description: |\n Metadata about the validation operation.\n Compatible with bulk JSON, CSV import and Holded Excel import.\n properties:\n total_customers:\n type: integer\n description: Total number of customers submitted\n example: 10\n is_dry_run:\n type: boolean\n description: Whether it was validation (true) or import (false)\n example: true\n processing_time_ms:\n type: integer\n description: Processing time in milliseconds\n example: 450\n source_type:\n type: string\n enum:\n - BULK_JSON\n - CSV_IMPORT\n - HOLDED_EXCEL\n description: |\n Data source type:\n - BULK_JSON: Data sent as JSON to bulk endpoint\n - CSV_IMPORT: Data parsed from CSV file\n - HOLDED_EXCEL: Data parsed from Holded Excel file\n example: CSV_IMPORT\n filename:\n type: string\n nullable: true\n description: File name (CSV or Excel depending on source_type)\n example: clientes_enero.csv\n file_size_bytes:\n type: integer\n nullable: true\n description: File size in bytes\n example: 1048576\n total_rows:\n type: integer\n nullable: true\n description: Total file rows excluding headers\n example: 250\n CustomerValidationItem:\n type: object\n required:\n - index\n - customer\n - status\n - errors\n - warnings\n description: |\n Individual customer validation result.\n Works for bulk JSON, CSV import and Holded Excel import.\n properties:\n index:\n type: integer\n description: |\n Customer index in the submitted list (0-based for bulk, 1-based for files).\n For CSV/Excel, represents the row number in the file.\n example: 0\n customer:\n allOf:\n - $ref: '#/components/schemas/Customer'\n description: |\n Parsed and validated customer.\n Can be null if there were critical parsing errors.\n status:\n type: string\n enum:\n - VALID\n - WARNING\n - ERROR\n - DUPLICATE\n - NIF_INVALID\n description: |\n Customer validation status:\n - VALID: Completely valid customer\n - WARNING: Valid customer but with non-critical warnings\n - ERROR: Invalid customer, cannot be processed\n - DUPLICATE: Duplicate customer (in batch/CSV or database)\n - NIF_INVALID: NIF not valid according to AEAT census (VeriFactu)\n example: VALID\n errors:\n type: array\n description: List of errors found (block import)\n items:\n $ref: '#/components/schemas/CustomerValidationError'\n warnings:\n type: array\n description: List of warnings (do not block import)\n items:\n $ref: '#/components/schemas/CustomerValidationWarning'\n row_number:\n type: integer\n nullable: true\n description: Row number in the file (CSV or Excel depending on source_type)\n example: 15\n CustomerValidationError:\n type: object\n required:\n - field\n - value\n - message\n description: Field-specific validation error\n properties:\n field:\n type: string\n description: Field that failed validation\n example: nif\n value:\n type: string\n description: Value that caused the error\n example: 12345678X\n message:\n type: string\n description: Error explanatory message\n example: NIF not found in AEAT census\n CustomerValidationWarning:\n type: object\n required:\n - field\n - message\n description: Validation warning (does not block import)\n properties:\n field:\n type: string\n description: Field with warning\n example: email\n message:\n type: string\n description: Warning message\n example: Email not provided, NIF will be used for invoicing\n CustomerValidationStatistics:\n type: object\n required:\n - total_processed\n - valid\n - with_warnings\n - with_errors\n - duplicates\n - invalid_nifs\n - imported\n - success_rate\n description: |\n Aggregated validation statistics.\n Compatible with all source types (bulk JSON, CSV and Holded Excel).\n properties:\n total_processed:\n type: integer\n description: Total customers processed\n example: 10\n valid:\n type: integer\n description: Completely valid customers (no warnings or errors)\n example: 6\n with_warnings:\n type: integer\n description: Valid customers but with warnings\n example: 2\n with_errors:\n type: integer\n description: Customers with errors (not importable)\n example: 2\n duplicates:\n type: integer\n description: Customers with duplicate NIFs\n example: 1\n invalid_nifs:\n type: integer\n description: Customers with NIFs not valid in AEAT\n example: 1\n imported:\n type: integer\n description: Customers actually imported (only if is_dry_run=false)\n example: 0\n success_rate:\n type: number\n format: float\n minimum: 0\n maximum: 1\n description: Success rate (valid + with_warnings) / total_processed\n example: 0.8\n importable:\n type: integer\n description: Total customers that can be imported (valid + with_warnings)\n example: 8\n not_importable:\n type: integer\n description: Total customers that CANNOT be imported (with_errors + duplicates + invalid_nifs)\n example: 2\n RecurringInvoiceResponse:\n type: object\n properties:\n id:\n type: string\n format: uuid\n name:\n type: string\n frequency:\n type: string\n enum:\n - MONTHLY\n day_of_month:\n type: integer\n start_date:\n type: string\n format: date\n end_date:\n type: string\n format: date\n nullable: true\n next_generation:\n type: string\n format: date\n preview_days:\n type: integer\n description: Days before emission date to create a draft for review. 0 means immediate emission.\n example: 3\n status:\n type: string\n enum:\n - ACTIVE\n - PAUSED\n - COMPLETED\n series_id:\n type: string\n format: uuid\n series_code:\n type: string\n invoice_type:\n type: string\n enum:\n - STANDARD\n - SIMPLIFIED\n customer_id:\n type: string\n format: uuid\n nullable: true\n recipient_fiscal_name:\n type: string\n nullable: true\n recipient_nif:\n type: string\n nullable: true\n lines:\n type: array\n items:\n $ref: '#/components/schemas/InvoiceLineTemplateResponse'\n payment_method:\n type: string\n nullable: true\n notes:\n type: string\n nullable: true\n verifactu_enabled:\n type: boolean\n send_automatically:\n type: boolean\n email_configuration:\n $ref: '#/components/schemas/RecurringEmailConfigResponse'\n generated_invoices:\n type: integer\n last_generation:\n type: string\n format: date-time\n nullable: true\n source_invoice_id:\n type: string\n format: uuid\n nullable: true\n created_at:\n type: string\n format: date-time\n updated_at:\n type: string\n format: date-time\n InvoiceLineTemplateResponse:\n type: object\n properties:\n id:\n type: string\n format: uuid\n order:\n type: integer\n description:\n type: string\n quantity:\n type: number\n unit:\n type: string\n nullable: true\n unit_price:\n type: number\n discount_percentage:\n type: number\n nullable: true\n tax_type:\n type: string\n vat_rate:\n type: number\n regime_key:\n type: string\n equivalence_surcharge_rate:\n type: number\n nullable: true\n irpf_rate:\n type: number\n nullable: true\n RecurringEmailConfigResponse:\n type: object\n nullable: true\n properties:\n recipients:\n type: array\n items:\n type: string\n cc:\n type: array\n items:\n type: string\n subject:\n type: string\n nullable: true\n message:\n type: string\n nullable: true\n UpdateRecurringInvoiceRequest:\n type: object\n properties:\n name:\n type: string\n maxLength: 255\n day_of_month:\n type: integer\n minimum: 1\n maximum: 31\n start_date:\n type: string\n format: date\n preview_days:\n type: integer\n minimum: 0\n maximum: 30\n description: Days before emission date to create a draft for review. 0 means immediate emission.\n end_date:\n type: string\n format: date\n nullable: true\n series_id:\n type: string\n format: uuid\n customer_id:\n type: string\n format: uuid\n nullable: true\n lines:\n type: array\n minItems: 1\n items:\n $ref: '#/components/schemas/RecurringLineRequest'\n payment_method:\n allOf:\n - $ref: '#/components/schemas/PaymentMethod'\n nullable: true\n payment_iban:\n type: string\n nullable: true\n payment_swift:\n type: string\n nullable: true\n payment_term_days:\n type: integer\n nullable: true\n notes:\n type: string\n nullable: true\n verifactu_enabled:\n type: boolean\n send_automatically:\n type: boolean\n email_configuration:\n $ref: '#/components/schemas/RecurringEmailConfigRequest'\n CreateRecurringInvoiceRequest:\n type: object\n required:\n - name\n - day_of_month\n - start_date\n - series_id\n - invoice_type\n - lines\n properties:\n name:\n type: string\n maxLength: 255\n day_of_month:\n type: integer\n minimum: 1\n maximum: 31\n start_date:\n type: string\n format: date\n preview_days:\n type: integer\n minimum: 0\n maximum: 30\n default: 0\n description: Days before emission date to create a draft for review. 0 means immediate emission.\n end_date:\n type: string\n format: date\n nullable: true\n series_id:\n type: string\n format: uuid\n invoice_type:\n type: string\n enum:\n - STANDARD\n - SIMPLIFIED\n customer_id:\n type: string\n format: uuid\n nullable: true\n lines:\n type: array\n minItems: 1\n items:\n $ref: '#/components/schemas/RecurringLineRequest'\n payment_method:\n allOf:\n - $ref: '#/components/schemas/PaymentMethod'\n nullable: true\n payment_iban:\n type: string\n nullable: true\n payment_swift:\n type: string\n nullable: true\n payment_term_days:\n type: integer\n nullable: true\n notes:\n type: string\n nullable: true\n verifactu_enabled:\n type: boolean\n default: false\n send_automatically:\n type: boolean\n default: false\n email_configuration:\n $ref: '#/components/schemas/RecurringEmailConfigRequest'\n CreateRecurringFromInvoiceRequest:\n type: object\n required:\n - name\n - day_of_month\n - start_date\n properties:\n name:\n type: string\n maxLength: 255\n day_of_month:\n type: integer\n minimum: 1\n maximum: 31\n start_date:\n type: string\n format: date\n end_date:\n type: string\n format: date\n nullable: true\n verifactu_enabled:\n type: boolean\n default: false\n send_automatically:\n type: boolean\n default: false\n email_configuration:\n $ref: '#/components/schemas/RecurringEmailConfigRequest'\n RecurringLineRequest:\n type: object\n required:\n - description\n - quantity\n - unit_price\n - vat_rate\n properties:\n description:\n type: string\n maxLength: 500\n quantity:\n type: number\n minimum: 0.01\n unit:\n type: string\n maxLength: 20\n unit_price:\n type: number\n minimum: 0\n discount_percentage:\n type: number\n minimum: 0\n maximum: 100\n tax_type:\n type: string\n default: IVA\n vat_rate:\n type: number\n regime_key:\n type: string\n default: '01'\n equivalence_surcharge_rate:\n type: number\n nullable: true\n irpf_rate:\n type: number\n nullable: true\n RecurringEmailConfigRequest:\n type: object\n nullable: true\n properties:\n recipients:\n type: array\n items:\n type: string\n cc:\n type: array\n items:\n type: string\n subject:\n type: string\n nullable: true\n message:\n type: string\n nullable: true\n GenerationHistoryResponse:\n type: object\n properties:\n id:\n type: string\n format: uuid\n invoice_id:\n type: string\n format: uuid\n generated_at:\n type: string\n format: date-time\n scheduled_date:\n type: string\n format: date\n CreateCompanyRequest:\n type: object\n required:\n - nif\n - legal_name\n - entity_type\n - address_street\n - address_postal_code\n - address_city\n - address_province\n properties:\n nif:\n type: string\n description: NIF/CIF of the business\n example: B12345678\n legal_name:\n type: string\n description: Legal/fiscal name\n example: Mi Empresa SL\n entity_type:\n type: string\n enum:\n - INDIVIDUAL\n - LEGAL_ENTITY\n description: Type of entity\n address_street:\n type: string\n example: Calle Mayor\n address_number:\n type: string\n example: '10'\n address_postal_code:\n type: string\n example: '28001'\n address_city:\n type: string\n example: Madrid\n address_province:\n type: string\n example: Madrid\n address_country:\n type: string\n default: ES\n legal_form:\n type: string\n description: Required for LEGAL_ENTITY\n representative_name:\n type: string\n description: Required for LEGAL_ENTITY\n representative_nif:\n type: string\n description: Required for LEGAL_ENTITY\n business_display_name:\n type: string\n description: Optional display name\n tax_type:\n type: string\n default: IVA\n tax_percentage:\n type: number\n default: 21\n irpf_percentage:\n type: number\n default: 15\n UpdateCompanyRequest:\n type: object\n description: Editable fields of a company. NIF, legal name, and entity type are immutable.\n properties:\n business_display_name:\n type: string\n description: Friendly display name for the company\n address_street:\n type: string\n address_number:\n type: string\n address_postal_code:\n type: string\n address_city:\n type: string\n address_province:\n type: string\n address_country:\n type: string\n CompanyData:\n type: object\n properties:\n id:\n type: string\n format: uuid\n nif:\n type: string\n legal_name:\n type: string\n business_display_name:\n type: string\n entity_type:\n type: string\n enum:\n - INDIVIDUAL\n - LEGAL_ENTITY\n is_primary:\n type: boolean\n verifactu_status:\n type: string\n enum:\n - NOT_CONFIGURED\n - PENDING\n - ACTIVE\n - ERROR\n can_issue_production_invoices:\n type: boolean\n created_at:\n type: string\n format: date-time\n CompanyResponse201:\n type: object\n properties:\n success:\n type: boolean\n data:\n $ref: '#/components/schemas/CompanyData'\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n ListCompanies200Response:\n type: object\n properties:\n success:\n type: boolean\n data:\n type: array\n items:\n $ref: '#/components/schemas/CompanyData'\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n RepresentationActionResponse:\n type: object\n properties:\n success:\n type: boolean\n data:\n type: object\n properties:\n status:\n type: string\n message:\n type: string\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n RepresentationDownloadResponse:\n type: object\n properties:\n success:\n type: boolean\n data:\n type: object\n properties:\n download_url:\n type: string\n format: uri\n expires_in_seconds:\n type: integer\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n RepresentationStatusResponse:\n type: object\n properties:\n success:\n type: boolean\n data:\n type: object\n properties:\n status:\n type: string\n enum:\n - NOT_STARTED\n - PDF_GENERATED\n - SUBMITTED\n - ACTIVE\n - ERROR\n - CANCELLED\n message:\n type: string\n updated_at:\n type: string\n format: date-time\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n CreateCompanyApiKeyRequest:\n type: object\n required:\n - name\n properties:\n name:\n type: string\n description: Descriptive name for the API key\n permissions:\n type: array\n items:\n type: string\n description: Scopes for the key (cannot exceed parent key scopes)\n expires_in_days:\n type: integer\n description: Expiration in days (optional)\n CompanyApiKeyData:\n type: object\n properties:\n id:\n type: string\n format: uuid\n name:\n type: string\n prefix:\n type: string\n environment:\n type: string\n enum:\n - PRODUCTION\n - SANDBOX\n permissions:\n type: array\n items:\n type: string\n created_at:\n type: string\n format: date-time\n expires_at:\n type: string\n format: date-time\n CreateCompanyApiKey201Response:\n type: object\n properties:\n success:\n type: boolean\n data:\n type: object\n properties:\n id:\n type: string\n format: uuid\n name:\n type: string\n key:\n type: string\n description: Plain text key (only shown once)\n prefix:\n type: string\n environment:\n type: string\n permissions:\n type: array\n items:\n type: string\n expires_at:\n type: string\n format: date-time\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n ListCompanyApiKeys200Response:\n type: object\n properties:\n success:\n type: boolean\n data:\n type: array\n items:\n $ref: '#/components/schemas/CompanyApiKeyData'\n meta:\n $ref: '#/components/schemas/ResponseMeta'\n ExemptionReason:\n type: string\n enum:\n - EXENTA_ART_20\n - EXENTA_ART_21_24\n - EXENTA_ART_25\n - EXENTA_ART_26\n - EXENTA_ART_140\n - NO_SUJETA_ART_7_9\n - ISP_ART_84_2_A\n - ISP_ART_84_2_E\n - ISP_ART_84_2_F\n - REGIMEN_ART_129\n - REGIMEN_ART_135\n - REGIMEN_ART_141\n - REGIMEN_ART_154\n - REGIMEN_ART_163_DECIES\n - OTRO\n description: |\n Tax exemption reason code per Spanish VAT Law (Ley 37/1992 LIVA).\n When OTRO, a custom text must be provided in exemption_reason_text.\n example: EXENTA_ART_20\n FieldDeserializationError:\n type: object\n required:\n - field\n - invalid_value\n properties:\n field:\n type: string\n description: JSON field name that caused the error (matches the API contract name)\n example: due_date\n invalid_value:\n type: string\n description: The value that was rejected\n example: 2026-03-04fds\n expected_format:\n type: string\n nullable: true\n description: Expected format hint (e.g. YYYY-MM-DD for dates)\n example: YYYY-MM-DD\n allowed_values:\n type: string\n nullable: true\n description: Comma-separated list of allowed values (for enum fields)\n example: STANDARD, SIMPLIFIED, CORRECTIVE\n WebhookSubscription:\n type: object\n properties:\n id:\n type: string\n format: uuid\n url:\n type: string\n format: uri\n events:\n type: array\n items:\n type: string\n active:\n type: boolean\n last_used_at:\n type: string\n format: date-time\n nullable: true\n created_at:\n type: string\n format: date-time\n WebhookEventTypeEnum:\n type: string\n description: |\n Available webhook event types:\n - `invoice.emitted` \u2014 Invoice emitted and finalized\n - `invoice.email.sent` \u2014 Invoice sent by email\n - `invoice.cancelled` \u2014 Invoice cancelled\n - `verifactu.status.updated` \u2014 VeriFactu status changed (PENDING, ACCEPTED, REJECTED)\n enum:\n - invoice.emitted\n - invoice.email.sent\n - invoice.cancelled\n - verifactu.status.updated\n CreateWebhookSubscriptionRequest:\n type: object\n required:\n - url\n - events\n properties:\n url:\n type: string\n format: uri\n description: HTTPS endpoint URL that will receive webhook POST requests.\n example: https://yourapp.com/webhooks/beel\n events:\n type: array\n minItems: 1\n items:\n $ref: '#/components/schemas/WebhookEventTypeEnum'\n description: List of event types to subscribe to.\n example:\n - invoice.emitted\n - verifactu.status.updated\n WebhookSubscriptionWithSecret:\n allOf:\n - $ref: '#/components/schemas/WebhookSubscription'\n - type: object\n properties:\n secret:\n type: string\n description: |\n HMAC-SHA256 signing secret. **Only returned on creation and secret rotation.**\n Store it securely \u2014 it cannot be retrieved again.\n example: whsec_a3f5b2e1c9d8f7e6b5a4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f2\n WebhookEventDataInvoiceEmitted:\n type: object\n description: Data payload for `invoice.emitted` events.\n additionalProperties: false\n required:\n - invoice_id\n - invoice_number\n properties:\n invoice_id:\n type: string\n format: uuid\n invoice_number:\n type: string\n example: 2025/0001\n customer_email:\n type: string\n nullable: true\n customer_name:\n type: string\n nullable: true\n WebhookEventDataInvoiceEmailSent:\n type: object\n description: Data payload for `invoice.email.sent` events.\n additionalProperties: false\n required:\n - invoice_id\n - all_recipients\n - sent_at\n properties:\n invoice_id:\n type: string\n format: uuid\n invoice_number:\n type: string\n nullable: true\n example: 2025/0001\n all_recipients:\n type: array\n items:\n type: string\n description: All email recipients (TO + CC).\n sent_at:\n type: string\n format: date-time\n WebhookEventDataInvoiceCancelled:\n type: object\n description: Data payload for `invoice.cancelled` events.\n additionalProperties: false\n required:\n - invoice_id\n - invoice_number\n properties:\n invoice_id:\n type: string\n format: uuid\n invoice_number:\n type: string\n example: 2025/0001\n cancellation_reason:\n type: string\n nullable: true\n WebhookEventDataVeriFactuStatusUpdated:\n type: object\n description: Data payload for `verifactu.status.updated` events.\n additionalProperties: false\n required:\n - invoice_id\n - verifactu_registration_id\n - previous_status\n - new_status\n properties:\n invoice_id:\n type: string\n format: uuid\n invoice_number:\n type: string\n nullable: true\n example: 2025-001\n verifactu_registration_id:\n type: string\n format: uuid\n previous_status:\n type: string\n enum:\n - PENDING\n - ACCEPTED\n - REJECTED\n new_status:\n type: string\n enum:\n - PENDING\n - ACCEPTED\n - REJECTED\n qr_url:\n type: string\n nullable: true\n example: https://sede.agenciatributaria.gob.es/Sede/verifactu?id=ABC123\n qr_base64:\n type: string\n nullable: true\n description: QR code as base64-encoded PNG for embedding in PDFs.\n invoice_hash:\n type: string\n nullable: true\n example: B11F3A015173AD99075E2720F61E2DE1FF08CBFEDD85C6F73C77AD835301B2A3\n error_code:\n type: string\n nullable: true\n error_message:\n type: string\n nullable: true\n WebhookEvent:\n type: object\n description: Payload delivered to your endpoint on every webhook event.\n properties:\n id:\n type: string\n description: Unique webhook event ID (UUID).\n example: 3f7a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c\n type:\n $ref: '#/components/schemas/WebhookEventTypeEnum'\n created_at:\n type: string\n format: date-time\n api_version:\n type: string\n example: 2025-01\n livemode:\n type: boolean\n description: '`false` for SANDBOX API keys.'\n test:\n type: boolean\n nullable: true\n description: '`true` only for test deliveries triggered from the dashboard.'\n data:\n oneOf:\n - $ref: '#/components/schemas/WebhookEventDataInvoiceEmitted'\n - $ref: '#/components/schemas/WebhookEventDataInvoiceEmailSent'\n - $ref: '#/components/schemas/WebhookEventDataInvoiceCancelled'\n - $ref: '#/components/schemas/WebhookEventDataVeriFactuStatusUpdated'\n UpdateWebhookSubscriptionRequest:\n type: object\n properties:\n url:\n type: string\n format: uri\n nullable: true\n description: New HTTPS endpoint URL.\n example: https://yourapp.com/webhooks/beel\n events:\n type: array\n nullable: true\n minItems: 1\n items:\n $ref: '#/components/schemas/WebhookEventTypeEnum'\n description: New list of event types to subscribe to.\n active:\n type: boolean\n nullable: true\n description: Enable or disable the webhook subscription.\n WebhookDeliveryLog:\n type: object\n properties:\n id:\n type: string\n format: uuid\n subscription_id:\n type: string\n format: uuid\n webhook_event_id:\n type: string\n format: uuid\n description: Groups all delivery attempts for the same logical event.\n event_type:\n type: string\n example: verifactu.status.updated\n attempt_number:\n type: integer\n description: Delivery attempt number (1 = first, 2 = first retry, etc.)\n http_status:\n type: integer\n nullable: true\n description: HTTP response status code. Null if connection failed.\n example: 200\n response_body:\n type: string\n nullable: true\n duration_ms:\n type: integer\n nullable: true\n description: Delivery duration in milliseconds.\n success:\n type: boolean\n error_message:\n type: string\n nullable: true\n description: Error description for failed deliveries (timeout, connection error, etc.)\n payload:\n type: string\n nullable: true\n description: The JSON payload that was sent (or attempted) to your endpoint.\n request_headers:\n type: object\n nullable: true\n description: |\n HTTP headers sent to your endpoint with this delivery attempt.\n Includes `BeeL-Signature`, `BeeL-Event`, `BeeL-Event-Id`, `BeeL-Delivery-Id`, and `Idempotency-Key`.\n additionalProperties:\n type: string\n example:\n BeeL-Signature: t=1741362026,v1=3c4f7a2e1b9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d0e9f8a7b6c5d4e3f\n BeeL-Event: verifactu.status.updated\n BeeL-Event-Id: 8ee6b023-c4e5-482e-93ca-dc66da2f9cb5\n BeeL-Delivery-Id: b3873959-67d8-4a11-87ba-b39cc2d395fe\n Idempotency-Key: 8ee6b023-c4e5-482e-93ca-dc66da2f9cb5\n delivered_at:\n type: string\n format: date-time\n schemas-TaxRegime:\n type: string\n enum:\n - IVA\n - IGIC\n - IPSI\n - OTHER\n description: |\n Applicable tax regime:\n - IVA: Value Added Tax (Iberian Peninsula and Balearic Islands)\n - IGIC: Canary Islands General Indirect Tax (Canary Islands)\n - IPSI: Production, Services and Import Tax (Ceuta and Melilla)\n - OTHER: Special regimes (exemptions, etc.)\n example: IVA\n VatPercentage:\n type: integer\n enum:\n - 0\n - 4\n - 10\n - 21\n description: |\n VAT percentage in integer format.\n Allowed values: 0 (exempt), 4 (super-reduced), 10 (reduced), 21 (standard).\n Valid values are validated according to the configured tax regime.\n example: 21\n securitySchemes:\n ApiKeyAuth:\n type: http\n scheme: bearer\n bearerFormat: beel_sk_*\n description: |\n API Key authentication.\n\n **Format:** `Authorization: Bearer beel_sk_<key>`\n\n **Obtaining Keys:** API Keys are managed from the BeeL dashboard\n\n **Security:** API Keys are secret credentials. Do not share them or store them in source code\n SessionCookie:\n type: apiKey\n in: cookie\n name: BEEL_SESSION\n description: |\n Session Cookie authentication (for browser-based access).\n\n **Note:** This method is primarily used by the web application.\n For programmatic access, use API Keys instead.\n examples:\n list-invoices-success:\n summary: List invoices (200)\n description: Invoice list with pagination metadata\n value:\n success: true\n data:\n invoices:\n - id: f47ac10b-58cc-4372-a567-0e02b2c3d479\n invoice_number: A/2025/0042\n series:\n id: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n code: A\n number: 42\n type: STANDARD\n status: ISSUED\n issue_date: '2025-01-20'\n due_date: '2025-02-20'\n issuer:\n legal_name: Mi Empresa SL\n nif: B12345678\n address:\n street: Calle Principal\n number: '10'\n postal_code: '28001'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n recipient:\n customer_id: 123e4567-e89b-12d3-a456-426614174000\n legal_name: Cliente Ejemplo SL\n nif: B87654321\n address:\n street: Calle Secundaria\n number: '20'\n postal_code: '08001'\n city: Barcelona\n province: Barcelona\n country: Espa\xF1a\n lines:\n - description: Consulting services\n quantity: 10\n unit: hours\n unit_price: 150\n taxable_base: 1500\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n line_total: 1815\n totals:\n taxable_base: 1500\n total_vat: 315\n total_irpf: 0\n total_equivalence_surcharge: 0\n vat_breakdown:\n - type: 21\n base: 1500\n amount: 315\n invoice_total: 1815\n pdf_download_url: /v1/invoices/f47ac10b-58cc-4372-a567-0e02b2c3d479/pdf\n created_at: '2025-01-20T09:00:00Z'\n updated_at: '2025-01-20T09:30:00Z'\n - id: 550e8400-e29b-41d4-a716-446655440002\n invoice_number: A/2025/0041\n series:\n id: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n code: A\n number: 41\n type: STANDARD\n status: PAID\n issue_date: '2025-01-15'\n due_date: '2025-02-15'\n payment_date: '2025-01-18'\n sent_at: '2025-01-16T10:00:00Z'\n paid_at: '2025-01-18T14:00:00Z'\n issuer:\n legal_name: Mi Empresa SL\n nif: B12345678\n address:\n street: Calle Principal\n number: '10'\n postal_code: '28001'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n recipient:\n customer_id: 456e7890-e12b-34d5-a678-901234567890\n legal_name: Otro Cliente SA\n nif: A12345678\n address:\n street: Avenida Central\n number: '100'\n postal_code: '46001'\n city: Valencia\n province: Valencia\n country: Espa\xF1a\n lines:\n - description: Software development\n quantity: 50\n unit: hours\n unit_price: 50\n taxable_base: 2500\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n line_total: 3025\n totals:\n taxable_base: 2500\n total_vat: 525\n total_irpf: 0\n total_equivalence_surcharge: 0\n vat_breakdown:\n - type: 21\n base: 2500\n amount: 525\n invoice_total: 3025\n pdf_download_url: /v1/invoices/550e8400-e29b-41d4-a716-446655440002/pdf\n created_at: '2025-01-15T10:00:00Z'\n updated_at: '2025-01-18T14:00:00Z'\n - id: 550e8400-e29b-41d4-a716-446655440003\n invoice_number: A/2025/0040\n series:\n id: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n code: A\n number: 40\n type: STANDARD\n status: OVERDUE\n issue_date: '2024-12-10'\n due_date: '2025-01-10'\n issuer:\n legal_name: Mi Empresa SL\n nif: B12345678\n address:\n street: Calle Principal\n number: '10'\n postal_code: '28001'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n recipient:\n customer_id: 789e0123-e45b-67d8-a901-234567890123\n legal_name: Cliente Moroso SL\n nif: B98765432\n address:\n street: Plaza Mayor\n number: '5'\n postal_code: '41001'\n city: Sevilla\n province: Sevilla\n country: Espa\xF1a\n lines:\n - description: Web maintenance\n quantity: 1\n unit: unit\n unit_price: 800\n taxable_base: 800\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n line_total: 968\n totals:\n taxable_base: 800\n total_vat: 168\n total_irpf: 0\n total_equivalence_surcharge: 0\n vat_breakdown:\n - type: 21\n base: 800\n amount: 168\n invoice_total: 968\n pdf_download_url: /v1/invoices/550e8400-e29b-41d4-a716-446655440003/pdf\n created_at: '2024-12-10T11:00:00Z'\n updated_at: '2024-12-10T11:00:00Z'\n pagination:\n current_page: 1\n items_per_page: 20\n total_items: 3\n total_pages: 1\n has_next: false\n has_previous: false\n meta:\n timestamp: '2025-01-20T12:00:00Z'\n request_id: 550e8400-e29b-41d4-a716-446655440000\n create-basic:\n summary: Basic ordinary invoice\n description: Registered customer, 21% VAT, single service line\n value:\n type: STANDARD\n issue_date: '2025-01-20'\n recipient:\n customer_id: 123e4567-e89b-12d3-a456-426614174000\n lines:\n - description: Corporate website development\n quantity: 40\n unit: hours\n unit_price: 37.5\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n notes: Payment via bank transfer\n create-with-adhoc-customer:\n summary: Invoice with ad-hoc customer\n description: Unregistered customer, inline data, multiple line items\n value:\n type: STANDARD\n issue_date: '2025-01-21'\n recipient:\n legal_name: Comercial Mart\xEDnez SL\n nif: B87654321\n email: contacto@comercialmartinez.es\n phone: '+34912345678'\n address:\n street: Avenida de la Constituci\xF3n\n number: '45'\n floor: 3\xBA B\n postal_code: '41001'\n city: Sevilla\n province: Sevilla\n country: Espa\xF1a\n lines:\n - description: Business strategic consulting\n quantity: 8\n unit: hours\n unit_price: 125\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n - description: Executive report preparation\n quantity: 1\n unit: document\n unit_price: 500\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n notes: |\n Invoice for consulting services for the month of January.\n Includes 8 hours of consulting and report preparation.\n create-with-irpf:\n summary: Invoice with 15% IRPF withholding\n description: Professional services with tax retention. Total = Base + VAT - IRPF\n value:\n type: STANDARD\n issue_date: '2025-01-22'\n recipient:\n customer_id: 789abc12-34de-56f7-8901-234567890abc\n lines:\n - description: Tax advisory services - January 2025\n quantity: 1\n unit: month\n unit_price: 2000\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n irpf_rate: 15\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n notes: |\n Invoice total: 2,420.00 \u20AC\n IRPF withholding (15%): -300.00 \u20AC\n Net amount payable: 2,120.00 \u20AC\n create-with-equivalence-surcharge:\n summary: Invoice with equivalence surcharge\n description: Retail customer. 5.2% surcharge on top of 21% VAT\n value:\n type: STANDARD\n issue_date: '2025-01-23'\n recipient:\n legal_name: Tienda de Electr\xF3nica L\xF3pez\n nif: 12345678Z\n address:\n street: Calle Comercio\n number: '56'\n postal_code: '08001'\n city: Barcelona\n province: Barcelona\n country: Espa\xF1a\n lines:\n - description: HP Laptops - 10 units\n quantity: 10\n unit: unit\n unit_price: 450\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '18'\n equivalence_surcharge_rate: 5.2\n - description: Wireless mice - 20 units\n quantity: 20\n unit: unit\n unit_price: 15\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '18'\n equivalence_surcharge_rate: 5.2\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n create-with-igic:\n summary: Invoice with IGIC (Canary Islands)\n description: Canary Islands customer. 7% IGIC instead of VAT\n value:\n type: STANDARD\n issue_date: '2025-01-24'\n recipient:\n legal_name: Importaciones Atl\xE1ntico SL\n nif: B35987654\n address:\n street: Avenida Mar\xEDtima\n number: '200'\n postal_code: '35001'\n city: Las Palmas de Gran Canaria\n province: Las Palmas\n country: Espa\xF1a\n lines:\n - description: Industrial air conditioning systems\n quantity: 3\n unit: unit\n unit_price: 3500\n discount_percentage: 0\n main_tax:\n type: IGIC\n percentage: 7\n regime_key: '01'\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n create-foreign-customer:\n summary: Invoice for foreign customer (EU)\n description: EU customer with reverse charge. 0% VAT, use id_otro for VAT number\n value:\n type: STANDARD\n issue_date: '2025-01-25'\n recipient:\n legal_name: Tech Europe GmbH\n alternative_id:\n type: '02'\n number: DE123456789\n country_code: DE\n email: invoices@techeurope.de\n address:\n street: Hauptstra\xDFe\n number: '456'\n postal_code: '10115'\n city: Berlin\n province: Berlin\n country: Alemania\n lines:\n - description: Software development services - Q1 2025\n quantity: 1\n unit: quarter\n unit_price: 15000\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 0\n regime_key: '02'\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n notes: |\n ** INTRA-COMMUNITY INVOICE **\n Reverse charge (Art. 84 LIVA)\n The recipient must apply IVA according to their country's regulations\n create-issue-directly:\n summary: Create, issue directly, and send by email\n description: |\n Skip draft state. Creates invoice directly as ISSUED with:\n - Definitive invoice number assigned\n - PDF generated (sync or async based on options.wait_for_pdf)\n - Email sent automatically if options.send_automatically=true\n value:\n type: STANDARD\n issue_date: '2025-01-27'\n recipient:\n customer_id: 123e4567-e89b-12d3-a456-426614174000\n lines:\n - description: Monthly web application maintenance\n quantity: 1\n unit: month\n unit_price: 850\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n irpf_rate: 15\n payment_info:\n method: DIRECT_DEBIT\n iban: ES9121000418450200051332\n options:\n issue_directly: true\n wait_for_pdf: true\n send_automatically: true\n create-with-custom-email:\n summary: Create invoice with custom email recipients\n description: |\n Issues invoice and sends to multiple recipients with:\n - Custom recipient list (overrides recipient.email)\n - CC to accounting department\n - Personalized subject and message\n value:\n type: STANDARD\n issue_date: '2025-01-27'\n due_date: '2025-02-26'\n recipient:\n legal_name: Acme Corporation SL\n nif: B12345678\n email: info@acme.es\n address:\n street: Calle Gran V\xEDa\n number: '42'\n floor: 5\xBA\n postal_code: '28013'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n lines:\n - description: Mobile application development - Phase 1\n quantity: 80\n unit: hours\n unit_price: 65\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n irpf_rate: 15\n - description: Application UX/UI design\n quantity: 20\n unit: hours\n unit_price: 55\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n irpf_rate: 15\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n notes: Project APP-2025. Includes source code and technical documentation.\n metadata:\n project_code: APP-2025-001\n purchase_order: PO-ACME-2025-042\n options:\n issue_directly: true\n wait_for_pdf: true\n send_automatically: true\n email_config:\n recipients:\n - facturas@acme.es\n - pedro.garcia@acme.es\n - maria.lopez@acme.es\n cc:\n - contabilidad@miempresa.com\n - gestor@asesoria.es\n subject: Invoice project APP-2025 - Mobile application development\n message: |\n Dear Acme Corporation team,\n\n Please find attached the invoice corresponding to the first phase\n of the mobile application development as agreed.\n\n Payment is due within 30 days. For any questions,\n please do not hesitate to contact us.\n\n Kind regards.\n simplified-without-nif:\n summary: Simplified without NIF \u2264400\u20AC\n description: Simplified invoice without NIF, max 400\u20AC total including VAT\n value:\n type: SIMPLIFIED\n issue_date: '2025-02-01'\n recipient: {}\n lines:\n - description: Computer repair service\n quantity: 1\n unit: service\n unit_price: 300\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '20'\n payment_info:\n method: CASH\n simplified-with-nif:\n summary: Simplified with NIF \u22643000\u20AC\n description: Simplified invoice with NIF, max 3000\u20AC total. No full address required\n value:\n type: SIMPLIFIED\n issue_date: '2025-02-02'\n recipient:\n legal_name: Mar\xEDa Garc\xEDa L\xF3pez\n nif: 12345678A\n lines:\n - description: Security system installation\n quantity: 1\n unit: installation\n unit_price: 1800\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '20'\n - description: Surveillance cameras - 4 units\n quantity: 4\n unit: unit\n unit_price: 150\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '20'\n payment_info:\n method: CARD\n created-success:\n summary: Invoice created (201)\n description: Invoice created successfully in DRAFT state with draft number and totals\n value:\n success: true\n data:\n id: f47ac10b-58cc-4372-a567-0e02b2c3d479\n invoice_number: null\n number: null\n type: STANDARD\n status: DRAFT\n issue_date: '2025-01-20'\n issuer:\n legal_name: Tu Empresa SL\n nif: B12345678\n address:\n street: Calle Ejemplo\n number: '123'\n postal_code: '28001'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n series:\n id: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n code: A\n recipient:\n customer_id: 123e4567-e89b-12d3-a456-426614174000\n legal_name: Cliente Ejemplo SL\n nif: B87654321\n email: cliente@ejemplo.com\n address:\n street: Avenida Cliente\n number: '456'\n postal_code: '28013'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n lines:\n - description: Corporate website development\n quantity: 40\n unit: hours\n unit_price: 37.5\n discount_percentage: 0\n taxable_base: 1500\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n line_total: 1815\n totals:\n taxable_base: 1500\n total_vat: 315\n total_irpf: 0\n total_equivalence_surcharge: 0\n vat_breakdown:\n - type: 21\n base: 1500\n amount: 315\n invoice_total: 1815\n payment_info:\n method: BANK_TRANSFER\n payment_term_days: 30\n notes: Payment via bank transfer\n verifactu:\n enabled: false\n created_at: '2025-01-20T10:30:00Z'\n updated_at: '2025-01-20T10:30:00Z'\n meta:\n timestamp: '2025-01-20T10:30:00Z'\n request_id: c1d2e3f4-a5b6-4c7d-8e9f-0a1b2c3d4e5f\n conflict-409:\n summary: Conflict (409)\n description: Duplicate resource exists (NIF, email, invoice number, series code)\n value:\n success: false\n error:\n code: CONFLICT\n message: Resource already exists\n details:\n conflict_type: DUPLICATE_NIF\n field: nif\n value: B12345678\n existing_resource_id: 123e4567-e89b-12d3-a456-426614174000\n message: A customer with NIF B12345678 already exists\n meta:\n timestamp: '2025-01-20T10:00:00Z'\n request_id: d4d4d4d4-0004-4000-a000-000000000004\n validation-422:\n summary: Validation error (422)\n description: Request data fails validation (missing fields, invalid format, business rules)\n value:\n success: false\n error:\n code: VALIDATION_ERROR\n message: Request validation failed\n details:\n errors:\n - field: nif\n message: Invalid NIF format. Expected 9 characters (8 digits + letter or letter + 8 digits)\n value: B123INVALID\n - field: lineas\n message: At least one line item is required\n value: []\n - field: receptor.direccion.codigo_postal\n message: Invalid postal code format. Expected 5 digits\n value: '280'\n - field: lineas[0].precio_unitario\n message: Unit price must be greater than or equal to 0\n value: -10.5\n meta:\n timestamp: '2025-01-20T10:00:00Z'\n request_id: c3c3c3c3-0003-4000-a000-000000000003\n get-invoice-success:\n summary: Get invoice (200)\n description: Single invoice retrieved with full details including VeriFactu signature and PDF URL\n value:\n success: true\n data:\n id: f47ac10b-58cc-4372-a567-0e02b2c3d479\n invoice_number: A/2025/0042\n series:\n id: a1b2c3d4-e5f6-7890-abcd-ef1234567890\n code: A\n number: 42\n type: STANDARD\n status: ISSUED\n issue_date: '2025-01-20'\n due_date: '2025-02-20'\n issuer:\n legal_name: Tu Empresa SL\n nif: B12345678\n address:\n street: Calle Ejemplo\n number: '123'\n postal_code: '28001'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n email: info@tuempresa.es\n phone: '+34912345678'\n recipient:\n customer_id: 123e4567-e89b-12d3-a456-426614174000\n legal_name: Cliente Ejemplo SL\n nif: B87654321\n email: cliente@ejemplo.com\n address:\n street: Avenida Cliente\n number: '456'\n postal_code: '28013'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n lines:\n - description: Corporate website development\n quantity: 40\n unit: hours\n unit_price: 37.5\n discount_percentage: 0\n taxable_base: 1500\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n irpf_rate: 15\n line_total: 1590\n totals:\n taxable_base: 1500\n total_vat: 315\n total_irpf: 225\n total_equivalence_surcharge: 0\n vat_breakdown:\n - type: 21\n base: 1500\n amount: 315\n irpf_breakdown:\n - type: 15\n base: 1500\n amount: 225\n invoice_total: 1590\n payment_info:\n method: BANK_TRANSFER\n iban: ES9121000418450200051332\n payment_term_days: 30\n notes: Payment via bank transfer\n verifactu:\n enabled: true\n invoice_hash: a7f3c9e2b1d4f8a6c3e9b2d5f1a8c4e7b9d2f5a1c8e4b7d3f9a2c6e1b5d8f4a7\n chaining_hash: c9e2b1d4f8a6c3e9b2d5f1a8c4e7b9d2f5a1c8e4b7d3f9a2c6e1b5d8f4a7b3c1\n qr_url: https://verifactu.agenciatributaria.gob.es/v?id=a7f3c9e2b1d4f8a6\n registration_date: '2025-01-20T10:35:00Z'\n submission_status: ACCEPTED\n pdf_download_url: /v1/invoices/f47ac10b-58cc-4372-a567-0e02b2c3d479/pdf\n created_at: '2025-01-20T10:30:00Z'\n updated_at: '2025-01-20T10:35:00Z'\n meta:\n timestamp: '2025-01-20T11:00:00Z'\n request_id: f4a5b6c7-d8e9-4f0a-1b2c-3d4e5f6a7b8c\n update-invoice:\n summary: Update draft invoice\n description: Update invoice in DRAFT state. Modify dates, lines, recipient, payment method\n value:\n issue_date: '2025-01-25'\n lines:\n - description: Corporate website development - Updated\n quantity: 45\n unit: hours\n unit_price: 40\n discount_percentage: 5\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n - description: Web maintenance - 3 months\n quantity: 3\n unit: month\n unit_price: 150\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n payment_info:\n method: DIRECT_DEBIT\n iban: ES9121000418450200051332\n notes: Invoice updated with new maintenance line. Payment via direct debit.\n corrective-total-r1:\n summary: Total corrective R1 - Legal error\n description: R1 code for legal errors. Fully cancels original invoice (status VOIDED)\n value:\n rectification_type: TOTAL\n rectification_code: R1\n reason: Cancellation of invoice issued due to a legally founded error under Art. 80 Uno LIVA. The transaction was not completed due to project cancellation before commencement.\n notes: Original invoice F/2025/0042 fully cancelled. Customer notified.\n corrective-total-r4:\n summary: Total corrective R4 - Other reasons\n description: R4 code (most common). General cancellations, customer requests, etc\n value:\n rectification_type: TOTAL\n rectification_code: R4\n reason: Invoice cancellation at customer's request. The service was cancelled before delivery and full cancellation of the invoice has been agreed.\n notes: Invoice cancelled at customer's request. No outstanding amount pending collection.\n corrective-partial-r2:\n summary: Partial corrective R2 - Bankruptcy\n description: R2 code for bankruptcy proceedings. Partial correction with amount adjustment\n value:\n rectification_type: PARTIAL\n rectification_code: R2\n reason: Partial correction of invoice due to customer bankruptcy proceedings under Art. 80 Tres LIVA. Amount adjusted to the agreement reached with the insolvency administrator.\n lines:\n - description: Adjustment for bankruptcy proceedings - 40% amount reduction\n quantity: -16\n unit: hours\n unit_price: 37.5\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n notes: Correction approved by insolvency administrator. Case 123/2024 Commercial Court No. 2 Madrid.\n corrective-partial-r3:\n summary: Partial corrective R3 - Bad debts\n description: R3 code for bad debts. VAT recovery on unpaid invoices after 6 months\n value:\n rectification_type: PARTIAL\n rectification_code: R3\n reason: Partial correction for bad debt under Art. 80 Cuatro LIVA. More than 6 months have elapsed since the invoice due date and collection efforts via notarial demands have been documented without result.\n lines:\n - description: Bad debt adjustment - Full cancellation of outstanding amount\n quantity: -40\n unit: hours\n unit_price: 37.5\n discount_percentage: 0\n main_tax:\n type: IVA\n percentage: 21\n regime_key: '01'\n notes: Collection efforts documented via notarial demands (Protocol 456/2024). Original invoice due on 15/03/2024.\n list-customers-success:\n summary: List customers (200)\n description: Customer list with pagination\n value:\n success: true\n data:\n customers:\n - id: 123e4567-e89b-12d3-a456-426614174000\n legal_name: Tech Solutions SL\n nif: B12345678\n email: admin@techsolutions.com\n phone: '+34912345678'\n address:\n street: Calle Mayor\n number: '15'\n postal_code: '28013'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n active: true\n created_at: '2024-06-15T10:30:00Z'\n updated_at: '2025-01-10T14:00:00Z'\n - id: 789abc12-34de-56f7-8901-234567890abc\n legal_name: Mar\xEDa Garc\xEDa L\xF3pez\n nif: 12345678Z\n email: maria.garcia@email.com\n phone: '+34654321987'\n address:\n street: Avenida de la Constituci\xF3n\n number: '25'\n floor: 3\xBA\n door: B\n postal_code: '41001'\n city: Sevilla\n province: Sevilla\n country: Espa\xF1a\n active: true\n created_at: '2024-08-20T09:00:00Z'\n updated_at: '2024-12-01T11:30:00Z'\n - id: 456def78-90ab-12cd-3456-78ef01234567\n legal_name: Distribuidora Levante SA\n nif: A22222222\n email: contabilidad@distribuidoralevante.es\n phone: '+34963456789'\n address:\n street: Pol\xEDgono Industrial Norte\n number: '7'\n postal_code: '46007'\n city: Valencia\n province: Valencia\n country: Espa\xF1a\n active: false\n created_at: '2024-03-10T16:45:00Z'\n updated_at: '2024-11-20T08:00:00Z'\n pagination:\n current_page: 1\n items_per_page: 20\n total_items: 3\n total_pages: 1\n has_next: false\n has_previous: false\n meta:\n timestamp: '2025-01-20T12:00:00Z'\n request_id: 550e8400-e29b-41d4-a716-446655440000\n company-customer:\n summary: Spanish company (SL/SA)\n description: 'Spanish company with CIF format: Letter + 8 digits (e.g., B12345678)'\n value:\n legal_name: Tech Solutions SL\n nif: B12345678\n email: admin@techsolutions.com\n phone: '+34912345678'\n address:\n street: Calle Mayor\n number: '123'\n floor: 2\xBA B\n postal_code: '28013'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n notes: Cliente prioritario - Pago puntual\n freelancer-customer:\n summary: Self-employed individual\n description: 'Self-employed with personal NIF. Format: 8 digits + letter (e.g., 12345678Z)'\n value:\n legal_name: Mar\xEDa Garc\xEDa L\xF3pez\n nif: 12345678Z\n email: maria.garcia@email.com\n phone: '+34654321987'\n address:\n street: Avenida de la Constituci\xF3n\n number: '45'\n floor: 3\xBA A\n postal_code: '41001'\n city: Sevilla\n province: Sevilla\n country: Espa\xF1a\n notes: Dise\xF1adora gr\xE1fica freelance\n eu_customer:\n summary: EU Customer with VAT\n value:\n legal_name: Tech Europe GmbH\n alternative_id:\n type: '02'\n number: DE123456789\n country_code: DE\n email: invoices@techeurope.de\n phone: '+49301234567'\n address:\n street: Hauptstra\xDFe\n number: '456'\n postal_code: '10115'\n city: Berlin\n province: Berlin\n country: Alemania\n notes: Cliente intracomunitario - Aplicar inversi\xF3n del sujeto pasivo\n us_customer:\n summary: US Customer with EIN\n value:\n legal_name: American Tech Corp\n alternative_id:\n type: '06'\n number: 12-3456789\n country_code: US\n email: billing@americantech.com\n phone: '+14155551234'\n address:\n street: Main Street\n number: '123'\n floor: Suite 500\n postal_code: '94102'\n city: San Francisco\n province: California\n country: Estados Unidos\n notes: Cliente extracomunitario - Factura de exportaci\xF3n\n created-success-2:\n summary: Customer created (201)\n description: Customer created successfully with generated ID and initial statistics\n value:\n success: true\n data:\n id: 123e4567-e89b-12d3-a456-426614174000\n legal_name: Tech Solutions SL\n nif: B12345678\n email: admin@techsolutions.com\n phone: '+34912345678'\n address:\n street: Calle Mayor\n number: '123'\n floor: 2\xBA B\n postal_code: '28013'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n notes: Cliente prioritario - Pago puntual\n active: true\n created_at: '2025-01-20T10:00:00Z'\n updated_at: '2025-01-20T10:00:00Z'\n meta:\n timestamp: '2025-01-20T10:00:00Z'\n request_id: c1c1c1c1-0001-4000-a000-000000000001\n get-customer-success:\n summary: Get customer (200)\n description: Single customer retrieved with full details\n value:\n success: true\n data:\n id: 123e4567-e89b-12d3-a456-426614174000\n legal_name: Tech Solutions SL\n nif: B12345678\n email: admin@techsolutions.com\n phone: '+34912345678'\n address:\n street: Calle Mayor\n number: '123'\n floor: 2\xBA B\n postal_code: '28013'\n city: Madrid\n province: Madrid\n country: Espa\xF1a\n notes: Cliente prioritario - Pago puntual\n active: true\n created_at: '2024-03-15T09:00:00Z'\n updated_at: '2025-01-20T10:00:00Z'\n meta:\n timestamp: '2025-01-20T11:00:00Z'\n request_id: b6c7d8e9-f0a1-4b2c-3d4e-5f6a7b8c9d0e\n responses:\n Unauthorized:\n description: Missing or invalid authentication\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: UNAUTHORIZED\n message: Authentication required\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n InternalServerError:\n description: Internal server error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: INTERNAL_ERROR\n message: Internal server error\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n InvalidJsonFormat:\n description: |\n Invalid JSON format \u2014 a field has a wrong type or format (e.g. invalid date, unknown enum value, malformed UUID).\n The `details` object contains `field`, `invalid_value`, and optionally `expected_format` or `allowed_values`.\n content:\n application/json:\n schema:\n allOf:\n - $ref: '#/components/schemas/ErrorResponse'\n - type: object\n properties:\n error:\n allOf:\n - $ref: '#/components/schemas/ErrorDetail'\n - type: object\n properties:\n details:\n $ref: '#/components/schemas/FieldDeserializationError'\n example:\n success: false\n error:\n code: INVALID_JSON_FORMAT\n message: 'The field ''due_date'' has an invalid date format: ''2026-03-04fds''. Expected format: YYYY-MM-DD.'\n details:\n field: due_date\n invalid_value: 2026-03-04fds\n expected_format: YYYY-MM-DD\n meta:\n timestamp: '2026-03-05T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n NotFound:\n description: Resource not found\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: NOT_FOUND\n message: Resource not found\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n ValidationError:\n description: Validation error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: VALIDATION_ERROR\n message: Validation error\n details:\n field_name: Field is required\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n Forbidden:\n description: Insufficient permissions\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: FORBIDDEN\n message: You do not have permission to access this resource\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n BadRequest:\n description: Bad request\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: BAD_REQUEST\n message: Invalid request\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n UnprocessableEntity:\n description: Unprocessable entity - validation or business rule error\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: UNPROCESSABLE_ENTITY\n message: Data cannot be processed\n details:\n field: Specific error description\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n Conflict:\n description: Resource conflict (e.g. duplicate)\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: CONFLICT\n message: Resource already exists\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\n PayloadTooLarge:\n description: Payload too large - file or request body exceeds the maximum allowed size\n content:\n application/json:\n schema:\n $ref: '#/components/schemas/ErrorResponse'\n example:\n success: false\n error:\n code: FILE_TOO_LARGE\n message: The file exceeds the maximum allowed size\n meta:\n timestamp: '2025-01-15T10:30:00Z'\n request_id: 4bf92f3577b34da6a3ce929d0e0e4736\nx-tagGroups:\n - name: Invoices\n x-icon: FileText\n tags:\n - Invoices\n - InvoiceLifecycle\n - InvoiceDelivery\n - name: Customers\n x-icon: Users\n tags:\n - Customers\n - Customer Import\n - Customer Templates\n - name: Products\n x-icon: Package\n tags:\n - Products\n - name: Invoice Series\n x-icon: List\n tags:\n - InvoiceSeries\n - name: Tax Configuration\n x-icon: Calculator\n tags:\n - ConfigurationTax\n - name: VeriFactu\n x-icon: ShieldCheck\n tags:\n - ConfigurationVeriFactu\n - name: Preferences\n x-icon: Settings\n tags:\n - ConfigurationPreferences\n - name: NIF Validation\n x-icon: Search\n tags:\n - NIF\n - name: Companies\n x-icon: Building2\n tags:\n - PublicCompanies\n - PublicCompanyRepresentations\n - PublicCompanyApiKeys\n - name: Recurring Invoices\n x-icon: Repeat\n tags:\n - RecurringInvoices\n - name: Webhooks\n x-icon: Webhook\n tags:\n - Webhooks\n";
|
|
@@ -11102,6 +11102,56 @@ function deriveBody(doc, operation) {
|
|
|
11102
11102
|
if ("multipart/form-data" in content) return { kind: "multipart", required };
|
|
11103
11103
|
return { kind: null, required: false };
|
|
11104
11104
|
}
|
|
11105
|
+
function flattenObjectSchema(doc, schema) {
|
|
11106
|
+
const properties = {};
|
|
11107
|
+
const required = /* @__PURE__ */ new Set();
|
|
11108
|
+
const collect = (node) => {
|
|
11109
|
+
const resolved = resolveRef(doc, node);
|
|
11110
|
+
if (!resolved) return;
|
|
11111
|
+
if (Array.isArray(resolved.allOf)) resolved.allOf.forEach(collect);
|
|
11112
|
+
if (resolved.properties && typeof resolved.properties === "object") {
|
|
11113
|
+
Object.assign(properties, resolved.properties);
|
|
11114
|
+
}
|
|
11115
|
+
if (Array.isArray(resolved.required)) resolved.required.forEach((r) => required.add(String(r)));
|
|
11116
|
+
};
|
|
11117
|
+
collect(schema);
|
|
11118
|
+
return { properties, required };
|
|
11119
|
+
}
|
|
11120
|
+
function fieldType(doc, schema) {
|
|
11121
|
+
let resolved = resolveRef(doc, schema) ?? {};
|
|
11122
|
+
if (Array.isArray(resolved.allOf)) {
|
|
11123
|
+
const merged = {};
|
|
11124
|
+
for (const sub of resolved.allOf) Object.assign(merged, resolveRef(doc, sub));
|
|
11125
|
+
resolved = { ...merged, ...resolved };
|
|
11126
|
+
}
|
|
11127
|
+
if (resolved.type === "array") {
|
|
11128
|
+
const item = fieldType(doc, resolved.items);
|
|
11129
|
+
return { type: `${item.type}[]`, ...item.enum ? { enum: item.enum } : {} };
|
|
11130
|
+
}
|
|
11131
|
+
const type = String(resolved.type ?? (resolved.properties ? "object" : "string"));
|
|
11132
|
+
return { type, ...Array.isArray(resolved.enum) ? { enum: resolved.enum.map(String) } : {} };
|
|
11133
|
+
}
|
|
11134
|
+
function deriveBodyFields(doc, operation) {
|
|
11135
|
+
const requestBody = resolveRef(doc, operation.requestBody);
|
|
11136
|
+
const json = requestBody?.content?.["application/json"];
|
|
11137
|
+
if (!json) return [];
|
|
11138
|
+
const { properties, required } = flattenObjectSchema(doc, json.schema);
|
|
11139
|
+
const fields = [];
|
|
11140
|
+
for (const [name, raw] of Object.entries(properties)) {
|
|
11141
|
+
const { type, enum: enumValues } = fieldType(doc, raw);
|
|
11142
|
+
const own = raw ?? {};
|
|
11143
|
+
const description = own.description ?? (resolveRef(doc, raw) ?? {}).description;
|
|
11144
|
+
fields.push({
|
|
11145
|
+
name,
|
|
11146
|
+
type,
|
|
11147
|
+
required: required.has(name),
|
|
11148
|
+
...enumValues ? { enum: enumValues } : {},
|
|
11149
|
+
...description ? { description: String(description).split("\n")[0]?.trim() } : {}
|
|
11150
|
+
});
|
|
11151
|
+
}
|
|
11152
|
+
fields.sort((a, b) => Number(b.required) - Number(a.required) || a.name.localeCompare(b.name));
|
|
11153
|
+
return fields;
|
|
11154
|
+
}
|
|
11105
11155
|
function deriveBinaryResponse(doc, operation) {
|
|
11106
11156
|
const responses = operation.responses ?? {};
|
|
11107
11157
|
for (const status of ["200", "201"]) {
|
|
@@ -11135,6 +11185,7 @@ function buildManifest(doc) {
|
|
|
11135
11185
|
queryParams: buildQueryParams(doc, [...sharedParams, ...operation.parameters ?? []]),
|
|
11136
11186
|
body: body.kind,
|
|
11137
11187
|
bodyRequired: body.required,
|
|
11188
|
+
bodyFields: body.kind === "json" ? deriveBodyFields(doc, operation) : [],
|
|
11138
11189
|
binaryResponse: deriveBinaryResponse(doc, operation)
|
|
11139
11190
|
});
|
|
11140
11191
|
}
|
|
@@ -11282,6 +11333,7 @@ function registerCommand(group, spec) {
|
|
|
11282
11333
|
const description = `${hint}: inline, @file.json, or - for stdin`;
|
|
11283
11334
|
if (spec.bodyRequired) sub.requiredOption("--data <json>", description);
|
|
11284
11335
|
else sub.option("--data <json>", description);
|
|
11336
|
+
sub.addHelpText("after", formatBodyHelp(spec));
|
|
11285
11337
|
}
|
|
11286
11338
|
if (spec.binaryResponse) {
|
|
11287
11339
|
sub.requiredOption("--output <path>", "Write the binary response to this file");
|
|
@@ -11318,6 +11370,27 @@ function registerCommand(group, spec) {
|
|
|
11318
11370
|
printJson(result.json);
|
|
11319
11371
|
});
|
|
11320
11372
|
}
|
|
11373
|
+
var MAX_DESC = 60;
|
|
11374
|
+
function formatBodyHelp(spec) {
|
|
11375
|
+
if (spec.body !== "json") return "";
|
|
11376
|
+
const lines = ["", "Body fields (--data JSON), * = required:"];
|
|
11377
|
+
if (spec.bodyFields.length === 0) {
|
|
11378
|
+
lines.push(" (free-form JSON object \u2014 see the full schema in the docs)");
|
|
11379
|
+
} else {
|
|
11380
|
+
const pad = Math.max(...spec.bodyFields.map((f) => f.name.length));
|
|
11381
|
+
for (const field of spec.bodyFields) {
|
|
11382
|
+
const marker = field.required ? "*" : " ";
|
|
11383
|
+
const type = field.enum ? `${field.type} (${field.enum.join("|")})` : field.type;
|
|
11384
|
+
const description = field.description ? ` ${truncate(field.description)}` : "";
|
|
11385
|
+
lines.push(` ${marker} ${field.name.padEnd(pad)} ${type}${description}`);
|
|
11386
|
+
}
|
|
11387
|
+
}
|
|
11388
|
+
lines.push(`Full schema (nested fields, formats): beel docs search ${spec.group}`);
|
|
11389
|
+
return lines.join("\n");
|
|
11390
|
+
}
|
|
11391
|
+
function truncate(text) {
|
|
11392
|
+
return text.length > MAX_DESC ? `${text.slice(0, MAX_DESC - 1)}\u2026` : text;
|
|
11393
|
+
}
|
|
11321
11394
|
function queryFlag(query) {
|
|
11322
11395
|
if (query.array) return `--${query.flag} <values...>`;
|
|
11323
11396
|
if (query.type === "boolean") return `--${query.flag}`;
|
|
@@ -11527,6 +11600,59 @@ ${chunk.content}
|
|
|
11527
11600
|
}
|
|
11528
11601
|
}
|
|
11529
11602
|
|
|
11603
|
+
// src/commands/commands.ts
|
|
11604
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
11605
|
+
|
|
11606
|
+
// src/generate/render-reference.ts
|
|
11607
|
+
function commandSignature(spec) {
|
|
11608
|
+
const parts = ["beel", spec.group, spec.action];
|
|
11609
|
+
for (const param of spec.pathParams) parts.push(`<${param}>`);
|
|
11610
|
+
if (spec.body === "json" && spec.bodyRequired) parts.push("--data <json>");
|
|
11611
|
+
if (spec.binaryResponse) parts.push("--output <path>");
|
|
11612
|
+
return parts.join(" ");
|
|
11613
|
+
}
|
|
11614
|
+
function toReference(commands) {
|
|
11615
|
+
return commands.map((spec) => ({
|
|
11616
|
+
command: `${spec.group} ${spec.action}`,
|
|
11617
|
+
signature: commandSignature(spec),
|
|
11618
|
+
description: spec.description,
|
|
11619
|
+
method: spec.method,
|
|
11620
|
+
path: spec.path
|
|
11621
|
+
}));
|
|
11622
|
+
}
|
|
11623
|
+
function cell(text) {
|
|
11624
|
+
return text.replace(/\|/g, "\\|").replace(/\n+/g, " ").trim();
|
|
11625
|
+
}
|
|
11626
|
+
function manifestToMarkdown(commands) {
|
|
11627
|
+
const groups = /* @__PURE__ */ new Map();
|
|
11628
|
+
for (const spec of commands) {
|
|
11629
|
+
groups.set(spec.group, [...groups.get(spec.group) ?? [], spec]);
|
|
11630
|
+
}
|
|
11631
|
+
const sections = [];
|
|
11632
|
+
for (const group of [...groups.keys()].sort()) {
|
|
11633
|
+
const rows = (groups.get(group) ?? []).map((spec) => `| \`${cell(commandSignature(spec))}\` | ${cell(spec.description)} |`).join("\n");
|
|
11634
|
+
sections.push(`### ${group}
|
|
11635
|
+
|
|
11636
|
+
| Command | Description |
|
|
11637
|
+
|---------|-------------|
|
|
11638
|
+
${rows}`);
|
|
11639
|
+
}
|
|
11640
|
+
return sections.join("\n\n");
|
|
11641
|
+
}
|
|
11642
|
+
|
|
11643
|
+
// src/commands/commands.ts
|
|
11644
|
+
function registerCommands(program3) {
|
|
11645
|
+
program3.command("commands").description("List every command this CLI exposes (derived from the API spec). No API key needed.").option("--markdown", "Output a markdown reference grouped by resource instead of JSON").action((opts) => {
|
|
11646
|
+
const manifest = buildManifest((0, import_yaml.parse)(public_api_default));
|
|
11647
|
+
if (opts.markdown) {
|
|
11648
|
+
process.stdout.write(`${manifestToMarkdown(manifest)}
|
|
11649
|
+
`);
|
|
11650
|
+
return;
|
|
11651
|
+
}
|
|
11652
|
+
printJson(toReference(manifest));
|
|
11653
|
+
});
|
|
11654
|
+
}
|
|
11655
|
+
|
|
11530
11656
|
// src/index.ts
|
|
11531
11657
|
var { version } = require_package();
|
|
11532
11658
|
var program2 = new Command("beel").description(
|
|
@@ -11539,8 +11665,9 @@ registerLogin(program2);
|
|
|
11539
11665
|
registerConfig(program2);
|
|
11540
11666
|
registerRequest(program2);
|
|
11541
11667
|
registerDocs(program2);
|
|
11668
|
+
registerCommands(program2);
|
|
11542
11669
|
try {
|
|
11543
|
-
registerSpecCommands(program2, (0,
|
|
11670
|
+
registerSpecCommands(program2, (0, import_yaml2.parse)(public_api_default));
|
|
11544
11671
|
} catch (error) {
|
|
11545
11672
|
fail(error);
|
|
11546
11673
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@beel_es/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Agent-first CLI for the BeeL invoicing API. Commands are derived at startup from the embedded OpenAPI spec. Run with npx @beel_es/cli — no install needed.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|