---
title: "Bulk create invoices"
method: POST
path: "/companies/{company}/invoices/bulk-create"
tags: ["Invoices"]
---

# Bulk create invoices

`POST /companies/{company}/invoices/bulk-create`

## Request

- Base URL: `https://api.factuarea.com/v1`
- URL: `https://api.factuarea.com/v1/companies/{company}/invoices/bulk-create`
- Auth: one of:
  - HTTP bearer
  - API key in header `X-API-Key`

Create up to 100 invoices in one call, each a full invoice payload; returns a `BulkCreateResult`.
- `dry_run=true` validates every row without persisting and classifies each in `results[]`.
- `dry_run=false` creates only the valid rows and reports the rest in `failures[]`.

## Path parameters

- `company` string, uuid, required

## Headers

- `Idempotency-Key` string, required
- `Factuarea-Version` string, date

## Request body

- BulkCreateInvoicesV1Request: Create invoices in bulk. `invoices[]` holds up to 100 invoice payloads and `dry_run` (default `false`) validates each row without persisting. Per-row rules (format, duplicate `external_id`, recipient AEAT census) are reported per row instead of failing the whole batch.
  - `invoices` CreateInvoiceRequest[], required
    - `type` 'F1' | 'F2' | 'null', nullable: Invoice type: `F1` (complete invoice, the default) or `F2` (simplified invoice, the ticket of a terminal). A simplified invoice needs simplified invoices enabled for the company (422 `simplified_invoices_disabled`) and cannot exceed the absolute cap of 3,000 € VAT included (422 `simplified_invoice_not_allowed`).
    - `client_id` string, uuid: Id of the client (UUID v7). Required with `type: F1` (or without `type`), and the client must have a complete tax ID. Optional with `type: F2`: without it the invoice is an anonymous ticket; with it, a qualified simplified invoice (the client must belong to your company).
    - `prices_include_tax` boolean, nullable: When `true`, the `unit_price` of every line is the FINAL price with taxes (VAT included) and the system computes the net base to the cent, so the invoice total equals the sum of the amounts you sent. If no distribution of the cents reaches that total the request is rejected with 422 `amount_reconciliation_failed` and nothing is issued. Catalog lines (`product_id`) are not accepted in this mode. Defaults to `false`.
    - `operation_on` string, date, nullable: Date the operation took place (YYYY-MM-DD) when it differs from the issue date (arts. 6.1.f and 7.1.c of Royal Decree 1619/2012). It cannot be later than `issued_on` (422 `operation_date_after_issue_date`) unless the first line that declares a `regime_key` uses 14 or 15. It is printed on the PDF and reported in the VeriFactu record.
    - `payment` object: Payment recorded in the same call: after issuing, a payment is registered for the whole amount due and the invoice ends up paid. It implies issuing. Its presence makes the request an unattended checkout.
      - `method` 'bank_transfer' | 'direct_debit' | 'sepa_direct_debit' | 'cash' | 'credit_card' | 'check' | 'paypal' | 'bizum' | 'other', required: Payment method, from the catalog of `GET /v1/payment-methods`. Required when `payment` is sent.
      - `paid_at` string, date-time, nullable: Date of the payment (ISO 8601); only its date is used. Defaults to the issue date.
      - `reference` string, nullable: Reference of the payment, for example the transaction id of the card terminal (up to 100 characters).
    - `series_id` string, uuid, nullable
    - `price_list_id` string, uuid, nullable
    - `reprice_strategy` 'existing_catalog_lines' | 'future_lines_only' | 'null', nullable
    - `issued_on` string, date, required
    - `due_on` string, date, required
    - `notes` string, nullable
    - `external_id` string, nullable
    - `metadata` Metadata, nullable: A free map of up to 50 key→value pairs for storing arbitrary structured data (values are strings up to 500 characters). Unlike `custom_fields` — an ordered list of typed `{field, value}` pairs with display semantics, present on the six document resources — `metadata` is an unordered map for opaque integration data; a document may carry both. The master resources (Client, Supplier) have no `custom_fields`, so their `metadata` doubles as the custom-fields store. **Reserved keys (read-only).** When the system auto-issues an invoice from a payment correlation (Stripe/GoCardless/MONEI), it writes `stripe_subscription_id`, `stripe_invoice_id`, `billing_reason`, `period_start` and `period_end` into that invoice metadata automatically. Do not set or overwrite them by hand — the platform owns them and a manual value may be replaced when the correlation runs.
    - `tags` string[], nullable
    - `custom_fields` object[], nullable
      - `field` string, required
      - `value` string, required
    - `lines` object[], required
      - `description` string, nullable
      - `additional_description` string, nullable
      - `quantity` number, required
      - `unit_price` union: Unit price. Send a JSON number or a decimal string with up to 4 decimals (`"0.0125"`, `"100.0000"`): the string is the exact form the response returns, so a line read from the API can be sent back unchanged. Derived amounts are computed from it and keep 2 decimals.
        - number
        - string
      - `tax_rate_id` string, uuid, nullable
      - `tax_rate` number, nullable: VAT rate of the line (0–100). Optional: when you omit it (or send `null`) the line takes, in this order, the tax it references or the tax assigned to its product, and otherwise the DEFAULT TAX of your company for this kind of document (tax settings), inherited as a whole: its rate, its indirect-tax regime (IVA, IGIC or IPSI) and its AEAT qualification; an exempt or not-subject default also declares its cause (`exemption_reason`) on the line unless the line sends its own. The line ends up exactly as if you had chosen that tax. If none of them exists the request is rejected with 422 `missing_required_param`, with `error.param` = `lines.N.tax_rate` and `error.line_index` = N (the zero-based index of the line): the API never guesses a rate. A `0` you send is honoured as a real 0 % rate, never confused with «not stated».
      - `retention_rate` number, nullable
      - `surcharge_rate` number, nullable
      - `retention_rate_id` string, uuid, nullable
      - `surcharge_rate_id` string, uuid, nullable
      - `product_id` string, uuid, nullable
      - `variant_id` string, uuid, nullable
      - `presentation_id` string, uuid, nullable
      - `confirmed_base_quantity` number, nullable
      - `discount_percent` number, nullable
      - `regime_key` '01' | '02' | '03' | '04' | '05' | '06' | '07' | '08' | '09' | '10' | '11' | '14' | '15' | '17' | '18' | '19' | '20' | 'null', nullable
      - `exemption_reason` 'E1' | 'E2' | 'E3' | 'E4' | 'E5' | 'E6' | 'N1' | 'N2' | 'null', nullable
      - `indirect_tax_regime` 'iva' | 'igic' | 'ipsi' | 'null', nullable
      - `line_type` 'NORMAL' | 'SUPLIDO' | 'null', nullable: Kind of line: `NORMAL` (default) for an ordinary line of your own operation, or `SUPLIDO` for a DISBURSEMENT — an amount you paid in the name and on behalf of the client (an official fee, duty or registry charge) and now re-invoice at cost. A disbursement is not part of your taxable base (art. 78.Tres.3 LIVA): it stays out of `subtotal`/`taxes_total`/`total`, is aggregated into `total_disbursements`, and is never declared in the AEAT VeriFactu record. A `SUPLIDO` line must carry no VAT, withholding, surcharge, discount, regime key, exemption cause or product, and is rejected on a simplified (`F2`) invoice.
      - `source_invoice_reference` string, nullable: Reference of the supporting document that originated the disbursement — the receipt or fee number issued by the public body (up to 100 characters). REQUIRED when `line_type` is `SUPLIDO`; leave it out on a normal line. Free text on purpose: the receipt of a public body is rarely a supplier invoice.
      - `source_invoice_ids` string[], nullable: Optional traceability of a disbursement: list of IDs (UUID v7) of your own expenses that back it. An expense of another company is rejected with 422. Omit it (or send `null`) when there is nothing to link.
      - `unit` string, nullable: Unit of measure printed next to the quantity on the document (`hours`, `kg`, `units`, …), up to 20 characters. Presentation only: free text, no closed catalog and no fiscal effect.
      - `exemption_reason_text` string, nullable: Free-text wording of the exemption provision of this line (up to 255 characters), printed under the line description to satisfy the mention required by art. 6.1.j of Royal Decree 1619/2012 when the catalogued cause does not cover it. Orthogonal to `exemption_reason` and to the document-level exemption cause; no coherence is enforced between them.
      - `line_total` number, nullable: Optional CHECKSUM of the line total. When sent, it is compared against the total this API computes and the request is rejected with 422 (`line_total_checksum_mismatch`, with the expected and received values in `error.details`) when they differ by more than one cent. Never stored and never returned: the invoiced amount is always the computed one, so this field only reports a rounding mismatch with your own ERP. Omit it and no checksum runs.
      - `configuration_uuid` string, uuid, nullable
      - `counted_quantity` number, nullable
      - `options` object[], nullable
        - `group_uuid` string, uuid, required
        - `value_uuid` string, uuid, required
      - `configuration_signature` string, nullable
      - `configuration_name` string, nullable
      - `price_source` 'price_list' | 'configuration' | 'presentation' | 'variant' | 'product' | 'manual' | 'supplier_offer' | 'pack_snapshot' | 'null', nullable
      - `price_semantics` 'per_base_unit' | 'per_commercial_unit' | 'null', nullable
      - `price_adjustment_total` number, nullable
      - `option_adjustments_absorbed` boolean, nullable
    - `options` object
      - `issue_directly` boolean, nullable: When `true`, the invoice is issued right after it is created (definitive number, frozen document) instead of staying as a draft.
      - `send_automatically` boolean, nullable: When `true`, the invoice is emailed once issued; it implies issuing. The recipient is `options.send_to` or, when you omit it, the email of the client. If there is no possible recipient (no client, or a client with no email, and no `send_to`) the request is rejected with 422 `missing_required_param` and `param` = `options.send_to` BEFORE anything is created: no draft is left behind and no number is consumed. Repeating the request of an `external_id` that is already issued does not email it again while that email is queued or delivered.
      - `send_to` string, email, nullable: Recipient email for `options.send_automatically`. Optional when the client has an email; required (422 `missing_required_param`) when you ask for the sending with no client or with a client that has no email. An empty value is rejected as well.
      - `wait_for_pdf` boolean, nullable: When `true`, the response waits up to about 15 seconds for the A4 PDF to be materialized (`pdf.status: ready`); if it is not ready by then `pdf.status` is `pending`. It implies issuing.
      - `register_verifactu` boolean, nullable: When `true`, the VeriFactu alta is generated synchronously, BEFORE responding, and the response carries its huella and QR in the `verifactu` block. It implies issuing. If the alta cannot be generated the invoice stays issued and the block reports `status: failed` with its `error_code`. Its transmission to AEAT follows its course in batches.
  - `dry_run` boolean, nullable

## Response `200`

- object
  - `data` BulkCreateResult, required: Result of a bulk invoice creation (`POST /v1/companies/{company}/invoices/bulk-create`). `dry_run` reports the mode. In validate-only mode (`dry_run=true`) `results[]` carries the per-row classification `{index, status, errors[], warnings[]}` and nothing is persisted; in create mode (`dry_run=false`) only valid rows are created and `failures[]` carries the rows that were not created (identified by `index`). `total = successful + failed`.
    - `dry_run` boolean, required: Whether the operation ran in validate-only mode (no invoice was created).
    - `total` integer, required: Number of rows processed (`successful + failed`).
    - `successful` integer, required: Number of rows that validated successfully (`dry_run=true`) or were created (`dry_run=false`).
    - `failed` integer, required: Number of rows that were invalid or could not be created. Equals `failures` length in create mode.
    - `results` object[], required: Per-row classification of validate-only mode (`dry_run=true`). Empty in create mode.
      - `index` integer, required: 0-based position of the row within the batch.
      - `status` string, required: Row status: `valid` (would be created without error) or `invalid` (has blocking errors).
      - `errors` FieldIssue[], required: Per-field blocking issues of the row. Empty when the row is valid.
        - `param` string, nullable, required: Name of the affected field, or `null` when the issue is not tied to a specific field.
        - `code` string, required: Machine-readable code for the field issue (stable across languages).
        - `message` string, required: Human-readable description of the issue, in Spanish.
      - `warnings` FieldIssue[], required: Per-field non-blocking warnings of the row (e.g. recipient not found in the AEAT census). Empty when there are none.
        - `param` string, nullable, required: Name of the affected field, or `null` when the issue is not tied to a specific field.
        - `code` string, required: Machine-readable code for the field issue (stable across languages).
        - `message` string, required: Human-readable description of the issue, in Spanish.
    - `failures` object[], required: Rows that could not be created (create mode, `dry_run=false`). Each item carries the 0-based `index`, an `error_code`, a Spanish `error_message` and the per-field `errors[]`. Empty in validate-only mode.
      - `index` integer, required: 0-based position of the row within the batch.
      - `error_code` string, required: Machine-readable error code from the v1 error catalog (stable across languages).
      - `error_message` string, required: Human-readable reason for the failure, in Spanish.
      - `errors` FieldIssue[]: Per-field blocking issues that made the row invalid.
        - `param` string, nullable, required: Name of the affected field, or `null` when the issue is not tied to a specific field.
        - `code` string, required: Machine-readable code for the field issue (stable across languages).
        - `message` string, required: Human-readable description of the issue, in Spanish.

## Other responses

- `400`: `parameter_unknown` with `subcode` `retired_active_profile_header`: the retired `X-Active-Profile` header was sent; the company goes in the path. `parameter_invalid_format` on `Factuarea-Version`: the header is not a `YYYY-MM-DD` date. `unsupported_api_version`: the `Factuarea-Version` date is not a supported version.
- `401`: Missing or invalid API key.
- `403`: The request is authenticated but not allowed: the API key lacks the required scope (`insufficient_scope`), the plan does not include the public API or this feature (`addon_not_active`, `feature_not_available_in_plan`), or the module is not available with a test key (`module_not_available_in_sandbox`).
- `404`: The requested resource does not exist or belongs to another company. `profile_not_found`: the company in the path does not exist, is not in the API key scope or belongs to the other environment (live or test).
- `409`: The request conflicts with data that already exists or with a request running at the same time. `resource_conflict`: another request was reserving identifiers for the same kind of record at that moment and this one could not complete its reservation. Nothing was written; send the request again after a few seconds.
- `422`: Validation failed, or the invoice cannot undergo the requested state transition (e.g. marking an already-paid invoice as paid, or editing an issued invoice — use a corrective instead). The `error.param` field identifies which input is invalid, if any. Issuing or annulling an invoice can also be rejected with `verifactu_not_eligible`: either a field of the invoice does not fit its billing record, or — only when the company has VeriFactu enabled in NO VERI*FACTU mode — it has no usable signing certificate (`error.subcode: signing_certificate_unavailable`; `error.param` is `certificate`, `representation` or `system_certificate`, depending on what is missing); in both cases the invoice stays exactly as it was.
- `429`: Rate limit exceeded. Retry after the duration in `Retry-After`.
- `500`: Unexpected server error.

---

[API](https://skmtc.dev/factuarea/apis/factuarea-public-api.md) · [All operations](https://skmtc.dev/factuarea/apis/factuarea-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/factuarea/apis/factuarea-public-api/revisions/718e77934422?raw)
