openapi: 3.0.3
info:
  title: FIRA Custom Webshop API
  description: |
    Webhook API for custom webshops integrating with [FIRA](https://fira.finance).

    Send a POST whenever an order is created in your webshop and FIRA will:
      - create an Invoice in your FIRA SME
      - optionally send it as an **eRačun** to Pondi (ePoslovanje.hr) — see the `eInvoiceRequest` block
      - return the created Invoice with eRačun status

    Auth is via the `FIRA-Api-Key` header. The key is generated when you register your
    custom webshop in FIRA → Settings → Webshop.
  version: 1.2.0
  contact:
    name: FIRA support
    email: info@fira.finance
    url: https://fira.finance
servers:
  - url: https://app.fira.finance
    description: Production
  - url: https://stage.fira.finance
    description: Staging
security:
  - FiraApiKey: []
tags:
  - name: API
    description: |
      Create invoices, send eRačun, fetch invoice PDFs, and look up reference data — all
      authenticated with the same `FIRA-Api-Key` header.
paths:
  /api/v1/webshop/order/custom:
    post:
      tags: [API]
      summary: Create invoice from custom webshop order
      description: |
        Creates an Invoice in FIRA from a webshop order payload. If `invoiceType` is `E_RAČUN`
        and `eInvoiceRequest` is supplied, FIRA also generates the UBL document and sends it
        to Pondi/ePoslovanje immediately, returning the Pondi document status in the response.

        **Idempotency (optional).** If you send `webshopOrderId`, a second POST with the same
        value for the same SME + invoiceType + webshopType is rejected with
        `400 Invoice for this order already created.` Omit `webshopOrderId` and every POST will
        create a fresh invoice — useful if your webshop already prevents duplicates on its side.

        **Errors.** Validation failures and Pondi rejections come back as `400` with a plain-text
        body explaining what to fix. The Invoice — if created — is left in place; the eRačun
        retry mechanism will re-attempt delivery.
      operationId: createCustomWebshopOrder
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WebshopOrderModel'
            examples:
              minimalRacun:
                summary: Minimal RAČUN (no eRačun)
                value:
                  webshopOrderId: 1001
                  webshopOrderNumber: "ORD-2026-0001"
                  invoiceType: RAČUN
                  paymentType: TRANSAKCIJSKI
                  currency: EUR
                  taxesIncluded: true
                  createdAt: "2026-05-15T10:00:00Z"
                  dueDate: "2026-05-29"
                  billingAddress:
                    name: "Primjer Kupac d.o.o."
                    address1: "Ilica 1"
                    city: "Zagreb"
                    country: "HR"
                    zipCode: "10000"
                    email: "racuni@primjer-kupac.hr"
                    oib: "12345678901"
                  taxValue: 20.00
                  brutto: 100.00
                  netto: 80.00
                  lineItems:
                    - name: "Konzultantska usluga"
                      price: 100.00
                      quantity: 1
                      unit: "kom"
                      taxRate: 0.25
              eRacunStandard:
                summary: E_RAČUN with 25% VAT
                value:
                  webshopOrderId: 1002
                  webshopOrderNumber: "ORD-2026-0002"
                  invoiceType: E_RAČUN
                  paymentType: TRANSAKCIJSKI
                  currency: EUR
                  taxesIncluded: true
                  createdAt: "2026-05-15T10:00:00Z"
                  dueDate: "2026-05-29"
                  billingAddress:
                    name: "FIRA Solutions d.o.o."
                    address1: "Trg Josipa Jurja Strossmayera 8"
                    city: "Zagreb"
                    country: "HR"
                    zipCode: "10000"
                    email: "racuni@fira.finance"
                    oib: "21233832319"
                  taxValue: 20.00
                  brutto: 100.00
                  netto: 80.00
                  lineItems:
                    - name: "Konzultantska usluga"
                      price: 100.00
                      quantity: 1
                      unit: "kom"
                      taxRate: 0.25
                      kpdCode: "63.10.13"
                  eInvoiceRequest:
                    businessProcess: "P1"
                    invoiceTypeCode: "380"
                    deliveryDate: "2026-05-15"
                    orderReference: "#123"
              eRacunWithExemption:
                summary: E_RAČUN with 0% VAT line + exemption (CLANAK_39)
                description: |
                  Required: every line with `taxRate: 0` carries both a `productTaxCategory` and a
                  `taxExemptionReason` enum value. The full list of valid enum names lives inline on
                  the `WebshopLineItemModel` schema below (`productTaxCategory.enum` /
                  `taxExemptionReason.enum`).
                value:
                  webshopOrderId: 1003
                  webshopOrderNumber: "ORD-2026-0003"
                  invoiceType: E_RAČUN
                  paymentType: TRANSAKCIJSKI
                  currency: EUR
                  taxesIncluded: true
                  createdAt: "2026-05-15T10:00:00Z"
                  dueDate: "2026-05-29"
                  billingAddress:
                    name: "FIRA Solutions d.o.o."
                    address1: "Trg Josipa Jurja Strossmayera 8"
                    city: "Zagreb"
                    country: "HR"
                    zipCode: "10000"
                    email: "racuni@fira.finance"
                    oib: "21233832319"
                  taxValue: 20.00
                  brutto: 150.00
                  netto: 130.00
                  lineItems:
                    - name: "Konzultantska usluga"
                      price: 100.00
                      quantity: 1
                      unit: "kom"
                      taxRate: 0.25
                      kpdCode: "63.10.13"
                    - name: "Edukacija - tečaj"
                      description: "Stručno usavršavanje (oslobođeno po čl. 39)"
                      price: 50.00
                      quantity: 1
                      unit: "kom"
                      taxRate: 0
                      kpdCode: "85.59.13"
                      productTaxCategory: "OSLOBODENO"
                      taxExemptionReason: "CLANAK_39"
                  eInvoiceRequest:
                    businessProcess: "P1"
                    invoiceTypeCode: "380"
                    deliveryDate: "2026-05-15"
                    orderReference: "#123"
              ponuda:
                summary: PONUDA (offer)
                value:
                  webshopOrderId: 1004
                  webshopOrderNumber: "ORD-2026-0004"
                  invoiceType: PONUDA
                  paymentType: TRANSAKCIJSKI
                  currency: EUR
                  taxesIncluded: true
                  createdAt: "2026-05-15T10:00:00Z"
                  validTo: "2026-06-15"
                  billingAddress:
                    name: "Primjer Kupac d.o.o."
                    address1: "Ilica 1"
                    city: "Zagreb"
                    country: "HR"
                    zipCode: "10000"
                  taxValue: 20.00
                  brutto: 100.00
                  netto: 80.00
                  lineItems:
                    - name: "Usluga"
                      price: 100.00
                      quantity: 1
                      unit: "kom"
                      taxRate: 0.25
              fiskalniB2C:
                summary: FISKALNI_RAČUN — card, physical person (B2C)
                description: |
                  Fiscalized invoice paid by card, issued to a physical person. Requires Fiscal
                  settings in your SME (FISKAL_1.p12). HR SMEs only.

                  `b2bClient: false` marks the buyer as a physical person — their OIB is **never**
                  reported to the Tax Authority, even if your checkout collected one. Always send
                  the flag explicitly; if you omit it, FIRA falls back to treating any present
                  `billingAddress.oib` as a B2B purchase.
                value:
                  webshopOrderId: 1005
                  webshopOrderNumber: "ORD-2026-0005"
                  invoiceType: FISKALNI_RAČUN
                  paymentType: KARTICA
                  b2bClient: false
                  currency: EUR
                  taxesIncluded: true
                  createdAt: "2026-05-15T14:32:00Z"
                  billingAddress:
                    name: "Marija Marić"
                    address1: "Maksimirska 4"
                    city: "Zagreb"
                    country: "HR"
                    zipCode: "10000"
                    email: "marija@example.hr"
                  taxValue: 5.00
                  brutto: 25.00
                  netto: 20.00
                  lineItems:
                    - name: "Pretplata mjesečna"
                      price: 25.00
                      quantity: 1
                      unit: "kom"
                      taxRate: 0.25
              fiskalniB2B:
                summary: FISKALNI_RAČUN — card, company buyer (B2B / R1)
                description: |
                  Fiscalized invoice paid by card, issued to a company. `b2bClient: true` together
                  with `billingAddress.oib` makes FIRA report the buyer's OIB to the Tax Authority
                  as "OIB primatelja" (R1-style sale). The OIB must be the company's valid
                  11-digit OIB — no `HR` prefix, no VAT-ID format.
                value:
                  webshopOrderId: 1006
                  webshopOrderNumber: "ORD-2026-0006"
                  invoiceType: FISKALNI_RAČUN
                  paymentType: KARTICA
                  b2bClient: true
                  currency: EUR
                  taxesIncluded: true
                  createdAt: "2026-05-15T15:05:00Z"
                  billingAddress:
                    name: "Primjer Kupac d.o.o."
                    company: "Primjer Kupac d.o.o."
                    address1: "Ilica 1"
                    city: "Zagreb"
                    country: "HR"
                    zipCode: "10000"
                    email: "racuni@primjer-kupac.hr"
                    oib: "12345678901"
                  taxValue: 5.00
                  brutto: 25.00
                  netto: 20.00
                  lineItems:
                    - name: "Pretplata mjesečna"
                      price: 25.00
                      quantity: 1
                      unit: "kom"
                      taxRate: 0.25
      responses:
        '200':
          description: Invoice created (and eRačun sent if requested). The response is the persisted Invoice.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InvoiceResponse'
              examples:
                eRacunSent:
                  summary: E_RAČUN sent successfully — einvoice fields populated
                  value:
                    id: 145266
                    invoiceType: "E_RAČUN"
                    invoiceNumber: "17-1-1-2025"
                    invoicePaymentStatus: "OPEN"
                    overall: 150.00
                    inTotal: 130.00
                    taxValue: 20.00
                    einvoiceId: 215667
                    eInvoiceDocumentStatus: "PENDING"
                    eInvoiceDocumentStatusMessage: "Na čekanju"
                eRacunFailed:
                  summary: Invoice created but eRačun rejected by Pondi — einvoiceError filled, retry will run later
                  value:
                    id: 145268
                    invoiceType: "RAČUN"
                    invoiceNumber: "1-F1-1"
                    overall: 100.00
                    einvoiceError: "User account is not active. You are not yet registered as a FIRA user with ePoslovanje (Pondi d.o.o.). …"
        '400':
          description: Bad request — validation, idempotency conflict, or missing webshop settings.
          content:
            text/plain:
              schema:
                type: string
              examples:
                duplicateOrder:
                  value: "Invoice for this order already created. This webshop order is already present."
                missingSettings:
                  value: "Webshop settings are incomplete — missing: default bank account (IBAN) for payment. Open FIRA → Settings → Webshop settings and fill in the missing field."
                missingEInvoiceRequest:
                  value: "eInvoiceRequest is required when invoiceType=E_RAČUN"
        '401':
          description: Missing or invalid FIRA-Api-Key
          content:
            text/plain:
              schema:
                type: string
        '500':
          description: Unexpected server error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorDetails'

  /api/v1/webshop/invoice/{invoiceId}/pdf:
    get:
      tags: [API]
      summary: Download invoice PDF
      description: |
        Returns the rendered PDF for one of your invoices. Use this to attach the document to
        customer emails, store a copy on your side, or surface it from your order admin.

        Useful invoice IDs come from the `id` field on the `POST /webshop/order/custom` response.
      operationId: getInvoicePdf
      parameters:
        - name: invoiceId
          in: path
          required: true
          schema:
            type: integer
            format: int64
          description: FIRA-internal invoice ID (the `id` returned by `POST /webshop/order/custom`).
      responses:
        '200':
          description: PDF stream. `Content-Disposition` is `inline` so browsers render it directly.
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '401':
          description: Missing or invalid FIRA-Api-Key.
          content:
            text/plain:
              schema:
                type: string
        '404':
          description: Invoice not found for this key.
        '500':
          description: PDF rendering failed (template error, certificate issue for fiscalized invoices, etc.).
          content:
            text/plain:
              schema:
                type: string

components:
  securitySchemes:
    FiraApiKey:
      type: apiKey
      in: header
      name: FIRA-Api-Key
      description: |
        Custom webshop secret. Find it in FIRA → Settings → Webshop → your webshop → "Webshop secret".

  schemas:
    WebshopOrderModel:
      type: object
      required: [invoiceType, currency, lineItems]
      properties:
        webshopOrderId:
          type: integer
          format: int64
          description: |
            **Optional.** Your webshop's order ID. If supplied, it acts as an idempotency key —
            a second POST with the same value for the same SME + invoiceType + webshopType is
            rejected with `400 Invoice for this order already created.`
            Omit it (or send `null`) and every POST will create a fresh invoice.
        webshopOrderNumber:
          type: string
          description: |
            Human-readable order reference from your shop (e.g. `"ORD-2026-0001"`, `"#1142"`).
            FIRA:
              - persists it on the invoice (`invoice.webshop_order_number`) so you can search /
                cross-reference invoices by their original order tag later;
              - prepends it as `"ORDER: <value>"` on the invoice **PDF terms** block, so the
                customer sees which order this invoice covers.

            Optional. Send `null` and the PDF terms line is just omitted. Unlike `webshopOrderId`
            (numeric idempotency key) this is a display string — you can reuse the same value
            across retries without triggering the duplicate-order guard.
        invoiceType:
          type: string
          enum: [PONUDA, RAČUN, RAČUN_ZA_PREDUJAM, STORNO_RAČUN, E_RAČUN, FISKALNI_RAČUN, FISKALNI_STORNO_RAČUN]
          description: |
            - `PONUDA` — offer (no fiscalization, no eRačun submission — ideal for testing)
            - `RAČUN` — standard invoice
            - `RAČUN_ZA_PREDUJAM` — advance-payment invoice (issued when the buyer pays before
              delivery). Same email + payment-data behavior as `RAČUN` (driven by your "Email for
              RAČUN" / "Show payment data on RAČUN" toggles in Webshop settings). Reconcile it
              against the final invoice on your own side once goods/service ship.
            - `STORNO_RAČUN` — storno (reversal) of a previously issued `RAČUN`. Send the same line items;
              line/total amounts are automatically forced negative. Reference the original through `internalNote`.
            - `E_RAČUN` — invoice + immediate Pondi eRačun send (requires `eInvoiceRequest`)
            - `FISKALNI_RAČUN` — fiscalized invoice (HR SMEs with Fiscal settings only)
            - `FISKALNI_STORNO_RAČUN` — storno of a previously fiscalized invoice. Same rules as `STORNO_RAČUN`
              but also re-fiscalized through the Tax Authority. HR SMEs with Fiscal settings only.
        paymentType:
          type: string
          enum: [GOTOVINA, TRANSAKCIJSKI, KARTICA]
        b2bClient:
          type: boolean
          description: |
            **Is the buyer a legal entity (B2B)?** Only relevant for `FISKALNI_RAČUN` /
            `FISKALNI_STORNO_RAČUN`; ignored for all other invoice types.

            Controls whether the client's OIB is reported to the Croatian Tax Authority as
            "OIB primatelja" (the R1-style buyer identification) during fiscalization:

            - `true` — buyer is a company / sole trader. Their `billingAddress.oib` (HR buyers,
              cash/card payments) is included in the fiscalization message.
            - `false` — buyer is a **physical person**. Their OIB is **never** reported to the
              Tax Authority, even if `billingAddress.oib` is filled in. Send this whenever your
              checkout lets private customers enter a personal OIB (e.g. for warranty records).
            - omitted / `null` — legacy behavior: FIRA infers the flag from OIB presence on the
              resolved client (`billingAddress.oib` set → treated as B2B).

            **Recommendation:** always send this field explicitly on fiscal invoice types. Derive
            it from your checkout's "company purchase" toggle (company name / VAT-ID field filled
            in), not from the mere presence of an OIB.
        paymentGatewayName:
          type: string
          description: |
            Optional. Display-only. When provided, FIRA prepends `"Način plaćanja: {value}"`
            (or the English / German equivalent based on buyer country) to the **PDF terms** block,
            right under the `ORDER: …` line — so the customer sees on the receipt which gateway
            charged them. Useful for `"Stripe"`, `"Monri"`, `"PayPal"`, etc. Backend accepts any
            string; not validated against your configured gateways.
        createdAt:
          type: string
          format: date-time
          description: ISO-8601, e.g. `2026-05-15T10:00:00Z`
        dueDate:
          type: string
          format: date
        validTo:
          type: string
          format: date
          description: Used by `PONUDA` (offer expiry date).
        currency:
          type: string
          description: ISO 4217 code, e.g. `EUR`, `USD`.
        taxesIncluded:
          type: boolean
          default: true
          description: |
            Tells FIRA how to interpret each `lineItems[].price`.

            - `true` (**default** when omitted or `null`) — `price` is **gross** (with VAT). FIRA
              derives net as `price / (1 + taxRate)`. Typical for B2C checkouts where the shopper
              sees the gross price.
            - `false` — `price` is **net** (without VAT). FIRA computes gross as `price * (1 + taxRate)`.
              Typical for B2B catalogs that quote net prices.

            Either way, the persisted invoice carries the same net total + gross total — only the
            calculation direction differs.

            **Side effect:** when effectively `true`, product prices are stored with 3 decimals so a
            gross→net split with rates like 25% doesn't lose precision. FIRA falls back to 2 decimals
            if every third decimal turns out to be zero.
        billingAddress:
          $ref: '#/components/schemas/WebshopCustomerAddressModel'
        shippingAddress:
          allOf:
            - $ref: '#/components/schemas/WebshopCustomerAddressModel'
          description: Used as delivery place city in the PDF if FIRA's default delivery place is not set.
        taxValue:
          type: number
          format: double
          description: |
            Total VAT amount for the order — sum of (lineItem.netto × taxRate) across every line.
            Written verbatim to the invoice header (`invoice.tax_value`) so the PDF header sum and
            the line-item sum must match. See `taxesIncluded` for the gross↔net derivation rule
            FIRA applies to each line.

            **Non-PDV SMEs must send `0`.** If your SME is not in the PDV system (`Sme.inPdv = false`
            in FIRA settings) and you send `taxValue > 0`, the request is rejected with
            `400 Order contains taxes while the SME is not in the PDV system.` and the error is
            logged in your webshop error log. Either flip the SME to PDV in FIRA settings or send
            `taxValue: 0` with all `lineItems[].taxRate: 0`.

            For storno invoice types (`STORNO_RAČUN`, `FISKALNI_STORNO_RAČUN`) the sign is forced
            negative server-side, so you can send the positive value with negative quantities and
            FIRA will flip it.
        brutto:
          type: number
          format: double
          description: |
            Total gross amount for the order (net + tax). Written verbatim to the invoice header
            (`invoice.overall`). Must match the sum of `lineItems[].price × quantity` when
            `taxesIncluded: true`, or `Σ(price × quantity × (1 + taxRate))` when `taxesIncluded: false`.
        netto:
          type: number
          format: double
          description: |
            Total net amount for the order (gross - tax). Written verbatim to the invoice header
            (`invoice.in_total`).

            **Required for PDV SMEs.** Non-PDV SMEs may omit it and FIRA will treat `brutto` as
            net (since there's no tax). For CUSTOM webshops a missing `netto` on a PDV SME is
            rejected with `400 Custom webshop order is missing 'netto' for SME registered for VAT.`
        discounts:
          type: array
          items:
            $ref: '#/components/schemas/WebshopLineItemModel'
        totalShipping:
          $ref: '#/components/schemas/WebshopLineItemModel'
        lineItems:
          type: array
          items:
            $ref: '#/components/schemas/WebshopLineItemModel'
        internalNote:
          type: string
          description: Visible only to the SME user in FIRA. Not printed on the PDF.
        termsHR:
          type: string
          description: Override terms shown on the PDF when buyer country is HR. Accepts HTML.
        termsEN:
          type: string
          description: Override terms shown on the PDF when buyer country is neither HR/AT/DE. Accepts HTML.
        termsDE:
          type: string
          description: Override terms shown on the PDF when buyer country is AT or DE. Accepts HTML.
        eInvoiceRequest:
          allOf:
            - $ref: '#/components/schemas/WebshopEInvoiceRequest'
          description: |
            **Required when `invoiceType` is `E_RAČUN`.** Carries the eRačun-specific metadata
            (business process, references, optional delivery dates) that FIRA forwards to Pondi.
            Seller OIB + operator info are derived server-side — not part of this payload.

    WebshopLineItemModel:
      type: object
      required: [name, price, quantity, taxRate]
      properties:
        name:
          type: string
        description:
          type: string
        lineItemId:
          type: string
          description: |
            Display-only SKU / your internal line reference. FIRA stores it on the invoice line
            (column `product_number`), prints it as the SKU on the PDF, and groups by it in
            reporting aggregations.

            **Not a lookup key.** It never queries FIRA's product registry, even if the value
            happens to equal a registered product number. Use `productCode` for registry lookups.
        price:
          type: number
          format: double
        quantity:
          type: number
          format: double
        unit:
          type: string
        taxRate:
          type: number
          format: double
          description: Decimal, e.g. `0.25` for 25%. `0` requires `taxExemptionReason` when sending eRačun.
        kpdCode:
          type: string
          description: |
            8-digit KPD code (Croatian Classification of Products by Activity). Required on every
            taxable line when sending eRačun, unless the line inherits one via `productCode`.

            **KPD resolution on each line, in order:**
            1. `kpdCode` here — wins outright.
            2. else `productCode` → looked up in FIRA's product registry; if the matched product
               has a KPD, that KPD is inherited onto the invoice line.
            3. else no KPD — eRačun send will be rejected for lines where the business process /
               invoice type code requires KPD (most do; `P4`, `P9`, and type code `386` don't).
        productCode:
          type: string
          description: |
            SME-scoped key into FIRA's product registry (FIRA UI → Settings → Products). When set,
            FIRA looks up the SME's ACTIVE product whose **`product_number`** matches this value
            and, if it carries a KPD, inherits that KPD onto the invoice line (only when `kpdCode`
            is not supplied — see resolution priority on `kpdCode`).

            Useful when your FIRA catalog already has KPDs assigned: send `productCode` and let
            FIRA fill in the KPD instead of tracking it webshop-side.

            **Naming caveat:** the JSON field is `productCode`, but it matches against the registry's
            `product_number` column. Same thing, just renamed for the public API.

            **Not the same as `lineItemId`** — `lineItemId` is a free-form display SKU that's never
            looked up. `productCode` is the lookup key.
        discountPercentage:
          type: number
          format: double
          description: Per-line discount, e.g. `10.0` for 10% off.
        taxExemptionReason:
          type: string
          description: |
            **Required when `taxRate: 0` and sending eRačun.** Stable enum name; FIRA renders the
            Croatian text + UBL `vatexCode` on the eRačun XML server-side. Pick from the values
            below — they correspond to Croatian Zakon o PDV-u articles plus the four prolazne
            stavke (POV_NAKN, POV_NAKN_TROSAK, TURISTICKA, PROLAZNA_STAVKA) and
            POSEBNI_POREZ_NA_MOTORNA_VOZILA.

            | Enum                            | Croatian description |
            |---------------------------------|----------------------|
            | `CLANAK_19`                     | Mjesto oporezivanja prema mjestu gdje se nekretnina nalazi (čl. 19. ZPDV). |
            | `CLANAK_20`                     | Mjesto stjecanja dobara unutar EU — gdje završava otprema / prijevoz (čl. 20. ZPDV). |
            | `CLANAK_33`                     | Porezna osnovica — naknada za isporuke dobara i usluga (čl. 33. ZPDV). |
            | `CLANAK_38_ST_6`                | Stopa 0 % — isporuka i ugradnja solarnih ploča (čl. 38. st. 6. ZPDV). |
            | `CLANAK_39`                     | Oslobođenja u javnom interesu — zdravstvo, školstvo, socijalna skrb, sport, kultura (čl. 39. ZPDV, VATEX-EU-132). |
            | `CLANAK_40`                     | Financijska oslobođenja — osiguranje, krediti, platni promet, fondovi (čl. 40. ZPDV). |
            | `CLANAK_41`                     | Isporuke unutar EU s pravom na pretporez (čl. 41. ZPDV, VATEX-EU-IC). |
            | `CLANAK_42`                     | Stjecanja unutar EU oslobođena PDV-a (čl. 42. ZPDV). |
            | `CLANAK_43`                     | Posebna oslobođenja — pomorski/zračni promet, slobodne zone (čl. 43. ZPDV). |
            | `CLANAK_44`                     | Oslobođenje kod uvoza — povrat dobara, umjetnine, diplomatski uvoz (čl. 44. ZPDV, VATEX-EU-143). |
            | `CLANAK_45`                     | Izvoz dobara u treće zemlje s pravom na pretporez (čl. 45. ZPDV, VATEX-EU-G). |
            | `CLANAK_46`                     | Međunarodni prijevoz osoba/dobara (čl. 46. ZPDV, VATEX-EU-G). |
            | `CLANAK_47`                     | Posredničke usluge kod izvoza (čl. 47. ZPDV, VATEX-EU-148). |
            | `CLANAK_48`                     | Isporuke i održavanje brodova/zrakoplova u međunarodnom prometu (čl. 48. ZPDV, VATEX-EU-151). |
            | `CLANAK_49`                     | Dodatne usluge kod izvoza / međunarodnog prijevoza (čl. 49. ZPDV, VATEX-EU-G). |
            | `CLANAK_50`                     | Isporuke nekretnina osim građevinskih zemljišta / novih zgrada (čl. 50. ZPDV). |
            | `CLANAK_51`                     | Najam nekretnina bez prava na pretporez (čl. 51. ZPDV). |
            | `CLANAK_52`                     | Isporuke dobara korištenih isključivo za oslobođene djelatnosti (čl. 52. ZPDV). |
            | `CLANAK_53`                     | Isporuke ljudskih organa, tkiva, krvi (čl. 53. ZPDV). |
            | `CLANAK_54`                     | Investicijsko zlato i pripadajuće posredničke usluge (čl. 54. ZPDV). |
            | `CLANAK_75`                     | Prijenos porezne obveze — primatelj građevinske usluge plaća PDV (čl. 75. st. 3. ZPDV, VATEX-EU-AE). |
            | `CLANAK_90`                     | Mali porezni obveznici — promet < 60.000 EUR (čl. 90. ZPDV). |
            | `CLANAK_91`                     | Posebni postupak — putničke agencije (čl. 91. ZPDV, VATEX-EU-D). |
            | `CLANAK_92`                     | Paušalna naknada za poljoprivrednike (čl. 92. ZPDV). |
            | `CLANAK_95_1`                   | Maržni postupak — rabljena roba (čl. 95. ZPDV, VATEX-EU-F). |
            | `CLANAK_95_2`                   | Maržni postupak — umjetnička djela (čl. 95. ZPDV, VATEX-EU-I). |
            | `CLANAK_95_3`                   | Maržni postupak — kolekcionarski predmeti i antikviteti (čl. 95. ZPDV, VATEX-EU-J). |
            | `POV_NAKN_TROSAK`               | Povratna naknada prikupljena u ime Fonda za zaštitu okoliša — prolazna stavka, ne ulazi u poreznu osnovicu. |
            | `POV_NAKN`                      | Povratna naknada za ambalažu. |
            | `TURISTICKA`                    | Turistička pristojba prikupljena u ime turističke zajednice — prolazna stavka. |
            | `PROLAZNA_STAVKA`               | Prolazna stavka koju poduzetnik plaća u ime i za račun kupca. |
            | `POSEBNI_POREZ_NA_MOTORNA_VOZILA` | PPMV — posebni porez na motorna vozila. |
          example: "CLANAK_39"
          enum:
            - CLANAK_19
            - CLANAK_20
            - CLANAK_33
            - CLANAK_38_ST_6
            - CLANAK_39
            - CLANAK_40
            - CLANAK_41
            - CLANAK_42
            - CLANAK_43
            - CLANAK_44
            - CLANAK_45
            - CLANAK_46
            - CLANAK_47
            - CLANAK_48
            - CLANAK_49
            - CLANAK_50
            - CLANAK_51
            - CLANAK_52
            - CLANAK_53
            - CLANAK_54
            - CLANAK_75
            - CLANAK_90
            - CLANAK_91
            - CLANAK_92
            - CLANAK_95_1
            - CLANAK_95_2
            - CLANAK_95_3
            - POV_NAKN_TROSAK
            - POV_NAKN
            - TURISTICKA
            - PROLAZNA_STAVKA
            - POSEBNI_POREZ_NA_MOTORNA_VOZILA
        productTaxCategory:
          type: string
          description: |
            HR tax category for the line.

            **Optional for the three standard rates** (`taxRate` of `0.25`, `0.13`, `0.05`) —
            when omitted, FIRA derives the category from `taxRate`
            (→ `STANDARDNA_STOPA_25` / `STANDARDNA_STOPA_13` / `STANDARDNA_STOPA_5`).

            **Required for every line with `taxRate: 0`** — together with `taxExemptionReason`.
            The 0 % rate maps to multiple different categories, so the integrator must declare
            which one applies. If either field is missing on a zero-rate line, the request is
            rejected with HTTP 400.

            | Enum                                                   | `taxRate` | HR code   | Description |
            |--------------------------------------------------------|-----------|-----------|-------------|
            | `STANDARDNA_STOPA_25`                                  | `0.25`    | `HR:PDV25`| Standardna stopa PDV-a 25 %. |
            | `STANDARDNA_STOPA_13`                                  | `0.13`    | `HR:PDV13`| Standardna stopa PDV-a 13 %. |
            | `STANDARDNA_STOPA_5`                                   | `0.05`    | `HR:PDV5` | Standardna stopa PDV-a 5 %. |
            | `OSLOBODENO`                                           | `0`       | `HR:E`    | Artikl oslobođen PDV-a prema odgovarajućem članku (npr. mali porezni obveznici). |
            | `PRIJENOS_POREZNE_OBVEZE`                              | `0`       | `HR:AE`   | Reverse charge — porez plaća primatelj računa. |
            | `POVRATNA_NAKNADA`                                     | `0`       | `HR:POVNAK` | Povratna naknada za ambalažu koja se ne smatra prolaznom (mora imati KPD). |
            | `POVRATNA_NAKNADA_TROSAK`                              | `0`       | `HR:POVNAK` | Povratna naknada prikupljena za Fond za zaštitu okoliša — prolazna stavka. |
            | `NULTA_STOPA`                                          | `0`       | `HR:Z`    | Nulta stopa PDV-a. |
            | `NIJE_PREDMET_OPOREZIVANJA_JER_CIJELI_RACUN_NE_PODLJEZE` | `0`     | `HR:O`    | Artikl ne podliježe PDV-u jer cijeli račun ne podliježe (čl. 5. ZPDV). |
            | `NIJE_PREDMET_OPOREZIVANJA`                            | `0`       | `HR:N`    | Artikl ne podliježe PDV-u. |
            | `TURISTICKA_PRISTROJBA`                                | `0`       | `HR:N`    | Prolazna stavka — prikuplja se u ime turističke zajednice. |
            | `PROLAZNA_STAVKA`                                      | `0`       | `HR:N`    | Prolazne stavke koje poduzetnik plaća u ime i za račun kupca. |
            | `POSEBNI_POREZ_NA_MOTORNA_VOZILA`                      | `0`       | `HR:PPMV` | PPMV — sam trošak je prolazna stavka (čl. 33. st. 3.). |
          example: "OSLOBODENO"
          enum:
            - STANDARDNA_STOPA_25
            - STANDARDNA_STOPA_13
            - STANDARDNA_STOPA_5
            - OSLOBODENO
            - PRIJENOS_POREZNE_OBVEZE
            - POVRATNA_NAKNADA
            - POVRATNA_NAKNADA_TROSAK
            - NULTA_STOPA
            - NIJE_PREDMET_OPOREZIVANJA_JER_CIJELI_RACUN_NE_PODLJEZE
            - NIJE_PREDMET_OPOREZIVANJA
            - TURISTICKA_PRISTROJBA
            - PROLAZNA_STAVKA
            - POSEBNI_POREZ_NA_MOTORNA_VOZILA

    WebshopCustomerAddressModel:
      type: object
      properties:
        name:
          type: string
        address1:
          type: string
        address2:
          type: string
        city:
          type: string
        country:
          type: string
          description: ISO country code (HR, AT, DE, …). Required for eRačun (`HR` only).
        phone:
          type: string
        zipCode:
          type: string
        email:
          type: string
        vatNumber:
          type: string
        oib:
          type: string
          description: |
            Croatian tax ID — 11 digits, no `HR` prefix (`12345678901`, not `HR12345678901`).
            Required on `billingAddress` when sending eRačun.

            On `FISKALNI_RAČUN` / `FISKALNI_STORNO_RAČUN` it is reported to the Tax Authority as
            "OIB primatelja" **only when the order is a B2B purchase** — see the `b2bClient` flag
            on the order. For physical persons send `b2bClient: false` (or omit the OIB entirely)
            so a personal OIB is never reported.
        company:
          type: string

    WebshopEInvoiceRequest:
      type: object
      required: [businessProcess, invoiceTypeCode]
      description: eRačun metadata that accompanies an `E_RAČUN` order — UBL business process, type code, references, optional delivery dates, and optional business-unit override.
      properties:
        businessProcess:
          type: string
          description: |
            UBL business process. The most common:
              - `P1` — invoicing of contract-based deliveries (default)
              - `P3` — invoice on a self-managed order
              - `P4` — advance payment
              - `P5` — payment on the spot
              - `P9` — credit note / negative invoice
              - `P10` — corrective invoice (cancellation/correction)
          enum: [P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12]
        invoiceTypeCode:
          type: string
          description: |
            UBL invoice type code. The full set FIRA accepts (same list as the eInvoice modal in
            the FIRA UI):

              - `82` — Račun za mjerene usluge (metered service invoice)
              - `326` — Parcijalni račun (partial invoice)
              - `380` — Komercijalni račun (commercial invoice — the default)
              - `381` — Odobrenje (credit note)
              - `383` — Terećenje (debit note)
              - `384` — Korektivni račun (corrective invoice)
              - `386` — Račun za predujam (advance-payment invoice)
              - `387` — Račun za najam (rental invoice)
              - `389` — Samoizdavajući račun (self-billed invoice)
              - `394` — Račun za leasing (leasing invoice)
              - `623` — Špediterski račun (forwarding-agent invoice)
              - `780` — Račun za prijevoz tereta (freight invoice)
              - `875` — Djelomična građevinska faktura (partial construction invoice)
              - `876` — Djelomična završna građevinska faktura (partial final construction invoice)
              - `877` — Završna građevinska faktura (final construction invoice)

            Most webshop integrations only ever need `380` (regular sale) or `386` (deposit/advance
            payment, often paired with `businessProcess: P4`).
          example: "380"
        buyerReference:
          type: string
        projectReference:
          type: string
        orderReference:
          type: string
        documentReference:
          type: string
        documentReferenceDate:
          type: string
          format: date
        salesOrderReference:
          type: string
        receiptDocumentReference:
          type: string
        dispatchDocumentReference:
          type: string
        originatorDocumentReference:
          type: string
        invoiceReference:
          type: string
        deliveryDate:
          type: string
          format: date
        deliveryDateFrom:
          type: string
          format: date
        deliveryDateTo:
          type: string
          format: date
        taxPointDate:
          type: string
          format: date
        prepaid:
          type: number
          format: double
        businessUnit:
          type: object
          properties:
            id:
              type: string
            name:
              type: string

    InvoiceResponse:
      type: object
      description: |
        FIRA Invoice as persisted, returned after a successful `POST /order/custom`.
        Most fields mirror the internal Invoice model — only the fields most useful to
        webshop integrators are documented here.
      properties:
        id:
          type: integer
          format: int64
          description: FIRA-internal invoice ID. Use it for any follow-up FIRA API call.
        invoiceType:
          type: string
          enum: [PONUDA, RAČUN, RAČUN_ZA_PREDUJAM, STORNO_RAČUN, E_RAČUN, FISKALNI_RAČUN, FISKALNI_STORNO_RAČUN]
          description: Set to `E_RAČUN` only after Pondi successfully accepted the document.
        invoiceNumber:
          type: string
        invoicePaymentStatus:
          type: string
          enum: [PAID, DUE, OPEN, PARTIALLY_PAID, CANCELED, DRAFT, ACCEPTED, REJECTED]
          description: |
            Lifecycle / settlement state of the invoice. Webshop integrators typically only see
            the payment-flow values; the `DRAFT/ACCEPTED/REJECTED/CANCELED` quartet is mostly
            internal but exposed here for completeness because it can leak into responses on
            edge flows (e.g. when an invoice is created in DRAFT before fiscalization completes).

            | Value           | Meaning |
            |-----------------|---------|
            | `OPEN`          | Issued, not yet due. |
            | `DUE`           | Past `dueDate`, not (fully) paid. |
            | `PAID`          | Fully settled. |
            | `PARTIALLY_PAID`| Some but not all of the total has been received. |
            | `DRAFT`         | Saved but not yet finalized (no number assigned / not fiscalized). |
            | `ACCEPTED`      | Counterpart (e.g. ePoslovanje) accepted the document. |
            | `REJECTED`      | Counterpart rejected the document. |
            | `CANCELED`      | Voided (e.g. replaced by STORNO_RAČUN). |
        overall:
          type: number
          format: double
        inTotal:
          type: number
          format: double
        taxValue:
          type: number
          format: double
        einvoiceId:
          type: integer
          format: int64
          description: |
            Pondi/ePoslovanje document ID once the eRačun is accepted. **Present only when the
            request used `invoiceType: E_RAČUN` AND Pondi accepted the document** — omitted from
            the response otherwise.
        eInvoiceDocumentStatus:
          type: string
          enum: [PENDING, AWAITING_TRANSMISSION, SENT, DELIVERED, APPROVED, REJECTED, FULLY_PAID, PARTIALLY_PAID, DELIVERY_FAILED]
          description: |
            Latest Pondi status. Polled right after send; subsequent transitions arrive via FIRA's
            scheduled status poll. **Present only on the `E_RAČUN` path** — omitted otherwise.
        eInvoiceDocumentStatusMessage:
          type: string
          description: Human-readable form of `eInvoiceDocumentStatus`. **Present only on the `E_RAČUN` path.**
        einvoiceError:
          type: string
          description: |
            Populated when the eRačun could not be sent (validation or Pondi rejection). Mirrors
            the FIRA UI error catalog. **Present only when `invoiceType: E_RAČUN` was used and
            something failed** — omitted on success and on every non-eRačun flow.

    ErrorDetails:
      type: object
      properties:
        timestamp:
          type: string
        message:
          type: string
        details:
          type: string
        validationErrors:
          type: array
          items:
            $ref: '#/components/schemas/ViolationError'

    ViolationError:
      type: object
      properties:
        fieldName:
          type: string
        message:
          type: string
        rejectedValue:
          type: string
