> ## Documentation Index
> Fetch the complete documentation index at: https://docs.infinity.swiss/llms.txt
> Use this file to discover all available pages before exploring further.

# Create invoice

> Creates a new customer invoice in the organisation. Set `isDraft` to `false` to immediately post the invoice and create the underlying transactions.

## Creating a customer invoice

Invoices belong to a template and are addressed to a contact (or a free-text recipient). They are made up of an ordered list of positions, each of which has a discriminator `type`:

* `product` — a standard billable line with a `quantity`, `singleAmount`, and `contraAccount`
* `discount` — reduces the preceding positions, by either a `percentage` or an absolute `amount` (exactly one of the two must be set)
* `text` — a non-billable text line
* `subtotal` — renders the running subtotal of the preceding positions

### Draft vs. posted

The `isDraft` flag controls whether the new invoice is posted immediately:

* `isDraft: true` — saves the invoice as a draft. No accounting transactions are created.
* `isDraft: false` — posts the invoice and creates the underlying transactions on `openingDate`. The transaction date must fall within an editable fiscal year and VAT period.

### Foreign-currency invoices

To issue an invoice in a non-primary currency, provide `originalCurrency` (3-character ISO 4217 code). The currency must already be enabled in the organisation.

`exchangeRate` is optional. When omitted, the invoice inherits the organisation's stored exchange rate for that currency, the same behaviour as foreign-currency [ledger entries](/api/accounting/create). When provided, it is expressed as `1 originalCurrency = X primaryCurrency` (e.g. `0.98` if 1 EUR = 0.98 CHF). An `exchangeRate` without `originalCurrency` is rejected.

### External reference

Use the optional `externalReference` string to retain an id that belongs outside Infinity. It is capped at 128 characters, returned on create, get, and list responses, and can be used as an exact-match filter when you [find invoices](/api/invoices/find).

The reference must be unique among the organisation's invoices. A duplicate returns `409 Conflict` with error code `customer-invoices/external-reference-already-exists`.

### Example

The following payload creates a posted CHF invoice with two product lines and a 10% discount:

```json theme={null}
{
  "template": "6474bb0cccc5b63aae56fa15",
  "openingDate": "2026-05-12",
  "recipient": "6474bb0cccc5b63aae56fa20",
  "title": "Beratungsleistungen Mai 2026",
  "isDraft": false,
  "payableInDays": 30,
  "amountMode": "gross",
  "language": "de",
  "positions": [
    {
      "type": "product",
      "name": "Beratung",
      "quantity": 4,
      "unit": "h",
      "singleAmount": 18000,
      "contraAccount": 3200,
      "taxCode": "UN81"
    },
    {
      "type": "product",
      "name": "Workshop-Vorbereitung",
      "quantity": 1,
      "singleAmount": 25000,
      "contraAccount": 3200,
      "taxCode": "UN81"
    },
    {
      "type": "discount",
      "percentage": 10,
      "discountText": "Rabatt"
    }
  ]
}
```

## Retrying safely with idempotency keys

If a request fails or times out, you may not know whether the invoice was created. To retry safely without creating a duplicate invoice, provide a unique `Idempotency-Key` header (for example a UUID) with your request:

```bash theme={null}
Idempotency-Key: 4cbe4d8c-8952-46a5-8bcd-1b4a67d0c496
```

When you retry a request with the same key and payload, the API returns the status and response of the original request – including the invoice it created – instead of creating the invoice again.

* Keys are scoped to your organisation, API key and endpoint. The same key can be used once for an invoice and once for an [estimate](/api/estimates/create).
* Keys are retained for 24 hours. After that, a key can be used again and will create a new invoice.
* Reusing a key with a different payload returns `409 Conflict` with error code `rest-api/idempotency-key-payload-mismatch`.
* While a request is still being processed, concurrent requests with the same key return `409 Conflict` with error code `rest-api/idempotency-key-request-in-progress`.
* If a request fails, its key is released, so the same key can be used to retry.

<Tip>
  Generate a new idempotency key for every new invoice, and reuse it only when
  retrying the same request.
</Tip>


## OpenAPI

````yaml POST /v1/invoices
openapi: 3.1.0
info:
  title: infinity.swiss Open API
  version: '1.0'
  summary: ''
servers:
  - url: https://api.infinity.swiss
security:
  - apiKey: []
paths:
  /v1/invoices:
    post:
      tags: []
      summary: Create invoice
      description: >-
        Creates a new customer invoice in the organisation. Set `isDraft` to
        `false` to immediately post the invoice and create the underlying
        transactions.
      operationId: create-invoice-v1
      parameters:
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        description: The invoice payload.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/InvoiceInput'
      responses:
        '201':
          description: The invoice was created successfully.
          content:
            application/json:
              schema:
                type: object
                properties:
                  invoice:
                    $ref: '#/components/schemas/Invoice'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorised'
        '403':
          $ref: '#/components/responses/InvoicePostingForbidden'
        '404':
          $ref: '#/components/responses/InvoiceReferencedResourceNotFound'
        '409':
          $ref: '#/components/responses/InvoiceCreationConflict'
components:
  parameters:
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        minLength: 1
        maxLength: 200
      description: >-
        A unique key that makes this request idempotent, for example a UUID. If
        a request fails or times out, it can safely be retried with the same key
        without creating a duplicate: a retry with the same key and payload
        returns the status and response of the original request, including the
        resource it created.


        Keys are scoped to your organisation and API key, and are retained for
        24 hours. Reusing a key with a different payload returns `409 Conflict`.
        While a request is still being processed, other requests with the same
        key also return `409 Conflict`.
  schemas:
    InvoiceInput:
      title: InvoiceInput
      type: object
      description: Request body for creating a customer invoice.
      required:
        - openingDate
        - positions
        - isDraft
      allOf:
        - $ref: '#/components/schemas/InvoiceBaseFields'
        - type: object
          properties:
            isDraft:
              type: boolean
              description: >-
                When `true` the invoice is created in draft state and no
                transactions are posted. Set to `false` to immediately post the
                invoice.
            isQrInvoice:
              type: boolean
              description: >-
                When `true` the document is rendered as a QR-bill (Swiss
                QR-Rechnung). Defaults to `false`.
    Invoice:
      title: Invoice
      type: object
      description: The full representation of a customer invoice.
      allOf:
        - $ref: '#/components/schemas/InvoiceCommonFields'
        - type: object
          required:
            - isQrInvoice
            - totalAmountPaid
            - totalRestAmount
            - isPaid
            - isOverpaid
            - isAnnulled
          properties:
            isQrInvoice:
              type: boolean
              description: '`true` when the document is rendered as a Swiss QR-bill.'
            totalAmountPaid:
              type: integer
              description: Sum of all recorded payments in cents.
            totalRestAmount:
              type: integer
              description: Outstanding balance in cents.
            isPaid:
              type: boolean
              description: '`true` once the invoice is fully paid.'
            isOverpaid:
              type: boolean
            isAnnulled:
              type: boolean
              description: '`true` once the invoice has been annulled.'
    InvoiceBaseFields:
      title: InvoiceBaseFields
      type: object
      description: Fields shared between invoice and estimate request payloads.
      properties:
        template:
          type: string
          description: >-
            The id of the invoice template to render the document with. When
            omitted while creating an invoice or estimate, the organisation's
            first template is used.
        openingDate:
          type: string
          format: date
          description: >-
            The opening date (issue date) of the document in YYYY-MM-DD format.
            For invoices, this is also the date used for the posted accounting
            transactions.
        positions:
          type: array
          description: >-
            The ordered list of positions on the document. At least one position
            is required, and discount positions must specify exactly one of
            `percentage` or `amount`.
          items:
            $ref: '#/components/schemas/InvoicePosition'
        recipient:
          type: string
          description: >-
            The id of the contact this document is addressed to. Required unless
            `customRecipientText` is provided.
        customRecipientText:
          type: string
          maxLength: 2000
          description: >-
            An ad-hoc recipient text used when no contact is associated with the
            document.
        externalReference:
          type: string
          maxLength: 128
          description: An optional reference for this document in an external system.
        title:
          type: string
          maxLength: 128
          description: Optional document title displayed on the rendered output.
        payableInDays:
          type: integer
          minimum: 0
          maximum: 365
          description: >-
            Payment terms for this document in days from the opening date.
            Defaults to 30 on create.
        introductoryText:
          type: string
          maxLength: 2000
          description: Text rendered above the positions.
        closingText:
          type: string
          maxLength: 2000
          description: Text rendered below the positions.
        amountMode:
          $ref: '#/components/schemas/AmountMode'
        language:
          $ref: '#/components/schemas/InvoiceLanguage'
        columns:
          type: array
          description: >-
            The columns to render in the positions table. If not provided the
            template default is used.
          items:
            $ref: '#/components/schemas/InvoiceColumn'
        originalCurrency:
          type: string
          minLength: 3
          maxLength: 3
          description: >-
            For foreign-currency documents: the 3-character ISO 4217 currency
            code the document is denominated in. Must be a currency code that is
            enabled in the organisation. When set without `exchangeRate`, the
            organisation's stored exchange rate for that currency is used.
        exchangeRate:
          type: number
          exclusiveMinimum: 0
          description: >-
            For foreign-currency documents: the conversion rate from the
            original currency to the primary accounting currency, expressed as
            `1 originalCurrency = X primaryCurrency`. Optional when
            `originalCurrency` is set; if omitted, defaults to the
            organisation's stored exchange rate for that currency. Cannot be set
            without `originalCurrency`.
    InvoiceCommonFields:
      title: InvoiceCommonFields
      type: object
      description: Response fields shared between invoices and estimates.
      required:
        - id
        - isDraft
        - number
        - organisation
        - template
        - openingDate
        - dueDate
        - positions
        - totalAmount
        - totalNetAmount
        - totalVatDebt
      properties:
        id:
          type: string
          description: A unique hex identifier for the document.
          readOnly: true
        isDraft:
          type: boolean
        number:
          type: string
          description: >-
            The formatted document number. May be empty for drafts that have not
            yet been issued.
        organisation:
          type: string
          description: The id of the owning organisation.
          readOnly: true
        template:
          type: string
          description: The id of the template used to render the document.
        recipient:
          type: string
          description: The id of the contact this document is addressed to.
        customRecipientText:
          type: string
        externalReference:
          type: string
          maxLength: 128
          description: An optional reference for this document in an external system.
        title:
          type: string
        openingDate:
          type: string
          format: date
        dueDate:
          type: string
          format: date
        payableInDays:
          type: integer
        introductoryText:
          type: string
        closingText:
          type: string
        positions:
          type: array
          items:
            $ref: '#/components/schemas/InvoicePosition'
        totalAmount:
          type: integer
          description: Total gross amount in cents (e.g. 12030 = 120.30).
        totalNetAmount:
          type: integer
          description: Total net amount in cents.
        totalVatDebt:
          type: integer
          description: Total VAT debt in cents.
        amountMode:
          $ref: '#/components/schemas/AmountMode'
        language:
          $ref: '#/components/schemas/InvoiceLanguage'
        originalCurrency:
          type: string
          minLength: 3
          maxLength: 3
          description: >-
            ISO 4217 currency code for the original document currency. Omitted
            for documents in the primary accounting currency.
        exchangeRate:
          type: number
          description: >-
            Exchange rate from the original currency to the primary accounting
            currency, if applicable.
        columns:
          type: array
          items:
            $ref: '#/components/schemas/InvoiceColumn'
        emailDeliveries:
          type: array
          description: Log of any email delivery attempts for this document.
          items:
            $ref: '#/components/schemas/EmailDelivery'
        publicPdfUrl:
          type: string
          description: >-
            A public URL for downloading the rendered PDF without
            authentication. May be omitted while the URL is being generated.
    InvoicePosition:
      title: InvoicePosition
      description: >-
        A single position on an invoice or estimate. Discriminated by the `type`
        field.
      oneOf:
        - $ref: '#/components/schemas/ProductPosition'
        - $ref: '#/components/schemas/DiscountPosition'
        - $ref: '#/components/schemas/TextPosition'
        - $ref: '#/components/schemas/SubtotalPosition'
      discriminator:
        propertyName: type
        mapping:
          product: '#/components/schemas/ProductPosition'
          discount: '#/components/schemas/DiscountPosition'
          text: '#/components/schemas/TextPosition'
          subtotal: '#/components/schemas/SubtotalPosition'
    AmountMode:
      title: AmountMode
      type: string
      description: >-
        Whether position amounts are entered gross (including VAT) or net
        (excluding VAT).
      enum:
        - gross
        - net
    InvoiceLanguage:
      title: InvoiceLanguage
      type: string
      description: The language used to render the document.
      enum:
        - de
        - en
        - fr
        - it
    InvoiceColumn:
      title: InvoiceColumn
      description: A column that can be shown on the rendered invoice or estimate.
      type: string
      enum:
        - quantity
        - unit
        - articleNumber
        - name
        - date
        - vatDebt
        - vatRate
        - singleAmount
        - totalAmount
        - discountRate
        - positionNumber
        - customTextColumn1
        - customTextColumn2
        - customTextColumn3
    EmailDelivery:
      title: EmailDelivery
      type: object
      description: A record of an attempt to send the document by email.
      required:
        - recipientEmail
        - status
        - timestamp
      properties:
        recipientEmail:
          type: string
          format: email
          description: The address the document was sent to.
        status:
          type: string
          description: The delivery status reported by the email provider.
        timestamp:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the delivery was recorded.
    ProductPosition:
      title: ProductPosition
      type: object
      description: >-
        A standard billable line item with a quantity, single amount, and contra
        account. Most invoice and estimate positions are of this type.
      required:
        - type
        - name
        - singleAmount
        - quantity
        - contraAccount
      properties:
        type:
          type: string
          enum:
            - product
          description: Discriminator for the position type.
        id:
          type: string
          description: >-
            Server-assigned identifier for the position. Present on responses;
            omit when creating.
          readOnly: true
        name:
          type: string
          maxLength: 256
          description: The name of the product or service for this position.
        singleAmount:
          type: integer
          description: >-
            The unit amount in cents. May be expressed gross or net depending on
            the document's `amountMode`.
          example: 12000
        quantity:
          type: number
          description: The number of units billed for this position.
          example: 2
        contraAccount:
          type: integer
          minimum: 1000
          maximum: 9999
          description: >-
            The contra account number this position books against (typically a
            revenue account).
        unit:
          type: string
          maxLength: 4
          description: Optional unit, e.g. `h`, `kg`, `pcs`.
        taxCode:
          $ref: '#/components/schemas/TaxCode'
        articleNumber:
          type: string
          maxLength: 48
          description: Optional article or SKU number to display alongside the position.
        description:
          type: string
          maxLength: 2000
          description: Optional multi-line description of the position.
        date:
          type: string
          maxLength: 12
          description: Optional free-text date column for the position, e.g. service date.
        discountRate:
          type: number
          minimum: 0
          maximum: 100
          description: Optional per-position discount rate in percent.
        customTextColumns:
          $ref: '#/components/schemas/CustomTextColumns'
        totalAmount:
          type: integer
          readOnly: true
          description: >-
            The total amount of this position (gross), in cents. Computed by the
            server.
        vatDebt:
          type: integer
          readOnly: true
          description: The VAT debt for this position, in cents. Computed by the server.
        originalCurrency:
          type: string
          minLength: 3
          maxLength: 3
          readOnly: true
          description: >-
            The 3-character ISO 4217 currency code in which this position was
            originally entered, if different from the primary accounting
            currency.
        exchangeRate:
          type: number
          readOnly: true
          description: The exchange rate used to convert the original currency amount.
    DiscountPosition:
      title: DiscountPosition
      type: object
      description: >-
        A discount line that reduces the sum of the preceding positions either
        by a percentage or by an absolute amount.
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - discount
        id:
          type: string
          readOnly: true
        percentage:
          type: number
          minimum: 0
          maximum: 100
          description: >-
            A percentage discount applied to the preceding positions. Exactly
            one of `percentage` or `amount` must be set.
        amount:
          type: integer
          minimum: 0
          description: >-
            An absolute discount amount in cents. Exactly one of `percentage` or
            `amount` must be set.
        discountText:
          type: string
          maxLength: 256
          description: Optional display text for the discount, e.g. "Rabatt".
    TextPosition:
      title: TextPosition
      type: object
      description: >-
        A non-billable text line that is rendered between positions for
        organisation or commentary.
      required:
        - type
        - content
      properties:
        type:
          type: string
          enum:
            - text
        id:
          type: string
          readOnly: true
        content:
          type: string
          maxLength: 2000
          description: The text content to render.
        style:
          type: string
          enum:
            - normal
            - headline
            - fine-print
          description: The visual style of the text line.
    SubtotalPosition:
      title: SubtotalPosition
      type: object
      description: >-
        A line that renders the running subtotal of all preceding positions,
        optionally with a custom label.
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - subtotal
        id:
          type: string
          readOnly: true
        label:
          type: string
          maxLength: 128
          description: Optional label for the subtotal line.
    TaxCode:
      title: TaxCode
      description: >-
        A VAT tax code. See [Liste der
        MWST-Codes](https://docs.infinity.swiss/de/docs/accounting/vat/tax-codes)
        for details. The empty string indicates no VAT.
      oneOf:
        - type: string
          enum:
            - ''
        - type: string
          enum:
            - UN81
            - UN77
            - UR26
            - UR25
            - US38
            - US37
            - UO81
            - UO77
            - BZB81
            - BZB77
            - BZM81
            - BZM77
            - VM81
            - VM38
            - VM26
            - VB81
            - VB38
            - VB26
            - VM77
            - VM37
            - VM25
            - VB77
            - VB37
            - VB25
            - ES81
            - ES26
            - ES77
            - ES25
            - UNO
            - UEX
            - ULA
            - ZOLLM
            - ZOLLB
    CustomTextColumns:
      title: CustomTextColumns
      type: object
      description: Optional values for the three custom text columns on a product position.
      properties:
        customTextColumn1:
          type: string
          maxLength: 64
        customTextColumn2:
          type: string
          maxLength: 64
        customTextColumn3:
          type: string
          maxLength: 64
  responses:
    BadRequest:
      description: >-
        A bad request is thrown if the request payload is malformed or fails
        validation.
      content:
        application/json:
          schema:
            type: object
            required:
              - code
            properties:
              code:
                type: string
                description: |-
                  The specific error code for this request. May be:
                  - `general/missing-fields`
                  - `general/malformed-request`
                  - `general/invalid-date`
                  - `general/input-too-long`
                  - `general/zero-or-negative-amount-not-allowed`
              invalidFields:
                type: array
                description: A list of the fields which were invalid, if applicable.
                items:
                  type: string
    Unauthorised:
      description: >-
        An unauthorised request was submitted. You may be using an incorrect or
        expired API token, or are attempting to access a document outside of the
        scope of the token.
      content:
        application/json:
          schema:
            type: object
            properties:
              code:
                type: string
                x-stoplight:
                  id: 08hkw8ukmi2lp
                description: Specific error code
          examples:
            Unauthorised request:
              value:
                code: general/unauthorised
    InvoicePostingForbidden:
      description: >-
        The requested transition is not allowed. This may happen when the
        relevant fiscal year or VAT period is not editable, or when the invoice
        is in a state that does not permit the operation.
      content:
        application/json:
          schema:
            type: object
            properties:
              code:
                type: string
                description: |-
                  The specific error code for this request. May include:
                  - `fiscal-year/not-found`
                  - `fiscal-year/closed`
                  - `vat-period/closed`
                  - `customer-invoice/is-draft`
                  - `customer-invoice/is-annulled`
                  - `customer-invoice/cannot-annul`
                  - `customer-invoice/cannot-modify`
    InvoiceReferencedResourceNotFound:
      description: >-
        A referenced resource could not be found, e.g. the invoice template,
        recipient contact, or one of the contra accounts used by a position.
      content:
        application/json:
          schema:
            type: object
            properties:
              code:
                type: string
                description: |-
                  The specific error code for this request. May include:
                  - `template/not-found`
                  - `contact/not-found`
                  - `ledger/account-not-found`
    InvoiceCreationConflict:
      description: >-
        A conflict is returned when an external reference belongs to another
        invoice, an idempotency key is reused with a different payload, or
        another request with the same idempotency key is still being processed.
      content:
        application/json:
          schema:
            type: object
            properties:
              code:
                type: string
                description: |-
                  The specific error code for this request. May be:
                  - `customer-invoices/external-reference-already-exists`
                  - `rest-api/idempotency-key-payload-mismatch`
                  - `rest-api/idempotency-key-request-in-progress`
  securitySchemes:
    apiKey:
      name: x-api-token
      type: apiKey
      in: header
      description: >-
        API token for authentication. Obtain from your Infinity account
        settings.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.