openapi: 3.1.0 jsonSchemaDialect: https://json-schema.org/draft/2020-12/schema info: title: BokaPOS API version: 1.0.0 license: name: Proprietary, BOKA GROUP DOO identifier: LicenseRef-BOKA-Proprietary description: | High-level, multi-tenant fiscalization API operated by BOKA GROUP. The initial V-PFR-only release is restricted to electronic and other distance commerce. This contract intentionally does not expose the raw V-PFR request contract. Every operation declares `x-boka-implementation` as either `implemented` or `planned`; executable contract specifications require the implemented route/method set to match the runtime exactly. servers: - url: https://api.bokapos.rs description: | The single BokaPOS deployment. Sandbox and production are not separate hosts: they are distinguished by the environment tag on the OAuth client credential and on the security element. Moving to production changes the credential, never the base URL. Every read and write is scoped to the caller's environment. A machine credential sees exactly the taxpayers, premises, security elements and documents of its own environment; an object of the other environment does not exist for it (404), and setup it registers belongs to its own environment. Portal users see both environments unless the organization hides its sandbox data (GET /v1/organization-settings), which happens automatically once a production element or credential is active. security: - oauth2: [] tags: - name: Runtime description: Authenticated build and adapter readiness visible to integrations. - name: Fiscal documents description: Synchronous fiscalization and the searchable electronic journal. - name: Advance workflows description: Server-managed Advance Sale, Advance Refund, and final Sale chains. - name: Refund workflows description: Full or partial refunds and conditional cash-refund copies. - name: Proforma and Training workflows description: Bounded, server-managed Proforma and Training issuance and reference chains. - name: Receipt delivery description: Branded electronic delivery after successful fiscalization. - name: Operations description: Status and recovery visibility for fiscal and background work. - name: Catalogue description: Persistent product, service, price, GTIN, and tax-label management. - name: Receipt branding description: Versioned taxpayer receipt presentation with optional premise overrides. - name: Operator preferences description: Per-user cashier defaults for the signed-in customer operator. - name: Organization settings description: Organization-wide portal settings for customer humans, currently the sandbox data visibility. - name: Fiscal configuration description: PFR/SUF-provided tax configuration exposed read-only to integrations. - name: Taxpayers and premises description: Organization-scoped taxpayer and business-premise administration. - name: Human access description: Organization-scoped customer membership and accepted role administration. - name: BOKA control plane description: BOKA-only staff membership and platform authority administration. - name: Security elements description: Secure onboarding and lifecycle management for premise-bound PFX credentials. - name: BOKA sandbox pool description: Staff-only pool of BOKA-owned sandbox elements assigned to customer organizations. - name: Billing description: Customer-visible licence state and the Owner billing overview; payment never happens on the platform. - name: BOKA licensing description: Root-only issuance of organization licences and recording of invoices issued outside the platform. paths: /v1/runtime: get: tags: [Runtime] operationId: getRuntime x-boka-implementation: implemented summary: Read the authenticated tenant and fiscal-adapter readiness security: - oauth2: [tenant:read] - humanOidc: [tenant:read] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' responses: '200': description: Runtime context content: application/json: schema: { $ref: '#/components/schemas/RuntimeInfo' } /v1/operator-preferences: get: tags: [Operator preferences] operationId: getOperatorPreferences x-boka-implementation: implemented summary: Read cashier defaults for the signed-in customer user security: - humanOidc: [tenant:read] responses: '200': description: Current cashier defaults; cashierId is null until configured content: application/json: schema: { $ref: '#/components/schemas/OperatorPreference' } put: tags: [Operator preferences] operationId: putOperatorPreferences x-boka-implementation: implemented summary: Save cashier defaults for the signed-in customer user security: - humanOidc: [tenant:read] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/OperatorPreferenceWrite' } responses: '200': description: Saved cashier defaults content: application/json: schema: { $ref: '#/components/schemas/OperatorPreference' } '422': { $ref: '#/components/responses/ValidationError' } /v1/organization-settings: get: tags: [Organization settings] operationId: getOrganizationSettings x-boka-implementation: implemented summary: Read the organization's sandbox data visibility and its effective state description: | Customer humans only; machine credentials are refused because their view is fixed to their own environment at issuance. The effective state is what the signed-in user currently gets: with the default `auto` setting sandbox data is shown until the organization holds an active production security element or an active production API credential, and hidden from then on. security: - humanOidc: [tenant:read] responses: '200': description: Current organization settings content: application/json: schema: { $ref: '#/components/schemas/OrganizationSettings' } put: tags: [Organization settings] operationId: putOrganizationSettings x-boka-implementation: implemented summary: Change the organization's sandbox data visibility description: | Owner or Administrator only. `shown` keeps sandbox data visible after go-live, `hidden` hides it regardless of readiness, `auto` restores the automatic rule. Every change is recorded as the customer audit event `organization-settings.sandbox-data-visibility-updated`. Machine credentials are never affected by this setting. security: - humanOidc: [tenant:write] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/OrganizationSettingsWrite' } responses: '200': description: Saved organization settings content: application/json: schema: { $ref: '#/components/schemas/OrganizationSettings' } '422': $ref: '#/components/responses/ValidationError' /v1/fiscal-documents: post: tags: [Fiscal documents] operationId: createFiscalDocument x-boka-implementation: implemented summary: Fiscalize a document synchronously description: | Accepts a business-level command and succeeds only after a signed V-PFR result has been durably recorded. A 503 response is not a fiscal receipt and may be retried with the same Idempotency-Key. Refund commands are rejected here and must use a server-managed workflow. Advance, Proforma, and Training chains must use their dedicated workflow APIs. An optional unitPriceBeforeDiscount is a Boka-local immutable display/audit fact; unitPrice is always the final reduced gross price sent to V-PFR. No discount field is invented in the supplier request. Repeating the identical command under the same Idempotency-Key after a 201 answers 200 with the stored document and never fiscalizes a second time; only the first 201 with fiscalized=true is the issuing of a receipt. security: - oauth2: [fiscal:write] - humanOidc: [fiscal:write] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FiscalDocumentCreate' responses: '200': description: Idempotent replay of the already fiscalized document under the same Idempotency-Key; no new receipt was issued content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '201': description: Fiscalized document content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/PfrUnavailable' get: tags: [Fiscal documents] operationId: listFiscalDocuments x-boka-implementation: implemented summary: Search the tenant electronic journal with stable keyset pagination description: | Returns durable fiscal-operation state ordered by createdAt and id, both descending. createdFrom and pfrFrom are inclusive; createdTo and pfrTo are exclusive. The opaque cursor continues the same ordering. A journal entry whose fiscalized value is false is not a fiscal receipt. Scoped to the caller's environment: a machine credential never sees documents of the other environment, and fetching one by id is 404. security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - $ref: '#/components/parameters/TaxpayerFilter' - $ref: '#/components/parameters/PremiseFilter' - $ref: '#/components/parameters/FiscalDocumentFilter' - $ref: '#/components/parameters/FiscalDocumentStatusFilter' - $ref: '#/components/parameters/InvoiceTypeFilter' - $ref: '#/components/parameters/TransactionTypeFilter' - $ref: '#/components/parameters/CreatedFromFilter' - $ref: '#/components/parameters/CreatedToFilter' - $ref: '#/components/parameters/PfrFromFilter' - $ref: '#/components/parameters/PfrToFilter' - $ref: '#/components/parameters/IdempotencyKeyFilter' - $ref: '#/components/parameters/ClientReferenceFilter' - $ref: '#/components/parameters/PfrNumberFilter' - $ref: '#/components/parameters/CashierIdFilter' - $ref: '#/components/parameters/BuyerIdFilter' - $ref: '#/components/parameters/FiscalDocumentSearch' - $ref: '#/components/parameters/FiscalDocumentCursor' - $ref: '#/components/parameters/PageSize' responses: '200': description: Page of fiscal documents content: application/json: schema: $ref: '#/components/schemas/FiscalDocumentPage' '400': $ref: '#/components/responses/ValidationError' '422': $ref: '#/components/responses/ValidationError' /v1/fiscal-documents/export: get: tags: [Fiscal documents] operationId: exportFiscalDocuments x-boka-implementation: implemented summary: Export the filtered tenant electronic journal as bounded deterministic CSV description: | Applies the same tenant-scoped filters and descending createdAt/id ordering as the electronic journal. The export contains at most 10,000 safe projection rows and never includes the raw fiscal command, caller metadata, complete PFR response, official journal text, verification URL, signature data, or secret material. Text cells that could be interpreted as spreadsheet formulas are neutralized. If more than 10,000 rows match, the request fails without returning a partial file. security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - $ref: '#/components/parameters/TaxpayerFilter' - $ref: '#/components/parameters/PremiseFilter' - $ref: '#/components/parameters/FiscalDocumentFilter' - $ref: '#/components/parameters/FiscalDocumentStatusFilter' - $ref: '#/components/parameters/InvoiceTypeFilter' - $ref: '#/components/parameters/TransactionTypeFilter' - $ref: '#/components/parameters/CreatedFromFilter' - $ref: '#/components/parameters/CreatedToFilter' - $ref: '#/components/parameters/PfrFromFilter' - $ref: '#/components/parameters/PfrToFilter' - $ref: '#/components/parameters/IdempotencyKeyFilter' - $ref: '#/components/parameters/ClientReferenceFilter' - $ref: '#/components/parameters/PfrNumberFilter' - $ref: '#/components/parameters/CashierIdFilter' - $ref: '#/components/parameters/BuyerIdFilter' - $ref: '#/components/parameters/FiscalDocumentSearch' responses: '200': description: Complete filtered journal CSV in stable descending order headers: Boka-Export-Schema-Version: schema: { type: string, const: boka-fiscal-journal-csv-v2 } Content-Disposition: schema: { type: string } description: Attachment filename for the journal CSV. content: text/csv: schema: { type: string } '422': description: Invalid filters or more than 10,000 matching rows content: application/problem+json: schema: { $ref: '#/components/schemas/ValidationProblem' } application/json: schema: { $ref: '#/components/schemas/ErrorCode' } /v1/fiscal-documents/{fiscalDocumentId}/copies: post: tags: [Fiscal documents] operationId: createFiscalDocumentCopy x-boka-implementation: implemented summary: Issue an official Копија of a fiscalized document description: | Issues a Copy (Копија Продаја or Копија Рефундација) of a fiscalized Normal or Advance document. The copy is built on the server from the stored original: the same items, payments, buyer identification and stored buyer lines, referenced to the original's PFR number and time, with the cashier given here. It is signed by the V-PFR as a new document and prints ОВО НИЈЕ ФИСКАЛНИ РАЧУН; a Копија Рефундација prints the customer signature line. Copy, Proforma and Training documents cannot be copied (422 COPY_SOURCE_NOT_COPYABLE), nor can a document that is not fiscalized (422 COPY_SOURCE_NOT_FISCALIZED). Same idempotency and outcome rules as createFiscalDocument. security: - oauth2: [fiscal:write] - humanOidc: [fiscal:write] parameters: - $ref: '#/components/parameters/FiscalDocumentId' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/FiscalDocumentCopyCreate' responses: '200': description: Idempotent replay of the already fiscalized copy under the same Idempotency-Key; no new document was issued content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '201': description: Fiscalized copy content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '404': description: The original does not exist in this organization (code FISCAL_DOCUMENT_NOT_FOUND) '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/PfrUnavailable' /v1/fiscal-documents/{fiscalDocumentId}: get: tags: [Fiscal documents] operationId: getFiscalDocument x-boka-implementation: implemented summary: Retrieve one fiscal document security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - $ref: '#/components/parameters/FiscalDocumentId' responses: '200': description: Fiscal document content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '404': $ref: '#/components/responses/NotFound' /v1/fiscal-documents/turnover-report: get: tags: [Fiscal documents] operationId: getTurnoverReport x-boka-implementation: implemented summary: Report immutable Normal and Advance turnover for a selected PFR-time period description: | Aggregates only tenant-scoped FISCALIZED Normal (Promet) and Advance (Avans) receipts whose authoritative PFR signing time is within the inclusive pfrFrom and exclusive pfrTo bounds. Sale and Refund amounts remain separate positive totals; the API does not infer a net value. Payment data comes from the hash-verified canonical request and tax bases/tax amounts come from the hash-verified complete PFR response. This is a Boka-local immutable report, not the SUF portal daily report; it does not claim SUF-only knowledge about missing or scanned receipts. security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - name: taxpayerId in: query required: true schema: { type: string, format: uuid } - name: businessPremiseId in: query required: true schema: { type: string, format: uuid } - name: pfrFrom in: query required: true description: Inclusive lower bound for authoritative PFR signing time. Any RFC 3339 offset is accepted (Z, +00:00, +02:00) and compared as an instant. schema: { type: string, format: date-time } - name: pfrTo in: query required: true description: Exclusive upper bound for authoritative PFR signing time. Any RFC 3339 offset is accepted and compared as an instant. schema: { type: string, format: date-time } responses: '200': description: Immutable local turnover report content: application/json: schema: { $ref: '#/components/schemas/TurnoverReport' } '404': $ref: '#/components/responses/NotFound' '409': description: A selected receipt failed source-integrity validation or totals exceeded the supported decimal range content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } '422': $ref: '#/components/responses/ValidationError' /v1/fiscal-documents/turnover-report/export: get: tags: [Fiscal documents] operationId: exportTurnoverReport x-boka-implementation: implemented summary: Export the immutable local turnover report as deterministic CSV description: | Reuses the exact same hash-reverified BOKA_LOCAL_IMMUTABLE_PFR report result as the JSON operation. The normalized CSV contains SUMMARY, FIRST_DOCUMENT, LAST_DOCUMENT, PAYMENT_TOTAL, and TAX_TOTAL record types. Sale and Refund remain separate positive amounts. This is not a SUF portal report and contains no official receipt journal or raw PFR payload. security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - name: taxpayerId in: query required: true schema: { type: string, format: uuid } - name: businessPremiseId in: query required: true schema: { type: string, format: uuid } - name: pfrFrom in: query required: true description: Inclusive lower bound for authoritative PFR signing time. Any RFC 3339 offset is accepted (Z, +00:00, +02:00) and compared as an instant. schema: { type: string, format: date-time } - name: pfrTo in: query required: true description: Exclusive upper bound for authoritative PFR signing time. Any RFC 3339 offset is accepted and compared as an instant. schema: { type: string, format: date-time } responses: '200': description: Complete normalized turnover-report CSV headers: Boka-Export-Schema-Version: schema: { type: string, const: boka-turnover-report-csv-v1 } Content-Disposition: schema: { type: string } description: Attachment filename containing the exact report scope and UTC bounds. content: text/csv: schema: { type: string } '404': $ref: '#/components/responses/NotFound' '409': description: A selected receipt failed source-integrity validation or totals exceeded the supported decimal range content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } '422': $ref: '#/components/responses/ValidationError' /v1/fiscal-documents/{fiscalDocumentId}/representations/{representationFormat}: get: tags: [Fiscal documents] operationId: getFiscalDocumentRepresentation x-boka-implementation: implemented summary: Retrieve one deterministic representation of a fiscalized receipt description: | Generates the selected representation only from a tenant-scoped FISCALIZED operation after rechecking the canonical request and complete original PFR response hashes. official-text is the exact stored supplier journal. canonical-json omits caller metadata and opaque encrypted/signature values. The direct PDF and PNG preview layouts keep the verification QR at 45 mm. Fixed media use only bundled local rendering resources and include no network-loaded content. No representation is generated for an unresolved, rejected, unavailable, corrupt, or non-fiscalized operation. security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - $ref: '#/components/parameters/FiscalDocumentId' - $ref: '#/components/parameters/ReceiptRepresentationFormat' responses: '200': description: Selected immutable receipt representation headers: ETag: schema: { type: string } description: SHA-256 entity tag of the generated representation bytes. Boka-Receipt-Representation-Version: schema: { type: string, const: boka-receipt-representation-v21 } Boka-Receipt-Source-Sha256: schema: { type: string, pattern: '^[0-9a-f]{64}$' } description: Stable hash joining the verified canonical request and original PFR-response hashes. Boka-Receipt-Branding-Sha256: schema: { type: string, pattern: '^[0-9a-f]{64}$' } description: Present only for a receipt that captured an immutable customer-branding revision. content: application/json: schema: { $ref: '#/components/schemas/ReceiptCanonicalPackage' } text/plain: schema: type: string description: Exact official V-PFR journal with no Boka-added text. image/svg+xml: schema: type: string description: QR encoding the exact stored HTTPS verification URL with a quiet zone. application/pdf: schema: type: string format: binary description: Direct A4, 80 mm, or 58 mm PDF selected by representationFormat. image/png: schema: type: string format: binary description: Direct 80 mm receipt preview generated from the immutable package. '404': $ref: '#/components/responses/NotFound' '409': description: Receipt unavailable or stored source integrity validation failed content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } /v1/refund-workflows: post: tags: [Refund workflows] operationId: createRefundWorkflow x-boka-implementation: implemented summary: Refund an explicitly identified Normal Sale description: | Supports full and partial Normal Refunds. Boka-issued originals are resolved only within the exact taxpayer and premise, and cumulative quantities are prevented from exceeding each stored original line. When any returned payment is cash, Boka automatically issues the required Copy Refundation and renders its customer-signature line. This first bounded workflow accepts only a Boka-stored original; external originals remain unsupported because their cumulative returned quantity cannot be independently proven. Normal fiscal traffic remains governed by the same disabled-by-default provider and uncertain-outcome safeguards. security: - oauth2: [refund:write] - humanOidc: [refund:write] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/RefundWorkflowCreate' responses: '200': description: Idempotent replay of a completed refund workflow content: application/json: schema: $ref: '#/components/schemas/RefundWorkflow' '201': description: Completed refund workflow content: application/json: schema: $ref: '#/components/schemas/RefundWorkflow' '409': $ref: '#/components/responses/IdempotencyConflict' '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' '503': description: The refund workflow is incomplete; no missing fiscal step is claimed as issued content: application/json: schema: { $ref: '#/components/schemas/RefundWorkflow' } /v1/proforma-training-workflows: post: tags: [Proforma and Training workflows] operationId: createProformaTrainingWorkflow x-boka-implementation: implemented summary: Issue one bounded Proforma or Training document description: | Accepts only Proforma or Training transactions through the dedicated workflow scope. Training Sale forbids a reference. Training Refund must reference an exact Boka-issued Training Sale. Proforma Sale may omit a reference or reference a Boka-issued Proforma Sale or Refund; Proforma Refund must reference a Boka-issued Proforma Sale. Refunds must exactly reproduce every source item and payment, identify the buyer, and only one unresolved or completed full refund may reserve a source. External and legacy references remain outside this bounded workflow. An unknown PFR outcome is terminal for automatic submission and an idempotent replay never creates a second fiscal request. security: - oauth2: [proforma-training:write] - humanOidc: [proforma-training:write] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProformaTrainingWorkflowCreate' responses: '200': description: Idempotent replay of a completed workflow content: application/json: schema: { $ref: '#/components/schemas/ProformaTrainingWorkflow' } '201': description: Fiscalized Proforma or Training document content: application/json: schema: { $ref: '#/components/schemas/ProformaTrainingWorkflow' } '202': description: The request is durably reserved but has not been submitted content: application/json: schema: { $ref: '#/components/schemas/ProformaTrainingWorkflow' } '404': $ref: '#/components/responses/NotFound' '409': description: Idempotency, source-reservation, or terminal workflow conflict content: application/json: schema: oneOf: - $ref: '#/components/schemas/ProformaTrainingWorkflowConflict' - $ref: '#/components/schemas/ProformaTrainingWorkflow' '422': description: The command violates a Boka or fiscal rule, or PFR definitively rejected the reserved operation content: application/problem+json: schema: { $ref: '#/components/schemas/ValidationProblem' } application/json: schema: oneOf: - $ref: '#/components/schemas/ErrorCode' - $ref: '#/components/schemas/ProformaTrainingWorkflow' '503': description: No fiscal receipt is claimed; the workflow is unavailable or its outcome is unresolved content: application/json: schema: oneOf: - $ref: '#/components/schemas/ProformaTrainingWorkflow' - $ref: '#/components/schemas/FiscalUnavailableError' /v1/proforma-training-workflows/{proformaTrainingWorkflowId}: get: tags: [Proforma and Training workflows] operationId: getProformaTrainingWorkflow x-boka-implementation: implemented summary: Read a persisted Proforma or Training workflow security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - $ref: '#/components/parameters/ProformaTrainingWorkflowId' responses: '200': description: Persisted workflow and fiscal-document state content: application/json: schema: { $ref: '#/components/schemas/ProformaTrainingWorkflow' } '404': $ref: '#/components/responses/NotFound' /v1/products/{productId}: get: tags: [Catalogue] operationId: getProduct x-boka-implementation: implemented summary: Retrieve one tenant-scoped catalogue entry security: - oauth2: [catalogue:read] - humanOidc: [catalogue:read] parameters: - $ref: '#/components/parameters/ProductId' - $ref: '#/components/parameters/StaffSupportContextHeader' responses: '200': description: Product content: application/json: schema: { $ref: '#/components/schemas/Product' } '404': $ref: '#/components/responses/NotFound' put: tags: [Catalogue] operationId: updateProduct x-boka-implementation: implemented summary: Replace editable catalogue details or active status description: The owning taxpayer is immutable; deactivation preserves historical receipt snapshots. security: - oauth2: [catalogue:write] - humanOidc: [catalogue:write] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' - $ref: '#/components/parameters/ProductId' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ProductUpdate' } responses: '200': description: Updated product content: application/json: schema: { $ref: '#/components/schemas/Product' } '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/products/import: post: tags: [Catalogue] operationId: importProducts x-boka-implementation: implemented summary: Validate and upsert a bounded taxpayer catalogue CSV description: The import is all-or-nothing, accepts at most 1,000 rows or 5 MB, and uses `|` between tax labels. security: - oauth2: [catalogue:write] - humanOidc: [catalogue:write] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' - name: taxpayerId in: query required: true schema: { type: string, format: uuid } requestBody: required: true content: text/csv: schema: { type: string } responses: '200': description: Import counts content: application/json: schema: { $ref: '#/components/schemas/ProductImportResult' } '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/products/export: get: tags: [Catalogue] operationId: exportProducts x-boka-implementation: implemented summary: Export one taxpayer catalogue as deterministic CSV security: - oauth2: [catalogue:read] - humanOidc: [catalogue:read] parameters: - name: taxpayerId in: query required: true schema: { type: string, format: uuid } - $ref: '#/components/parameters/StaffSupportContextHeader' responses: '200': description: Catalogue CSV using invariant decimals and `|`-separated tax labels content: text/csv: schema: { type: string } '404': $ref: '#/components/responses/NotFound' /v1/advance-cases: get: tags: [Advance workflows] operationId: listAdvanceCases x-boka-implementation: implemented summary: List advance chains for operator selection description: Returns tenant-scoped advance chains in most-recently-updated order so an operator can continue a chain without handling an internal identifier. Filters narrow by state, reference text and last change. security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - name: taxpayerId in: query schema: { type: string, format: uuid } - name: businessPremiseId in: query schema: { type: string, format: uuid } - name: state in: query description: OPEN is every chain still in progress (including unknown-outcome states the operator must resolve); CLOSED and FAILED are terminal. schema: { type: string, enum: [OPEN, CLOSED, FAILED] } - name: search in: query description: Case-insensitive substring of the client reference. schema: { type: string, maxLength: 200 } - name: updatedFrom in: query description: Only chains changed at or after this instant. Any RFC 3339 offset is accepted and compared as an instant. schema: { type: string, format: date-time } - name: pageSize in: query schema: { type: integer, minimum: 1, maximum: 50, default: 25 } responses: '200': description: Recent advance chains content: application/json: schema: { $ref: '#/components/schemas/AdvanceCasePage' } '422': $ref: '#/components/responses/ValidationError' post: tags: [Advance workflows] operationId: createAdvanceCase x-boka-implementation: implemented summary: Create a server-managed advance chain description: | Creates a Boka-owned, same-taxpayer/same-premise chain. External and pre-eFiscalization starting references are deliberately outside this bounded workflow. security: - oauth2: [advance:write] - humanOidc: [advance:write] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AdvanceCaseCreate' responses: '201': description: Advance case content: application/json: schema: $ref: '#/components/schemas/AdvanceCase' '200': description: Idempotent replay of the same case creation content: application/json: schema: $ref: '#/components/schemas/AdvanceCase' '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' /v1/advance-cases/{advanceCaseId}: get: tags: [Advance workflows] operationId: getAdvanceCase x-boka-implementation: implemented summary: Retrieve an advance chain and recovery state security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - $ref: '#/components/parameters/AdvanceCaseId' responses: '200': description: Advance case content: application/json: schema: $ref: '#/components/schemas/AdvanceCase' '404': $ref: '#/components/responses/NotFound' /v1/advance-cases/{advanceCaseId}/payments: post: tags: [Advance workflows] operationId: fiscalizeAdvancePayment x-boka-implementation: implemented summary: Fiscalize the next Advance Sale in a chain description: | Boka references the immediately preceding fiscalized Advance Sale stored in this exact case. Any unresolved prior outcome blocks another payment, and a changed idempotent replay is rejected. security: - oauth2: [advance:write] - humanOidc: [advance:write] parameters: - $ref: '#/components/parameters/AdvanceCaseId' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AdvancePaymentCreate' responses: '201': description: Fiscalized Advance Sale content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '200': description: Idempotent replay of the fiscalized Advance Sale content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/PfrUnavailable' /v1/advance-cases/{advanceCaseId}/cancellations: post: tags: [Advance workflows] operationId: cancelAdvanceSale x-boka-implementation: implemented summary: Cancel the latest erroneous Advance Sale with a standalone Advance Refund description: | Issues an Advance Refund that repeats the complete latest Advance Sale of the open case, references it, and carries the seller's own PIB as the buyer (10:), as the official cancellation procedure prescribes. The case stays open; the next Advance Sale chains to the sale before the cancelled one. Only the latest fiscalized Advance Sale can be cancelled. security: - oauth2: [advance:write] - humanOidc: [advance:write] parameters: - $ref: '#/components/parameters/AdvanceCaseId' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AdvanceCancellationCreate' responses: '201': description: Fiscalized Advance Refund cancelling the Advance Sale content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '200': description: Idempotent replay of the fiscalized Advance Refund content: application/json: schema: $ref: '#/components/schemas/FiscalDocument' '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/PfrUnavailable' /v1/advance-cases/{advanceCaseId}/close: post: tags: [Advance workflows] operationId: closeAdvanceCase x-boka-implementation: implemented summary: Issue the Advance Refund and final Normal Sale description: | This is a recoverable two-document workflow. If the Advance Refund is fiscalized but the final Sale is confirmed not submitted, the response exposes the pending state and an idempotent replay retries only that stored final operation. An unknown outcome blocks instead of retrying. If the V-PFR rejects the Advance Refund, the case is `FAILED` with the rejection code, the refund document is `REJECTED`, and the final Sale reserved with it is never sent: it is closed as `NOT_FISCALIZED` with `ADVANCE_REFUND_NOT_FISCALIZED` and `retryable: false`. Open a new case to try again; a replay of the failed close re-drives nothing. This bounded operation closes the whole Boka-owned chain; partial and legacy/external realization remain unsupported. The Advance Refund is not a customer-delivery document. security: - oauth2: [advance:close] - humanOidc: [advance:close] parameters: - $ref: '#/components/parameters/AdvanceCaseId' - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/AdvanceCaseClose' responses: '201': description: Closed advance case content: application/json: schema: $ref: '#/components/schemas/AdvanceCloseResult' '200': description: Idempotent replay of an already closed case content: application/json: schema: $ref: '#/components/schemas/AdvanceCloseResult' '202': description: Advance Refund is fiscalized; final Sale remains pending content: application/json: schema: $ref: '#/components/schemas/AdvanceCloseResult' '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/PfrUnavailable' /v1/receipt-deliveries: get: tags: [Receipt delivery] operationId: listReceiptDeliveries x-boka-implementation: implemented summary: List the electronic deliveries of one fiscal document security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - name: fiscalDocumentId in: query required: true schema: { type: string, format: uuid } responses: '200': description: Deliveries in creation order (at most 100) content: application/json: schema: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/ReceiptDelivery' } '422': $ref: '#/components/responses/ValidationError' post: tags: [Receipt delivery] operationId: createReceiptDelivery x-boka-implementation: implemented summary: Email a fiscalized receipt to the customer description: | Queues one email delivery of one fiscalized document from the platform mailbox, in the taxpayer's chosen language, with the official verification URL as an active link and the A4 PDF attached. Delivery is queued only after the signed result is durably stored, only when the taxpayer enabled email delivery in its settings (422 RECEIPT_DELIVERY_DISABLED otherwise; the taxpayer then delivers through its own system), and only while the platform transport is configured (503 RECEIPT_DELIVERY_UNAVAILABLE otherwise). An advance refund, and a copy of one, are refused with 422 RECEIPT_DELIVERY_DOCUMENT_NOT_ISSUED_TO_BUYER: the official guideline never issues that document to the buyer, so send the final receipt of the advance case instead. Every other kind is delivered. A delivery outcome is never evidence of fiscalization. Tenant branding stays outside the fiscal receipt boundary. security: - oauth2: [fiscal:write] - humanOidc: [fiscal:write] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReceiptDeliveryCreate' responses: '202': description: Delivery queued headers: Location: schema: { type: string } content: application/json: schema: $ref: '#/components/schemas/ReceiptDelivery' '200': description: Idempotent replay content: application/json: schema: $ref: '#/components/schemas/ReceiptDelivery' '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' '503': description: The platform mail transport is not configured /v1/receipt-deliveries/{receiptDeliveryId}: get: tags: [Receipt delivery] operationId: getReceiptDelivery x-boka-implementation: implemented summary: Read one electronic delivery security: - oauth2: [fiscal:read] - humanOidc: [fiscal:read] parameters: - name: receiptDeliveryId in: path required: true schema: { type: string, format: uuid } responses: '200': description: Delivery content: application/json: schema: $ref: '#/components/schemas/ReceiptDelivery' '404': $ref: '#/components/responses/NotFound' /v1/receipt-delivery-settings/taxpayers/{taxpayerId}: get: tags: [Receipt delivery] operationId: getReceiptDeliverySettings x-boka-implementation: implemented summary: Read how a taxpayer delivers receipts electronically description: | Absent settings mean disabled: the taxpayer delivers through its own system and the API refuses to queue platform email. security: - humanOidc: [tenant:read] parameters: - $ref: '#/components/parameters/TaxpayerId' responses: '200': description: Settings content: application/json: schema: $ref: '#/components/schemas/ReceiptDeliverySettings' '404': $ref: '#/components/responses/NotFound' put: tags: [Receipt delivery] operationId: putReceiptDeliverySettings x-boka-implementation: implemented summary: Set how a taxpayer delivers receipts electronically security: - humanOidc: [tenant:write] parameters: - $ref: '#/components/parameters/TaxpayerId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReceiptDeliverySettingsWrite' responses: '200': description: Saved settings content: application/json: schema: $ref: '#/components/schemas/ReceiptDeliverySettings' '404': $ref: '#/components/responses/NotFound' '409': description: The taxpayer is closed '422': $ref: '#/components/responses/ValidationError' /v1/receipt-delivery-settings/taxpayers/{taxpayerId}/previews: post: tags: [Receipt delivery] operationId: previewReceiptDeliveryMessage x-boka-implementation: implemented summary: Render a sample receipt e-mail with draft settings description: | Renders the HTML of the message exactly as the customization would send it, with clearly labelled sample fiscal values and the taxpayer's current receipt-branding logo. Stores nothing, sends nothing and never produces a fiscal document. Inline images are embedded as data URIs so the portal can show the page directly. security: - humanOidc: [tenant:read] parameters: - $ref: '#/components/parameters/TaxpayerId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReceiptDeliveryPreviewRequest' responses: '200': description: The sample message content: text/html: schema: { type: string } '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' /v1/receipt-delivery-settings/taxpayers/{taxpayerId}/test-messages: post: tags: [Receipt delivery] operationId: sendReceiptDeliveryTestMessage x-boka-implementation: implemented summary: Send a sample receipt e-mail to one address description: | Owner or Administrator. Sends the sample message built from the saved settings to the given address, without attachments and without the internal copy. It is not a receipt delivery and is never queued or recorded as one. Refused with 503 while the platform transport is not configured. security: - humanOidc: [tenant:write] parameters: - $ref: '#/components/parameters/TaxpayerId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ReceiptDeliveryTestMessageRequest' responses: '202': description: The sample was handed to the mail transport content: application/json: schema: type: object additionalProperties: false required: [recipient, subject] properties: recipient: { type: string } subject: { type: string } '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' '502': description: The mail transport refused the sample '503': description: The platform mail transport is not configured /v1/operations/{operationId}: get: tags: [Operations] operationId: getOperation x-boka-implementation: implemented summary: Inspect one tenant-scoped fiscal operation description: | Reads Boka's durable PostgreSQL operation state without contacting V-PFR, opening a security element, recovering an outcome, or retrying a fiscal command. A false fiscalized value is never a receipt. security: - oauth2: [operations:read] - humanOidc: [operations:read] parameters: - $ref: '#/components/parameters/OperationId' responses: '200': description: Operation content: application/json: schema: $ref: '#/components/schemas/Operation' '404': $ref: '#/components/responses/NotFound' /v1/receipt-branding/assets: get: tags: [Receipt branding] operationId: listReceiptBrandingAssets x-boka-implementation: implemented summary: List immutable sanitized logo assets for one taxpayer security: - humanOidc: [tenant:read] parameters: - name: taxpayerId in: query required: true schema: { type: string, format: uuid } responses: '200': description: Logo assets content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingAssetPage' } '404': { $ref: '#/components/responses/NotFound' } post: tags: [Receipt branding] operationId: uploadReceiptBrandingAsset x-boka-implementation: implemented summary: Decode, sanitize, and append a customer logo asset description: | Owner and Administrator only. Accepts PNG or WebP, decodes with strict byte/dimension/pixel limits, strips metadata by re-encoding to PNG, and creates a monochrome thermal derivative. SVG, animation, external URLs, and mutable replacement are not accepted. security: - humanOidc: [tenant:write] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingAssetUpload' } responses: '201': description: Immutable sanitized logo asset content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingAsset' } '404': { $ref: '#/components/responses/NotFound' } '422': { $ref: '#/components/responses/ValidationError' } /v1/receipt-branding/assets/{assetId}/content: get: tags: [Receipt branding] operationId: getReceiptBrandingAssetContent x-boka-implementation: implemented summary: Read one tenant-private immutable sanitized PNG asset security: - humanOidc: [tenant:read] parameters: - name: assetId in: path required: true schema: { type: string, format: uuid } - name: variant in: query schema: { type: string, enum: [color, thermal], default: color } responses: '200': description: Sanitized PNG bytes headers: ETag: schema: { type: string } content: image/png: schema: { type: string, format: binary } '404': { $ref: '#/components/responses/NotFound' } '422': { $ref: '#/components/responses/ValidationError' } /v1/receipt-branding/taxpayers/{taxpayerId}: get: tags: [Receipt branding] operationId: getTaxpayerReceiptBranding x-boka-implementation: implemented summary: Read the current taxpayer-default receipt-branding revision security: - humanOidc: [tenant:read] parameters: - name: taxpayerId in: path required: true schema: { type: string, format: uuid } responses: '200': description: Taxpayer receipt branding, configured or empty content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingProfile' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [Receipt branding] operationId: putTaxpayerReceiptBranding x-boka-implementation: implemented summary: Append and select a taxpayer-default receipt-branding revision security: - humanOidc: [tenant:write] parameters: - name: taxpayerId in: path required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingProfileWrite' } responses: '200': description: Newly selected immutable branding revision content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingProfile' } '404': { $ref: '#/components/responses/NotFound' } '409': { $ref: '#/components/responses/ResourceConflict' } '422': { $ref: '#/components/responses/ValidationError' } /v1/receipt-branding/taxpayers/{taxpayerId}/previews: post: tags: [Receipt branding] operationId: previewTaxpayerReceiptBranding x-boka-implementation: implemented summary: Render a receipt-branding preview from a draft, without saving it description: | Renders the supplied draft branding onto a fixed sample receipt and returns the HTML. A branding change cannot be previewed on a real receipt: a receipt is bound to the immutable branding revision that was in force when it was issued, so the most recent document would show the branding it was issued with and would never reflect an edit. The preview is drawn through the same renderer, stylesheet and layout a real receipt uses, so it cannot drift away from what the printer produces. Nothing is reserved, stored or counted. Every value in the sample is unusable as a fiscal artefact: the invoice number is not a well-formed PFR number, the verification URL is under the reserved `.invalid` top-level domain and cannot resolve, and the page carries a banner naming it a sample. This operation is not a fiscal document route and never produces evidence of fiscalization. security: - humanOidc: [tenant:read] parameters: - name: taxpayerId in: path required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingProfileWrite' } responses: '200': description: Sample receipt rendered with the draft branding content: text/html: schema: { type: string } '404': { $ref: '#/components/responses/NotFound' } '422': { $ref: '#/components/responses/ValidationError' } /v1/receipt-branding/taxpayers/{taxpayerId}/business-premises/{businessPremiseId}: get: tags: [Receipt branding] operationId: getPremiseReceiptBranding x-boka-implementation: implemented summary: Read a premise override or its effective inherited taxpayer default security: - humanOidc: [tenant:read] parameters: - name: taxpayerId in: path required: true schema: { type: string, format: uuid } - name: businessPremiseId in: path required: true schema: { type: string, format: uuid } responses: '200': description: Effective premise receipt branding content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingProfile' } '404': { $ref: '#/components/responses/NotFound' } put: tags: [Receipt branding] operationId: putPremiseReceiptBranding x-boka-implementation: implemented summary: Append and select an explicit premise receipt-branding override security: - humanOidc: [tenant:write] parameters: - name: taxpayerId in: path required: true schema: { type: string, format: uuid } - name: businessPremiseId in: path required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingProfileWrite' } responses: '200': description: Newly selected immutable premise revision content: application/json: schema: { $ref: '#/components/schemas/ReceiptBrandingProfile' } '404': { $ref: '#/components/responses/NotFound' } '409': { $ref: '#/components/responses/ResourceConflict' } '422': { $ref: '#/components/responses/ValidationError' } delete: tags: [Receipt branding] operationId: deletePremiseReceiptBranding x-boka-implementation: implemented summary: Remove the current premise pointer so new receipts inherit the taxpayer default description: Existing receipts retain their already captured immutable revision. security: - humanOidc: [tenant:write] parameters: - name: taxpayerId in: path required: true schema: { type: string, format: uuid } - name: businessPremiseId in: path required: true schema: { type: string, format: uuid } responses: '204': { description: Override absent; inheritance is effective for future reservations } '404': { $ref: '#/components/responses/NotFound' } /v1/products: get: tags: [Catalogue] operationId: listProducts x-boka-implementation: implemented summary: List ESIR catalogue entries by organization or taxpayer security: - oauth2: [catalogue:read] - humanOidc: [catalogue:read] parameters: - $ref: '#/components/parameters/TaxpayerFilter' - $ref: '#/components/parameters/StaffSupportContextHeader' - name: search in: query schema: { type: string, maxLength: 200 } - name: isActive in: query schema: { type: boolean } - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/PageSize' responses: '200': description: Product page content: application/json: schema: $ref: '#/components/schemas/ProductPage' '400': $ref: '#/components/responses/ValidationError' '422': $ref: '#/components/responses/ValidationError' post: tags: [Catalogue] operationId: createProduct x-boka-implementation: implemented summary: Add a product or service to the ESIR catalogue security: - oauth2: [catalogue:write] - humanOidc: [catalogue:write] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ProductCreate' responses: '201': description: Product content: application/json: schema: $ref: '#/components/schemas/Product' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/tax-rates: get: tags: [Fiscal configuration] operationId: listTaxRates x-boka-implementation: implemented summary: List the current PFR-provided tax labels and rates description: Performs a fresh authenticated PFR status fetch for the exact active premise security element. The response is not a cache authority and no built-in label or rate fallback exists. security: - oauth2: [configuration:read] - humanOidc: [configuration:read] parameters: - name: taxpayerId in: query required: true schema: { type: string, format: uuid } - name: businessPremiseId in: query required: true schema: { type: string, format: uuid } responses: '200': description: Current fiscal tax configuration content: application/json: schema: { $ref: '#/components/schemas/CurrentTaxConfiguration' } '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' '503': description: A fresh authoritative PFR configuration could not be obtained /v1/taxpayers: get: tags: [Taxpayers and premises] operationId: listTaxpayers x-boka-implementation: implemented summary: List taxpayer legal entities granted to this client description: | Scoped to the caller's environment: a machine credential sees only the taxpayers of its own environment, a portal user sees both unless the organization hides its sandbox data. security: - oauth2: [tenant:read] - humanOidc: [tenant:read] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' responses: '200': description: Taxpayers content: application/json: schema: { $ref: '#/components/schemas/TaxpayerPage' } '400': $ref: '#/components/responses/ValidationError' post: tags: [Taxpayers and premises] operationId: createTaxpayer x-boka-implementation: implemented summary: Add a taxpayer legal entity to the organization description: | The taxpayer belongs to the caller's environment: production for a human, the credential's own environment for a machine credential. A machine credential without a valid environment claim is refused with 403 `CREDENTIAL_ENVIRONMENT_REQUIRED`. A tax identifier is unique per organization and environment, so the same company may be registered once in sandbox and once in production (409 `TAXPAYER_ALREADY_EXISTS` only within the same environment). security: - oauth2: [tenant:write] - humanOidc: [tenant:write] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/TaxpayerCreate' responses: '201': description: Taxpayer content: application/json: schema: { $ref: '#/components/schemas/Taxpayer' } '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/taxpayers/{taxpayerId}: get: tags: [Taxpayers and premises] operationId: getTaxpayer x-boka-implementation: implemented summary: Retrieve one tenant-scoped taxpayer legal entity security: - oauth2: [tenant:read] - humanOidc: [tenant:read] parameters: - $ref: '#/components/parameters/TaxpayerId' - $ref: '#/components/parameters/StaffSupportContextHeader' responses: '200': description: Taxpayer content: application/json: schema: { $ref: '#/components/schemas/Taxpayer' } '404': $ref: '#/components/responses/NotFound' put: tags: [Taxpayers and premises] operationId: updateTaxpayer x-boka-implementation: implemented summary: Update a taxpayer legal name or lifecycle state description: The PIB is immutable. Closed taxpayers remain as immutable history and cannot be reopened. security: - oauth2: [tenant:write] - humanOidc: [tenant:write] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' - $ref: '#/components/parameters/TaxpayerId' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/TaxpayerUpdate' } responses: '200': description: Updated taxpayer content: application/json: schema: { $ref: '#/components/schemas/Taxpayer' } '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/taxpayers/{taxpayerId}/business-premises: get: tags: [Taxpayers and premises] operationId: listBusinessPremises x-boka-implementation: implemented summary: List the taxpayer's business premises security: - oauth2: [tenant:read] - humanOidc: [tenant:read] parameters: - $ref: '#/components/parameters/TaxpayerId' - $ref: '#/components/parameters/StaffSupportContextHeader' responses: '200': description: Premises content: application/json: schema: { $ref: '#/components/schemas/BusinessPremisePage' } '400': $ref: '#/components/responses/ValidationError' post: tags: [Taxpayers and premises] operationId: createBusinessPremise x-boka-implementation: implemented summary: Register a Boka representation of a PU business premise security: - oauth2: [tenant:write] - humanOidc: [tenant:write] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' - $ref: '#/components/parameters/TaxpayerId' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/BusinessPremiseCreate' responses: '201': description: Business premise content: application/json: schema: { $ref: '#/components/schemas/BusinessPremise' } '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/taxpayers/{taxpayerId}/business-premises/{businessPremiseId}: get: tags: [Taxpayers and premises] operationId: getBusinessPremise x-boka-implementation: implemented summary: Retrieve one tenant-scoped distance-commerce premise security: - oauth2: [tenant:read] - humanOidc: [tenant:read] parameters: - $ref: '#/components/parameters/TaxpayerId' - $ref: '#/components/parameters/BusinessPremiseId' - $ref: '#/components/parameters/StaffSupportContextHeader' responses: '200': description: Business premise content: application/json: schema: { $ref: '#/components/schemas/BusinessPremise' } '404': $ref: '#/components/responses/NotFound' put: tags: [Taxpayers and premises] operationId: updateBusinessPremise x-boka-implementation: implemented summary: Update a distance-commerce premise name or lifecycle state description: The PU identifier and commerce mode are immutable. Closed premises remain as immutable history and cannot be reopened. security: - oauth2: [tenant:write] - humanOidc: [tenant:write] parameters: - $ref: '#/components/parameters/StaffSupportContextHeader' - $ref: '#/components/parameters/TaxpayerId' - $ref: '#/components/parameters/BusinessPremiseId' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/BusinessPremiseUpdate' } responses: '200': description: Updated business premise content: application/json: schema: { $ref: '#/components/schemas/BusinessPremise' } '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/buyer-registry-lookups: get: tags: [Fiscal documents] operationId: lookupBuyerRegistry x-boka-implementation: implemented summary: Preview the buyer lines a receipt will print for a company buyer PIB description: | For the cashier screens. Given a valid nine-digit PIB, returns the legal name, registration number, address and city the receipt will print under the buyer identification, answered from the organization's stored registry answer when it is younger than 24 hours and otherwise from the NBS unified account register with a five-second bound. Issuance resolves the same facts again through the same path and stores them with the document; this route stores nothing on any receipt. Any customer human who may issue receipts (Owner, Administrator, Operator) may call it. Answers Cache-Control: no-store. security: - humanOidc: [fiscal:write] parameters: - name: taxIdentifier in: query required: true schema: { type: string, pattern: '^[0-9]{9}$' } description: Nine-digit Serbian PIB of the buyer (mod-11 checked). responses: '200': description: Buyer facts the receipt will print content: application/json: schema: { $ref: '#/components/schemas/BuyerRegistryLookup' } '404': description: The register holds no company for the PIB, or it could not answer within the bound (code BUYER_NOT_FOUND); the receipt will carry the buyer identification alone '422': $ref: '#/components/responses/ValidationError' '503': description: No stored answer and the company registry is not configured (code COMPANY_REGISTRY_UNCONFIGURED) /v1/company-registry-lookups: get: tags: [Taxpayer onboarding] operationId: lookupCompanyRegistry x-boka-implementation: implemented summary: Look up a company in the NBS unified account register by PIB or registration number description: | Read-only prefill aid for the customer taxpayer form. Owner or Administrator humans supply exactly one of taxIdentifier (nine-digit PIB, mod-11 checked) or registrationNumber (eight-digit matični broj). The API queries the National Bank of Serbia register fresh on every call, stores nothing, returns the aggregated company with every account and its blockade flag, and answers Cache-Control: no-store. Registry data is a human prefill aid, never fiscal input. security: - humanOidc: [tenant:write] parameters: - $ref: '#/components/parameters/CompanyRegistryTaxIdentifier' - $ref: '#/components/parameters/CompanyRegistryRegistrationNumber' responses: '200': description: Company found in the register content: application/json: schema: { $ref: '#/components/schemas/CompanyRegistryLookup' } '404': description: The register holds no company for the identifier (code COMPANY_NOT_FOUND) '422': $ref: '#/components/responses/ValidationError' '502': description: The register rejected the request or is unreachable (code COMPANY_REGISTRY_*) '503': description: The company registry lookup is not configured (code COMPANY_REGISTRY_UNCONFIGURED) /v1/staff-company-registry-lookups: get: tags: [BOKA control plane] operationId: lookupCompanyRegistryAsStaff x-boka-implementation: implemented summary: Look up a company in the NBS unified account register for the staff new-customer wizard description: | Same read-only lookup as /v1/company-registry-lookups, for Platform Administrator or Root staff who provision customers. The wizard prefills the organization name and the customer taxpayer from the result; staff confirm or edit every value before it is stored. security: - humanOidc: [customers:write] parameters: - $ref: '#/components/parameters/CompanyRegistryTaxIdentifier' - $ref: '#/components/parameters/CompanyRegistryRegistrationNumber' responses: '200': description: Company found in the register content: application/json: schema: { $ref: '#/components/schemas/CompanyRegistryLookup' } '404': description: The register holds no company for the identifier (code COMPANY_NOT_FOUND) '422': $ref: '#/components/responses/ValidationError' '502': description: The register rejected the request or is unreachable (code COMPANY_REGISTRY_*) '503': description: The company registry lookup is not configured (code COMPANY_REGISTRY_UNCONFIGURED) /v1/customer-provisionings: get: tags: [BOKA control plane] operationId: listStaffCustomerDirectory x-boka-implementation: implemented summary: List the safe BOKA staff customer directory description: | Returns organization identity and provisioning lifecycle metadata needed to select a customer for an audited support context. It deliberately excludes Owner contact, membership, managed-identity, and failure details. security: - humanOidc: [customers:read] parameters: - name: search in: query schema: { type: string, maxLength: 200 } - name: status in: query schema: type: string enum: - pending-identity - identity-prepared - invitation-dispatching - invitation-sent - invitation-outcome-unknown - identity-conflict - identity-control-plane-failed - active - $ref: '#/components/parameters/Cursor' - $ref: '#/components/parameters/PageSize' responses: '200': description: Redacted customer-directory page content: application/json: schema: { $ref: '#/components/schemas/StaffCustomerDirectoryPage' } '422': $ref: '#/components/responses/ValidationError' post: tags: [BOKA control plane] operationId: createCustomerProvisioning x-boka-implementation: implemented summary: Provision a customer organization and its pending Owner description: | Platform Administrator or Root creates the server-generated organization, pending Owner membership, audit event, and durable identity-invitation intent atomically. The Owner remains unlinked and inactive until the trusted managed- identity continuation succeeds; this route never accepts identity or activation fields. security: - humanOidc: [customers:write] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CustomerProvisioningCreate' } responses: '200': description: Idempotent replay of the original provisioning result content: application/json: schema: { $ref: '#/components/schemas/CustomerProvisioning' } '201': description: Customer organization and pending Owner created content: application/json: schema: { $ref: '#/components/schemas/CustomerProvisioning' } '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' /v1/customer-provisionings/{customerProvisioningId}: get: tags: [BOKA control plane] operationId: getCustomerProvisioning x-boka-implementation: implemented summary: Retrieve safe customer-provisioning state security: - humanOidc: [customers:read] parameters: - $ref: '#/components/parameters/CustomerProvisioningId' responses: '200': description: Customer-provisioning state content: application/json: schema: { $ref: '#/components/schemas/CustomerProvisioning' } '404': $ref: '#/components/responses/NotFound' /v1/customer-provisionings/{customerProvisioningId}/resend-invitation: post: tags: [BOKA control plane] operationId: resendCustomerProvisioningInvitation x-boka-implementation: implemented summary: Authorize one explicit re-dispatch of a pending Owner invitation description: | Platform Administrator or Root explicitly re-enters the trusted invitation dispatch path for a provisioning whose invitation was sent or ended with an unknown delivery outcome while its Owner is still unlinked and pending. The command records a fresh durable dispatch intent and an audit event; the trusted worker performs the actual send. It never accepts identity fields, never repeats an ambiguous delivery automatically, and is rejected once the Owner identity is linked or active. security: - humanOidc: [customers:write] parameters: - $ref: '#/components/parameters/CustomerProvisioningId' responses: '200': description: Re-dispatch authorized; the provisioning re-entered the trusted flow content: application/json: schema: { $ref: '#/components/schemas/CustomerProvisioning' } '404': $ref: '#/components/responses/NotFound' '409': description: The invitation cannot be re-sent from the current state content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } /v1/support-contexts: post: tags: [BOKA control plane] operationId: createStaffSupportContext x-boka-implementation: implemented summary: Open a short-lived staff support context description: | Binds the exact active staff identity to one organization, one support purpose, a safe case reference and reason, and a server-controlled expiry. It does not impersonate a customer or accept an organization header as authority. The default purpose `customer-support-read` is read-only and open to every staff role. The purpose `customer-data-edit` (authority `read-write`) is reserved for Senior Operations and Root; it additionally permits the taxpayer, business-premise and catalogue writes listed on those operations and nothing else. Writes made under it are recorded in the customer's organization audit stream with `actorKind` `boka-staff`. security: - humanOidc: [support-contexts:write] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StaffSupportContextCreate' } responses: '201': description: Active staff support context headers: Location: schema: { type: string, format: uri-reference } content: application/json: schema: { $ref: '#/components/schemas/StaffSupportContext' } '403': description: The staff role cannot open a context with the requested purpose (`STAFF_SUPPORT_CONTEXT_PURPOSE_FORBIDDEN`) '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/support-contexts/{supportContextId}: get: tags: [BOKA control plane] operationId: getStaffSupportContext x-boka-implementation: implemented summary: Read one support context owned by the exact staff identity security: - humanOidc: [support-contexts:read] parameters: - $ref: '#/components/parameters/SupportContextId' responses: '200': description: Staff support context content: application/json: schema: { $ref: '#/components/schemas/StaffSupportContext' } '404': $ref: '#/components/responses/NotFound' delete: tags: [BOKA control plane] operationId: closeStaffSupportContext x-boka-implementation: implemented summary: Close one support context owned by the exact staff identity security: - humanOidc: [support-contexts:write] parameters: - $ref: '#/components/parameters/SupportContextId' responses: '204': description: Context closed or already closed '404': $ref: '#/components/responses/NotFound' /v1/human-access-audit-events: get: tags: [BOKA control plane] operationId: listHumanAccessAuditEvents x-boka-implementation: implemented summary: Read the redacted human-access event stream visible to the active staff identity description: | Support, Operations, and Security Administrator receive only events produced by their exact managed identity. Platform Administrator and Root receive the platform stream. The response deliberately omits actor issuer/subject, email, raw metadata, case reason, digests, provider failures, and secret references. This route does not accept a staff support context and does not grant tenant-read authority. security: - humanOidc: [staff:read] parameters: - name: eventType in: query schema: { type: string, maxLength: 100, pattern: '^human-access\.' } - name: outcome in: query schema: { type: string, enum: [accepted, rejected, succeeded, unknown] } - name: organizationId in: query schema: { type: string, format: uuid } - $ref: '#/components/parameters/HumanAccessAuditCursor' - $ref: '#/components/parameters/PageSize' responses: '200': description: Redacted human-access audit page content: application/json: schema: { $ref: '#/components/schemas/HumanAccessAuditPage' } '400': $ref: '#/components/responses/ValidationError' '422': $ref: '#/components/responses/ValidationError' /v1/audit-events: get: tags: [Organization audit] operationId: listOrganizationAuditEvents x-boka-implementation: implemented summary: Read the redacted operational event stream for the authenticated organization description: | Returns only explicitly reviewed catalogue, onboarding, and security-element lifecycle event types belonging to the organization resolved from the verified human identity. All active customer roles may read this view. Actor identity, organization ID, raw metadata, upload-session IDs, provider detail, secret references, and unreviewed event types are withheld. Staff support contexts do not authorize this route. security: - humanOidc: [tenant:read] parameters: - name: eventType in: query schema: type: string enum: - catalogue.product-created - catalogue.product-updated - catalogue.products-imported - onboarding.taxpayer-updated - onboarding.business-premise-updated - receipt-branding.asset-uploaded - receipt-branding.profile-updated - receipt-branding.premise-override-removed - security-element.upload-session.created - security-element.upload-session.completed - security-element.upload-session.failed - security-element.suspended - security-element.retired - license.updated - license.invoice-recorded - license.invoice-updated - receipt-delivery.settings-updated - organization-settings.sandbox-data-visibility-updated - name: outcome in: query schema: { type: string, enum: [success, failure] } - $ref: '#/components/parameters/TaxpayerFilter' - $ref: '#/components/parameters/PremiseFilter' - $ref: '#/components/parameters/OrganizationAuditCursor' - $ref: '#/components/parameters/OrganizationAuditPageSize' responses: '200': description: Redacted organization operational-audit page content: application/json: schema: { $ref: '#/components/schemas/OrganizationAuditPage' } '422': $ref: '#/components/responses/ValidationError' /v1/organization-members: get: tags: [Human access] operationId: listOrganizationMembers x-boka-implementation: implemented summary: List safe customer membership metadata for the authenticated organization description: Requires an active Owner or Administrator membership whose persisted role agrees with the signed token. security: - humanOidc: [memberships:read] responses: '200': description: Organization members content: application/json: schema: { $ref: '#/components/schemas/OrganizationMembershipPage' } post: tags: [Human access] operationId: inviteOrganizationMember x-boka-implementation: implemented summary: Record a pending organization invitation description: Creates the pending membership and durable managed-identity workflow atomically. The trusted control plane issues the provider action link and activates only after exact identity, tenant, and role verification. Pending invitations grant no access. Owner creation and ownership transfer require a future step-up flow. security: - humanOidc: [memberships:write] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/OrganizationMembershipCreate' } responses: '201': description: Pending organization membership content: application/json: schema: { $ref: '#/components/schemas/OrganizationMembership' } '403': description: The requested role change requires step-up or greater authority content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/organization-members/{organizationMemberId}: put: tags: [Human access] operationId: updateOrganizationMember x-boka-implementation: implemented summary: Change a non-Owner organization membership description: An unlinked invitation cannot be activated through this public route. Suspending an unlinked membership cancels its workflow; cancelled invitations require a future dedicated reinvitation operation. security: - humanOidc: [memberships:write] parameters: - $ref: '#/components/parameters/OrganizationMemberId' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/OrganizationMembershipUpdate' } responses: '200': description: Updated organization membership content: application/json: schema: { $ref: '#/components/schemas/OrganizationMembership' } '403': description: The requested role change requires step-up or greater authority content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' '503': description: The managed identity provider could not apply a role change for a linked member (MEMBERSHIP_ROLE_SYNC_UNAVAILABLE or MEMBERSHIP_ROLE_SYNC_NOT_CONFIGURED); the membership is unchanged content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } /v1/staff-members: get: tags: [BOKA control plane] operationId: listStaffMembers x-boka-implementation: implemented summary: List safe BOKA staff membership metadata security: - humanOidc: [staff:read] responses: '200': description: BOKA staff members content: application/json: schema: { $ref: '#/components/schemas/StaffMembershipPage' } post: tags: [BOKA control plane] operationId: inviteStaffMember x-boka-implementation: implemented summary: Record a pending BOKA staff invitation description: Platform Administrator may manage regular staff; only Root may create Platform Administrator or Root invitations. Regular-staff creation atomically records a trusted managed-identity workflow. Elevated staff remains pending until its dedicated high-assurance procedure exists. security: - humanOidc: [staff:write] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StaffMembershipCreate' } responses: '201': description: Pending staff membership content: application/json: schema: { $ref: '#/components/schemas/StaffMembership' } '403': description: Root authority is required for this role content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' /v1/staff-members/{staffMemberId}: put: tags: [BOKA control plane] operationId: updateStaffMember x-boka-implementation: implemented summary: Change a BOKA staff membership within the actor's authority description: Self-changes are rejected and pending identities cannot be activated through this route. Suspending an unlinked regular-staff membership cancels its managed-identity workflow. security: - humanOidc: [staff:write] parameters: - $ref: '#/components/parameters/StaffMemberId' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StaffMembershipUpdate' } responses: '200': description: Updated staff membership content: application/json: schema: { $ref: '#/components/schemas/StaffMembership' } '403': description: Root authority is required for this role content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } '404': $ref: '#/components/responses/NotFound' '409': $ref: '#/components/responses/ResourceConflict' '422': $ref: '#/components/responses/ValidationError' '503': description: The managed identity provider could not apply a role change for a linked member (MEMBERSHIP_ROLE_SYNC_UNAVAILABLE or MEMBERSHIP_ROLE_SYNC_NOT_CONFIGURED); the membership is unchanged content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } /v1/security-element-upload-sessions: post: tags: [Security elements] operationId: createSecurityElementUploadSession x-boka-implementation: implemented summary: Create a one-time encrypted certificate onboarding session description: | Used by both customer self-service and BOKA-assisted onboarding. The resulting upload cannot be used to retrieve plaintext secrets. The premise must belong to the session's environment; a production certificate can never be bound to the BOKA sandbox premise (422 `PREMISE_ENVIRONMENT_MISMATCH`). security: - oauth2: [security-elements:write] - humanOidc: [security-elements:write] parameters: - $ref: '#/components/parameters/IdempotencyKey' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [taxpayerId, businessPremiseId, environment] properties: taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } environment: { $ref: '#/components/schemas/FiscalEnvironment' } responses: '201': description: One-time upload session content: application/json: schema: $ref: '#/components/schemas/SecurityElementUploadSession' '409': $ref: '#/components/responses/IdempotencyConflict' '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/SecurityElementCustodyUnavailable' /v1/security-element-upload-sessions/{uploadSessionId}/complete: post: tags: [Security elements] operationId: completeSecurityElementUploadSession x-boka-implementation: implemented summary: Consume a one-time session with a compact JWE payload description: | Encrypt the complete SecurityElementUploadPayload as compact JWE using RSA-OAEP-256 and A256GCM with the key returned when the session was created. Customer self-service sends the original supplier ZIP as archiveBase64; direct pfxBase64 remains available for controlled integrations. Exactly one certificate-material field is permitted. The session is single-use. A structurally valid certificate remains PENDING_VERIFICATION until the V-PFR sandbox verification succeeds. security: - oauth2: [security-elements:write] - humanOidc: [security-elements:write] parameters: - name: uploadSessionId in: path required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [encryptedPayload] properties: encryptedPayload: type: string description: Compact JWE containing SecurityElementUploadPayload JSON. responses: '201': description: Security element accepted for later V-PFR verification content: application/json: schema: { $ref: '#/components/schemas/SecurityElement' } '409': description: Session already consumed or certificate already onboarded '410': description: Session expired or its ephemeral encryption key is no longer available '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/SecurityElementCustodyUnavailable' /v1/security-elements: get: tags: [Security elements] operationId: listSecurityElements x-boka-implementation: implemented summary: List safe security-element metadata description: | Secret envelope references and plaintext values are never returned. Scoped to the caller's environment like every other tenant read. security: - oauth2: [security-elements:read] - humanOidc: [security-elements:read] parameters: - name: taxpayerId in: query schema: { type: string, format: uuid } - name: businessPremiseId in: query schema: { type: string, format: uuid } responses: '200': description: Tenant-scoped security elements content: application/json: schema: type: object required: [items] properties: items: type: array items: { $ref: '#/components/schemas/SecurityElement' } /v1/security-elements/{securityElementId}: get: tags: [Security elements] operationId: getSecurityElement x-boka-implementation: implemented summary: Read safe security-element metadata security: - oauth2: [security-elements:read] - humanOidc: [security-elements:read] parameters: - $ref: '#/components/parameters/SecurityElementId' responses: '200': description: Security element metadata content: application/json: schema: { $ref: '#/components/schemas/SecurityElement' } '404': $ref: '#/components/responses/NotFound' /v1/security-elements/{securityElementId}/suspend: post: tags: [Security elements] operationId: suspendSecurityElement x-boka-implementation: implemented summary: Prevent a security element from fiscal routing security: - oauth2: [security-elements:write] - humanOidc: [security-elements:write] parameters: - $ref: '#/components/parameters/SecurityElementId' responses: '200': description: Suspended security element content: application/json: schema: { $ref: '#/components/schemas/SecurityElement' } '404': $ref: '#/components/responses/NotFound' '409': description: Invalid lifecycle transition /v1/security-elements/{securityElementId}/retire: post: tags: [Security elements] operationId: retireSecurityElement x-boka-implementation: implemented summary: Permanently retire a security element from fiscal routing security: - oauth2: [security-elements:write] - humanOidc: [security-elements:write] parameters: - $ref: '#/components/parameters/SecurityElementId' responses: '200': description: Retired security element content: application/json: schema: { $ref: '#/components/schemas/SecurityElement' } '404': $ref: '#/components/responses/NotFound' /v1/staff-security-elements: get: tags: [BOKA security elements] operationId: listStaffSecurityElements x-boka-implementation: implemented summary: List customer security elements by lifecycle state for BOKA staff description: | Security Administrator or Root. Cross-tenant safe metadata only (organization, taxpayer, premise, JID, environment, certificate window, lifecycle) plus the latest staff-authorized activation job. Never exposes secrets, envelope references, verification URLs, or raw supplier responses. security: - humanOidc: [staff:read] parameters: - name: status in: query schema: type: string enum: [pending-verification, active, suspended, retired] default: pending-verification - $ref: '#/components/parameters/PageSize' responses: '200': description: Customer security elements in the requested state content: application/json: schema: { $ref: '#/components/schemas/StaffSecurityElementPage' } '422': $ref: '#/components/responses/ValidationError' /v1/staff-security-elements/{staffSecurityElementId}/activation-jobs: get: tags: [BOKA security elements] operationId: listSecurityElementActivationJobs x-boka-implementation: implemented summary: List activation jobs for one customer security element security: - humanOidc: [staff:read] parameters: - $ref: '#/components/parameters/StaffSecurityElementId' responses: '200': description: Activation jobs, newest first content: application/json: schema: { $ref: '#/components/schemas/SecurityElementActivationJobPage' } post: tags: [BOKA security elements] operationId: requestSecurityElementActivationJob x-boka-implementation: implemented summary: Authorize a staff-triggered activation of a pending production element description: | Security Administrator or Root commits a durable activation job before any supplier call. The trusted worker then runs the unchanged metadata verification and proves the element with one Training (Obuka) receipt signed by V-PFR, which creates no fiscal liability, and activates the element only after the stored signed response and the independent public receipt verification succeed. Sandbox elements activate through pool assignment and are refused here. One open job per element; a failed job records a safe code and may be followed by a new job, which reuses the already reserved fiscal operation and never resubmits blindly. security: - humanOidc: [staff:write] parameters: - $ref: '#/components/parameters/StaffSecurityElementId' responses: '202': description: Activation job queued for the trusted worker content: application/json: schema: { $ref: '#/components/schemas/SecurityElementActivationJob' } '404': $ref: '#/components/responses/NotFound' '409': description: Not pending, not a production element, or a job is already open content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } '503': description: The activation job worker is not enabled in this environment content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } /v1/staff-sandbox-elements: get: tags: [BOKA sandbox pool] operationId: listStaffSandboxElements x-boka-implementation: implemented summary: List the BOKA-owned sandbox security-element pool description: Safe metadata only; plaintext secrets and envelope references are never exposed. security: - humanOidc: [staff:read] responses: '200': description: Pool elements content: application/json: schema: { $ref: '#/components/schemas/StaffSandboxElementPage' } post: tags: [BOKA sandbox pool] operationId: createStaffSandboxElement x-boka-implementation: implemented summary: Add a BOKA-owned sandbox element to the pool description: | Staff-only. The PFX is opened and verified offline (JID, taxpayer PIB, sandbox origins, validity) before the secrets are protected into the dedicated pool custody store. Live sandbox verification remains a prerequisite before fiscalization is ever enabled. security: - humanOidc: [staff:write] requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StaffSandboxElementCreate' } responses: '201': description: Pool element stored with verified offline facts content: application/json: schema: { $ref: '#/components/schemas/StaffSandboxElement' } '409': description: A pool element with this certificate already exists '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/SecurityElementCustodyUnavailable' /v1/staff-sandbox-elements/{staffSandboxElementId}: get: tags: [BOKA sandbox pool] operationId: getStaffSandboxElement x-boka-implementation: implemented summary: Read one pool element's safe metadata security: - humanOidc: [staff:read] parameters: - $ref: '#/components/parameters/StaffSandboxElementId' responses: '200': description: Pool element content: application/json: schema: { $ref: '#/components/schemas/StaffSandboxElement' } '404': $ref: '#/components/responses/NotFound' /v1/staff-sandbox-elements/{staffSandboxElementId}/retire: post: tags: [BOKA sandbox pool] operationId: retireStaffSandboxElement x-boka-implementation: implemented summary: Retire a pool element and every element assigned from it security: - humanOidc: [staff:write] parameters: - $ref: '#/components/parameters/StaffSandboxElementId' responses: '200': description: Retired pool element content: application/json: schema: { $ref: '#/components/schemas/StaffSandboxElement' } '404': $ref: '#/components/responses/NotFound' /v1/staff-sandbox-elements/{staffSandboxElementId}/assignments: get: tags: [BOKA sandbox pool] operationId: listStaffSandboxAssignments x-boka-implementation: implemented summary: List the organizations this pool element is assigned to security: - humanOidc: [staff:read] parameters: - $ref: '#/components/parameters/StaffSandboxElementId' responses: '200': description: Assignments content: application/json: schema: { $ref: '#/components/schemas/StaffSandboxAssignmentPage' } '404': $ref: '#/components/responses/NotFound' post: tags: [BOKA sandbox pool] operationId: createStaffSandboxAssignment x-boka-implementation: implemented summary: Assign a pool element to a customer organization description: | Creates the BOKA sandbox taxpayer and distance-commerce premise inside the customer organization when missing, copies the pool secrets into the exact tenant custody scope, and activates an organization-scoped sandbox security element. No plaintext leaves custody. security: - humanOidc: [staff:write] parameters: - $ref: '#/components/parameters/StaffSandboxElementId' requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [organizationId] properties: organizationId: { type: string, format: uuid } responses: '201': description: Assignment with the created organization-scoped element content: application/json: schema: { $ref: '#/components/schemas/StaffSandboxAssignment' } '404': $ref: '#/components/responses/NotFound' '409': description: Already assigned, or the premise already holds an active element '422': $ref: '#/components/responses/ValidationError' '503': $ref: '#/components/responses/SecurityElementCustodyUnavailable' /v1/api-credentials: get: tags: [BOKA control plane] operationId: listApiCredentials x-boka-implementation: implemented summary: List issued machine API credentials description: Metadata only; secrets are never retrievable after creation. security: - humanOidc: [customers:read] parameters: - name: organizationId in: query required: false schema: { type: string, format: uuid } responses: '200': description: Issued credentials content: application/json: schema: { $ref: '#/components/schemas/ApiCredentialPage' } post: tags: [BOKA control plane] operationId: createApiCredential x-boka-implementation: implemented summary: Issue a machine API credential for one organization and environment description: | Staff-only. The durable record commits before the identity provider is called. The response contains the generated secret exactly once; Boka never stores it, and it cannot be read again. The provisioned client carries hardcoded org_id and boka_env claims plus the standard integration scope bundle. security: - humanOidc: [customers:write] requestBody: required: true content: application/json: schema: type: object additionalProperties: false required: [organizationId, environment] properties: organizationId: { type: string, format: uuid } environment: { $ref: '#/components/schemas/FiscalEnvironment' } displayName: { type: string, maxLength: 200 } responses: '201': description: Issued credential including its one-time secret content: application/json: schema: { $ref: '#/components/schemas/ApiCredential' } '422': $ref: '#/components/responses/ValidationError' '502': description: The identity provider rejected or failed the operation '503': description: The identity provider for API credentials is not configured /v1/api-credentials/{apiCredentialId}/revoke: post: tags: [BOKA control plane] operationId: revokeApiCredential x-boka-implementation: implemented summary: Revoke a machine API credential description: Disables the identity-provider client, then durably marks the credential revoked. security: - humanOidc: [customers:write] parameters: - name: apiCredentialId in: path required: true schema: { type: string, format: uuid } responses: '200': description: Revoked credential content: application/json: schema: { $ref: '#/components/schemas/ApiCredential' } '404': $ref: '#/components/responses/NotFound' '409': description: A failed credential cannot be revoked '502': description: The identity provider rejected or failed the operation '503': description: The identity provider for API credentials is not configured /v1/license: get: tags: [Billing] operationId: getLicenseSummary x-boka-implementation: implemented summary: Read which paid capabilities the organization currently has description: | Any customer human or API client. Reports the licence state (none, active, suspended, not-started, expired), whether production fiscalization is currently allowed, which paid modules (Advance, E-mail) are enabled, and the effective prices in RSD with VAT included. Sandbox elements and sandbox traffic are never gated by licensing. A refused production command returns 403 with LICENSE_REQUIRED, LICENSE_NOT_STARTED, LICENSE_EXPIRED, LICENSE_SUSPENDED or MODULE_NOT_LICENSED before anything is reserved or sent to the V-PFR. Refunds of already fiscalized receipts and advance cancellations are never refused by licensing. security: - oauth2: [tenant:read] - humanOidc: [tenant:read] responses: '200': description: Licence summary content: application/json: schema: { $ref: '#/components/schemas/LicenseSummary' } /v1/billing: get: tags: [Billing] operationId: getBilling x-boka-implementation: implemented summary: Owner billing overview description: | Customer Owner only. Returns the licence without internal notes, the platform price list, the current Belgrade calendar month's statement (computed from active production elements, enabled modules and the number of production documents that received a signed V-PFR response), the previous eleven monthly statements, the invoices BOKA recorded with their payment state, and the production elements. Payment never happens on the platform: BOKA issues every invoice outside it and Root records the outcome. security: - humanOidc: [tenant:read] responses: '200': description: Billing overview content: application/json: schema: { $ref: '#/components/schemas/Billing' } /v1/staff-licenses: get: tags: [BOKA licensing] operationId: listStaffLicenses x-boka-implementation: implemented summary: List every organization with its licence state for BOKA Root description: | Root only. One row per organization: licence (or none), active production elements, the current month's document count and estimated gross total, and the number of unpaid invoices. security: - humanOidc: [staff:read] responses: '200': description: Organizations and their licences content: application/json: schema: { $ref: '#/components/schemas/StaffLicensePage' } /v1/staff-licenses/{organizationId}: get: tags: [BOKA licensing] operationId: getStaffLicense x-boka-implementation: implemented summary: Read one organization's licence, statements, invoices and elements security: - humanOidc: [staff:read] parameters: - $ref: '#/components/parameters/StaffLicenseOrganizationId' responses: '200': description: Licence detail content: application/json: schema: { $ref: '#/components/schemas/StaffLicenseDetail' } '404': $ref: '#/components/responses/NotFound' put: tags: [BOKA licensing] operationId: putStaffLicense x-boka-implementation: implemented summary: Issue or edit an organization's licence description: | Root only. Creates the licence on first write (201) and replaces it on later writes (200). The customer enters nothing. Every write records a customer-visible audit event (license.updated) and a staff audit event. Licensing never changes fiscal data. security: - humanOidc: [staff:write] parameters: - $ref: '#/components/parameters/StaffLicenseOrganizationId' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StaffLicenseWrite' } responses: '200': description: Updated licence detail content: application/json: schema: { $ref: '#/components/schemas/StaffLicenseDetail' } '201': description: Licence issued content: application/json: schema: { $ref: '#/components/schemas/StaffLicenseDetail' } '403': description: The request is already bound to a different organization '404': $ref: '#/components/responses/NotFound' '422': $ref: '#/components/responses/ValidationError' /v1/staff-licenses/{organizationId}/invoices: post: tags: [BOKA licensing] operationId: createStaffLicenseInvoice x-boka-implementation: implemented summary: Record an invoice BOKA issued for one billing period description: | Root only. The invoice itself is issued outside the platform; this records its number, dates and gross amount so the Owner sees it on the billing page. Invoice numbers are unique per organization. security: - humanOidc: [staff:write] parameters: - $ref: '#/components/parameters/StaffLicenseOrganizationId' requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StaffLicenseInvoiceCreate' } responses: '201': description: Recorded invoice content: application/json: schema: { $ref: '#/components/schemas/LicenseInvoice' } '403': description: The request is already bound to a different organization '404': $ref: '#/components/responses/NotFound' '409': description: The invoice number already exists for this organization '422': $ref: '#/components/responses/ValidationError' /v1/staff-licenses/{organizationId}/invoices/{invoiceId}: put: tags: [BOKA licensing] operationId: updateStaffLicenseInvoice x-boka-implementation: implemented summary: Correct a recorded invoice or record its payment security: - humanOidc: [staff:write] parameters: - $ref: '#/components/parameters/StaffLicenseOrganizationId' - name: invoiceId in: path required: true schema: { type: string, format: uuid } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/StaffLicenseInvoiceUpdate' } responses: '200': description: Updated invoice content: application/json: schema: { $ref: '#/components/schemas/LicenseInvoice' } '403': description: The request is already bound to a different organization '404': $ref: '#/components/responses/NotFound' '409': description: The invoice number already exists for this organization '422': $ref: '#/components/responses/ValidationError' components: securitySchemes: oauth2: type: oauth2 flows: clientCredentials: tokenUrl: https://auth.bokapos.rs/realms/boka/protocol/openid-connect/token scopes: fiscal:read: Read fiscal documents and receipt representations fiscal:write: Fiscalize standalone documents refund:write: Create refund workflows proforma-training:write: Create bounded Proforma and Training workflows advance:write: Create advance cases and payments advance:close: Close an advance case delivery:write: Deliver receipts electronically operations:read: Read operation status catalogue:read: Read catalogue products catalogue:write: Change catalogue products configuration:read: Read fiscal configuration tenant:read: Read granted taxpayers and premises tenant:write: Administer taxpayers and premises security-elements:read: Read safe V-PFR security-element metadata security-elements:write: Onboard or rotate V-PFR security elements humanOidc: type: oauth2 description: Confidential BFF authorization-code flow. Human claims are intersected with active persisted memberships. flows: authorizationCode: authorizationUrl: https://auth.bokapos.rs/realms/boka/protocol/openid-connect/auth tokenUrl: https://auth.bokapos.rs/realms/boka/protocol/openid-connect/token scopes: tenant:read: Read the exact customer tenant or an explicitly validated staff support context tenant:write: Administer taxpayers and premises within the authorized tenant catalogue:read: Read customer catalogue data within the exact authorized tenant context catalogue:write: Change customer catalogue products within the authorized tenant operations:read: Read durable fiscal-operation status within the authorized tenant fiscal:read: Read fiscal documents and receipt representations within the authorized tenant fiscal:write: Fiscalize documents within the authorized tenant refund:write: Create refund workflows within the authorized tenant advance:write: Create Advance cases and fiscalize their Advance Sales within the authorized tenant advance:close: Close Advance cases through the server-owned Refund and final Sale chain configuration:read: Read freshly fetched PFR tax configuration within the authorized tenant security-elements:read: Read safe V-PFR security-element metadata within the authorized tenant security-elements:write: Onboard, suspend, or retire V-PFR security elements within the authorized tenant memberships:read: Read organization membership metadata memberships:write: Invite or change non-Owner organization members staff:read: Read BOKA staff membership metadata staff:write: Invite or change BOKA staff within the actor's authority customers:read: Read safe BOKA customer-provisioning state customers:write: Provision BOKA customer organizations and pending Owners support-contexts:read: Read support contexts owned by the exact staff identity support-contexts:write: Open or close short-lived staff support contexts parameters: StaffLicenseOrganizationId: name: organizationId in: path required: true schema: { type: string, format: uuid } CompanyRegistryTaxIdentifier: name: taxIdentifier in: query required: false description: Nine-digit Serbian PIB. Exactly one of taxIdentifier or registrationNumber is required. schema: { type: string, pattern: '^[0-9]{9}$' } CompanyRegistryRegistrationNumber: name: registrationNumber in: query required: false description: Eight-digit Serbian registration number (matični broj). Exactly one of taxIdentifier or registrationNumber is required. schema: { type: string, pattern: '^[0-9]{8}$' } IdempotencyKey: name: Idempotency-Key in: header required: true description: Unique within the applicable tenant or control-plane operation; reuse only for the same canonical command. schema: { type: string, minLength: 16, maxLength: 200 } FiscalDocumentId: name: fiscalDocumentId in: path required: true schema: { type: string, format: uuid } ReceiptRepresentationFormat: name: representationFormat in: path required: true schema: type: string enum: [canonical-json, official-text, qr-svg, pdf-a4, pdf-80mm, pdf-58mm, preview-png] AdvanceCaseId: name: advanceCaseId in: path required: true schema: { type: string, format: uuid } ProformaTrainingWorkflowId: name: proformaTrainingWorkflowId in: path required: true schema: { type: string, format: uuid } SecurityElementId: name: securityElementId in: path required: true schema: { type: string, format: uuid } StaffSandboxElementId: name: staffSandboxElementId in: path required: true schema: { type: string, format: uuid } StaffSecurityElementId: name: staffSecurityElementId in: path required: true schema: { type: string, format: uuid } OperationId: name: operationId in: path required: true schema: { type: string, format: uuid } TaxpayerId: name: taxpayerId in: path required: true schema: { type: string, format: uuid } BusinessPremiseId: name: businessPremiseId in: path required: true schema: { type: string, format: uuid } ProductId: name: productId in: path required: true schema: { type: string, format: uuid } OrganizationMemberId: name: organizationMemberId in: path required: true schema: { type: string, format: uuid } StaffMemberId: name: staffMemberId in: path required: true schema: { type: string, format: uuid } SupportContextId: name: supportContextId in: path required: true schema: { type: string, format: uuid } StaffSupportContextHeader: name: Boka-Support-Context in: header required: false description: Exact active staff support-context ID, always revalidated against the signed staff identity. A `customer-support-read` context is valid only for the allowlisted GET routes; a `customer-data-edit` context (Senior Operations or Root) additionally permits the taxpayer, business-premise and catalogue writes that declare this parameter. schema: { type: string, format: uuid } CustomerProvisioningId: name: customerProvisioningId in: path required: true schema: { type: string, format: uuid } TaxpayerFilter: name: taxpayerId in: query required: false schema: { type: string, format: uuid } PremiseFilter: name: businessPremiseId in: query required: false schema: { type: string, format: uuid } FiscalDocumentFilter: name: fiscalDocumentId in: query schema: { type: string, format: uuid } FiscalDocumentStatusFilter: name: status in: query schema: { $ref: '#/components/schemas/FiscalDocumentStatus' } InvoiceTypeFilter: name: invoiceType in: query schema: { $ref: '#/components/schemas/InvoiceType' } TransactionTypeFilter: name: transactionType in: query schema: { $ref: '#/components/schemas/TransactionType' } CreatedFromFilter: name: createdFrom in: query description: Inclusive lower bound for durable operation creation time. Any RFC 3339 offset is accepted (Z, +00:00, +02:00) and compared as an instant. schema: { type: string, format: date-time } CreatedToFilter: name: createdTo in: query description: Exclusive upper bound for durable operation creation time. Any RFC 3339 offset is accepted and compared as an instant. schema: { type: string, format: date-time } PfrFromFilter: name: pfrFrom in: query description: Inclusive lower bound for authoritative PFR signing time; excludes entries with no PFR receipt. Any RFC 3339 offset is accepted and compared as an instant. schema: { type: string, format: date-time } PfrToFilter: name: pfrTo in: query description: Exclusive upper bound for authoritative PFR signing time; excludes entries with no PFR receipt. Any RFC 3339 offset is accepted and compared as an instant. schema: { type: string, format: date-time } IdempotencyKeyFilter: name: idempotencyKey in: query schema: { type: string, maxLength: 200 } ClientReferenceFilter: name: clientReference in: query schema: { type: string, maxLength: 200 } PfrNumberFilter: name: pfrNumber in: query schema: { type: string, maxLength: 500 } CashierIdFilter: name: cashierId in: query schema: { type: string, maxLength: 500 } BuyerIdFilter: name: buyerId in: query schema: { type: string, maxLength: 500 } FiscalDocumentSearch: name: search in: query description: Case-insensitive contains search across safe identifiers only; raw PFR payload, journal, metadata, and secrets are excluded. schema: { type: string, maxLength: 200 } FiscalDocumentCursor: name: cursor in: query description: Opaque keyset cursor returned by the preceding journal page. schema: { type: string, maxLength: 200 } Cursor: name: cursor in: query schema: { type: string, format: uuid } HumanAccessAuditCursor: name: cursor in: query description: Opaque keyset cursor returned by the preceding audit page. schema: { type: string, maxLength: 200 } OrganizationAuditCursor: name: cursor in: query description: Opaque keyset cursor returned by the preceding organization audit page. schema: { type: string, maxLength: 200 } OrganizationAuditPageSize: name: pageSize in: query schema: { type: integer, minimum: 1, maximum: 100, default: 50 } PageSize: name: pageSize in: query schema: { type: integer, minimum: 1, maximum: 200, default: 50 } responses: IdempotencyConflict: description: The key was already used with different canonical content content: application/json: schema: { $ref: '#/components/schemas/IdempotencyConflict' } ResourceConflict: description: The requested resource conflicts with an existing tenant-scoped record content: application/json: schema: { $ref: '#/components/schemas/Conflict' } ValidationError: description: The command violates a Boka or fiscal rule content: application/problem+json: schema: { $ref: '#/components/schemas/ValidationProblem' } application/json: schema: { $ref: '#/components/schemas/ErrorCode' } PfrUnavailable: description: No fiscal receipt was issued because V-PFR was unavailable or its outcome requires reconciliation content: application/json: schema: { $ref: '#/components/schemas/FiscalDocument' } SecurityElementCustodyUnavailable: description: Security-element custody is disabled, unavailable, or cannot safely use the required key version content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } NotFound: description: Resource does not exist within the authenticated tenant scope content: application/json: schema: { $ref: '#/components/schemas/ErrorCode' } schemas: RuntimeInfo: type: object additionalProperties: false required: [manufacturer, productName, esirNumber, softwareVersion, buildCommit, instanceId, organizationId, clientId, fiscalEndpointsEnabled, pfrAdapter] properties: manufacturer: type: string enum: [BOKA GROUP DOO] productName: type: string enum: [BokaPOS] esirNumber: type: string maxLength: 60 description: | The ESIR number printed on every fiscal receipt, as the Tax Administration approval number and the declared product version separated by a slash. Empty while no approval decision has assigned one. This is not the deployed build; see softwareVersion. examples: ["1656/1.0.0", ""] softwareVersion: type: string minLength: 1 maxLength: 64 pattern: '^[0-9A-Za-z][0-9A-Za-z.+-]{0,63}$' buildCommit: type: string description: Exact 40-character Git commit; only Development may report the explicit unversioned-development placeholder. instanceId: type: string minLength: 1 maxLength: 128 description: Deployment-owned ESIR software instance identity; never a tenant, OAuth client, certificate, or approval number. organizationId: { type: string, format: uuid } clientId: { type: string } fiscalEndpointsEnabled: { type: boolean } pfrAdapter: { type: string, enum: [configured, not-configured] } InvoiceType: type: string enum: [NORMAL, PROFORMA, COPY, TRAINING, ADVANCE] TransactionType: type: string enum: [SALE, REFUND] PaymentType: type: string enum: [OTHER, CASH, CARD, CHECK, WIRE_TRANSFER, VOUCHER, INSTANT_PAYMENT] FiscalDocumentStatus: type: string enum: [RECEIVED, VALIDATED, SUBMITTING, FISCALIZED, REJECTED, NOT_FISCALIZED, OUTCOME_UNKNOWN, RECONCILING] FiscalDocumentCreate: type: object additionalProperties: false required: - taxpayerId - businessPremiseId - clientReference - invoiceType - transactionType - cashier - items - payments properties: taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } clientReference: type: string description: Caller-owned order, invoice, or transaction reference. invoiceType: { $ref: '#/components/schemas/InvoiceType' } transactionType: { $ref: '#/components/schemas/TransactionType' } cashier: { $ref: '#/components/schemas/Cashier' } buyer: { $ref: '#/components/schemas/Buyer' } reference: { $ref: '#/components/schemas/ReferenceTarget' } transactionOccurredAt: type: string format: date-time description: Accepted only for an Advance Sale containing a wire-transfer payment, where the official earlier-payment ESIR-time rule applies; it never overrides PFR time. items: type: array minItems: 1 items: { $ref: '#/components/schemas/LineItem' } payments: type: array minItems: 1 items: { $ref: '#/components/schemas/Payment' } commercialFooter: type: string description: Non-fiscal text rendered only in the permitted area outside the fiscal boundary. metadata: type: object additionalProperties: { type: string } description: Non-fiscal caller metadata; never sent as a substitute for a mandated field. FiscalDocument: type: object required: - id - taxpayerId - businessPremiseId - idempotencyKey - clientReference - invoiceType - transactionType - cashierId - status - fiscalized - createdAt - updatedAt - retryable properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } idempotencyKey: { type: string } clientReference: { type: string } invoiceType: { $ref: '#/components/schemas/InvoiceType' } transactionType: { $ref: '#/components/schemas/TransactionType' } cashierId: { type: string } buyerId: { type: [string, 'null'] } buyerDetails: description: The buyer's company-registry facts stored with the document at reservation and printed under the buyer identification. Null when the buyer is not a domestic company (prefixes 10, 12, 14 with a valid PIB) or the register could not answer at issuance. oneOf: - $ref: '#/components/schemas/FiscalBuyerDetails' - type: 'null' status: { $ref: '#/components/schemas/FiscalDocumentStatus' } fiscalized: { type: boolean } failureCode: { type: [string, 'null'] } pfrRejection: description: Present only on a single-document read of a REJECTED operation. The V-PFR's own validation result, bounded to the property path of the caller's request and the supplier's numeric codes (2310 invalid tax label; 2800 to 2808 required, length, range, value, format and list-size validation). Lists never carry it. oneOf: - type: 'null' - type: object additionalProperties: false required: [items] properties: items: type: array maxItems: 10 items: type: object additionalProperties: false required: [property, codes] properties: property: { type: string, maxLength: 120 } codes: type: array maxItems: 5 items: { type: string, maxLength: 16 } retryable: { type: boolean } pfr: oneOf: - $ref: '#/components/schemas/PfrResult' - type: 'null' receipt: oneOf: - $ref: '#/components/schemas/ReceiptRepresentations' - type: 'null' reference: $ref: '#/components/schemas/ResolvedReference' createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } TurnoverReport: type: object additionalProperties: false required: - source - periodBasis - amountConvention - taxpayerId - businessPremiseId - pfrFrom - pfrTo - documentCount - firstDocument - lastDocument - paymentTotals - taxTotals properties: source: { type: string, const: BOKA_LOCAL_IMMUTABLE_PFR } periodBasis: { type: string, const: PFR_SDC_TIME } amountConvention: { type: string, const: SALE_REFUND_SEPARATE } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } pfrFrom: type: string format: date-time description: Inclusive authoritative PFR signing-time bound. pfrTo: type: string format: date-time description: Exclusive authoritative PFR signing-time bound. documentCount: { type: integer, format: int64, minimum: 0 } firstDocument: oneOf: - $ref: '#/components/schemas/TurnoverBoundary' - type: 'null' lastDocument: oneOf: - $ref: '#/components/schemas/TurnoverBoundary' - type: 'null' paymentTotals: type: array items: { $ref: '#/components/schemas/TurnoverPaymentTotal' } taxTotals: type: array items: { $ref: '#/components/schemas/TurnoverTaxTotal' } TurnoverBoundary: type: object additionalProperties: false required: [fiscalDocumentId, pfrInvoiceNumber, pfrTime] properties: fiscalDocumentId: { type: string, format: uuid } pfrInvoiceNumber: { type: string } pfrTime: { type: string, format: date-time } TurnoverPaymentTotal: type: object additionalProperties: false required: [securityElementJid, paymentType, saleAmount, refundAmount] properties: securityElementJid: { type: string } paymentType: { $ref: '#/components/schemas/PaymentType' } saleAmount: { type: number, minimum: 0 } refundAmount: { type: number, minimum: 0 } TurnoverTaxTotal: type: object additionalProperties: false required: - invoiceType - categoryType - label - rate - categoryName - saleTaxableAmount - saleTaxAmount - saleTotalAmount - refundTaxableAmount - refundTaxAmount - refundTotalAmount properties: invoiceType: type: string enum: [NORMAL, ADVANCE] categoryType: { type: integer, minimum: 0, maximum: 2 } label: { type: string } rate: { type: number, minimum: 0 } categoryName: { type: string } saleTaxableAmount: type: number minimum: 0 description: Sum of the Boka-derived taxable base per label (see ReceiptCanonicalTaxItem.taxableAmountPerLabel) over the Sale documents in the period; four decimals, as the V-PFR tax amounts it is reconciled with. saleTaxAmount: type: number minimum: 0 description: Sum of the V-PFR signed tax amounts for the label over the Sale documents in the period. saleTotalAmount: type: number minimum: 0 description: Gross of the receipt lines carrying the label over the Sale documents in the period (taxable base plus tax). refundTaxableAmount: { type: number, minimum: 0, description: As saleTaxableAmount, over the Refund documents. } refundTaxAmount: { type: number, minimum: 0, description: As saleTaxAmount, over the Refund documents. } refundTotalAmount: { type: number, minimum: 0, description: As saleTotalAmount, over the Refund documents. } Cashier: type: object additionalProperties: false required: [id] properties: id: { type: string } displayName: { type: string } Buyer: type: object additionalProperties: false properties: id: type: string description: Official prefix and value, for example 10:123456789. optionalField: type: string description: Official buyer-cost-center prefix and value where applicable. LineItem: type: object additionalProperties: false required: [name, quantity, unitPrice, taxLabels] properties: catalogProductId: type: string format: uuid description: Optional; arbitrary inline items are permitted. name: { type: string, minLength: 1, maxLength: 2048 } unitOfMeasure: type: string minLength: 1 maxLength: 50 description: "Required on every item except the codebook advance literals (10: Аванс (Ђ) and siblings), which are prescribed verbatim without a unit. The API refuses any other item without one (422, Items.UnitOfMeasure) and composes it into the signed item name as name/unit." quantity: type: number minimum: 0.001 maximum: 99999999999.999 multipleOf: 0.001 description: V-PFR Decimal(14,3). unitPrice: type: number minimum: 0 maximum: 999999999999999999999999.99 multipleOf: 0.01 description: Final gross unit price sent to V-PFR as Decimal(28,4). Boka applies the mandated fiscal rounding rules. unitPriceBeforeDiscount: type: number minimum: 0 maximum: 999999999999999999999999.9999 multipleOf: 0.0001 description: Optional Boka-local immutable gross unit price before discount. When present it must be greater than unitPrice; it is displayed outside the exact PFR journal and is never sent as a supplier field. gtin: { type: string, minLength: 8, maxLength: 14 } taxLabels: type: array minItems: 1 uniqueItems: true items: { type: string } Payment: type: object additionalProperties: false required: [type, amount] properties: type: { $ref: '#/components/schemas/PaymentType' } amount: type: number minimum: 0 maximum: 999999999999999999999999.99 multipleOf: 0.01 description: At most two decimals (the V-PFR rejects more with validation code 2804); the field type on the wire is Decimal(28,4). ReferenceTarget: oneOf: - $ref: '#/components/schemas/BokaDocumentReference' - $ref: '#/components/schemas/ExternalFiscalReference' BokaDocumentReference: type: object additionalProperties: false required: [source, fiscalDocumentId] properties: source: { type: string, const: BOKA } fiscalDocumentId: { type: string, format: uuid } ExternalFiscalReference: type: object additionalProperties: false required: [source, pfrNumber, pfrTime, invoiceType, transactionType] properties: source: { type: string, const: EXTERNAL } pfrNumber: { type: string } pfrTime: { type: string, format: date-time } invoiceType: { $ref: '#/components/schemas/InvoiceType' } transactionType: { $ref: '#/components/schemas/TransactionType' } ResolvedReference: type: object required: [pfrNumber, pfrTime] properties: fiscalDocumentId: { type: string, format: uuid } pfrNumber: { type: string } pfrTime: { type: string, format: date-time } PfrResult: type: object description: Safe journal subset; opaque internal fiscal data and signature values are never exposed. required: [invoiceNumber, sdcTime, requestedBy, signedBy, totalAmount, verificationUrl, journal] properties: invoiceNumber: { type: string } sdcTime: { type: string, format: date-time } requestedBy: { type: string } signedBy: { type: string } transactionTypeCounter: type: [integer, 'null'] description: V-PFR transaction-type counter (the left half of the printed counter). Null when the stored receipt does not carry it; the printed counter is always in the journal text. totalCounter: type: [integer, 'null'] description: V-PFR total counter (the right half of the printed counter and the last segment of invoiceNumber). Null under the same condition as transactionTypeCounter. invoiceCounterExtension: type: [string, 'null'] description: Official Cyrillic invoice/transaction type pair printed after the counter (ПП, ПР, АП, АР, КП, КР, ОП, ОР, РП, РР). Null under the same condition as transactionTypeCounter. totalAmount: { type: number } totalTax: type: [number, 'null'] description: Sum of the V-PFR signed tax amounts of the document (the printed "Укупан износ пореза" before its two-decimal display rounding). Null under the same condition as transactionTypeCounter. verificationUrl: { type: string, format: uri } journal: { type: string } ReceiptRepresentations: type: object additionalProperties: false required: [textUrl, jsonUrl, pdfA4Url, pdf80mmUrl, pdf58mmUrl, previewImageUrl, qrImageUrl, verificationUrl, preferredPaperFormat] properties: textUrl: { type: string, format: uri-reference } jsonUrl: { type: string, format: uri-reference } pdfA4Url: { type: string, format: uri-reference } pdf80mmUrl: { type: string, format: uri-reference } pdf58mmUrl: { type: string, format: uri-reference } previewImageUrl: { type: string, format: uri-reference } qrImageUrl: { type: string, format: uri-reference } verificationUrl: { type: string, format: uri } preferredPaperFormat: type: string enum: [80mm, 58mm, a4] description: Immutable preferred print/PDF format captured when this fiscal operation was reserved. ReceiptCanonicalPackage: type: object additionalProperties: false required: - schemaVersion - sourceSha256 - canonicalRequestSha256 - originalPfrResponseSha256 - fiscalDocumentId - taxpayerId - businessPremiseId - clientReference - invoiceType - transactionType - request - pfr - branding properties: schemaVersion: { type: string, const: boka-receipt-representation-v21 } sourceSha256: { type: string, pattern: '^[0-9a-f]{64}$' } canonicalRequestSha256: { type: string, pattern: '^[0-9a-f]{64}$' } originalPfrResponseSha256: { type: string, pattern: '^[0-9a-f]{64}$' } fiscalDocumentId: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } clientReference: { type: string } invoiceType: { $ref: '#/components/schemas/InvoiceType' } transactionType: { $ref: '#/components/schemas/TransactionType' } request: { $ref: '#/components/schemas/ReceiptCanonicalRequest' } pfr: { $ref: '#/components/schemas/ReceiptCanonicalPfr' } branding: oneOf: - $ref: '#/components/schemas/ReceiptCanonicalBranding' - type: 'null' ReceiptCanonicalBranding: type: object additionalProperties: false required: [revisionId, contentSha256, preferredPaperFormat] properties: revisionId: { type: string, format: uuid } contentSha256: { type: string, pattern: '^[0-9a-f]{64}$' } businessPremiseId: { type: [string, 'null'], format: uuid } headerLogoAssetId: { type: [string, 'null'], format: uuid } headerLogoSha256: { type: [string, 'null'], pattern: '^[0-9a-f]{64}$' } footerLogoAssetId: { type: [string, 'null'], format: uuid } footerLogoSha256: { type: [string, 'null'], pattern: '^[0-9a-f]{64}$' } displayName: { type: [string, 'null'] } website: { type: [string, 'null'], format: uri } phone: { type: [string, 'null'] } email: { type: [string, 'null'], format: email } thankYouSr: { type: [string, 'null'] } thankYouEn: { type: [string, 'null'] } supportSr: { type: [string, 'null'] } supportEn: { type: [string, 'null'] } preferredPaperFormat: { type: string, enum: [80mm, 58mm, a4] } FiscalDocumentCopyCreate: type: object additionalProperties: false required: [cashier] properties: cashier: { $ref: '#/components/schemas/Cashier' } clientReference: type: [string, 'null'] maxLength: 200 description: Defaults to COPY- followed by the original's client reference. ReceiptCanonicalRequest: type: object additionalProperties: false required: [cashierId, items, payments] properties: cashierId: { type: string } cashierDisplayName: { type: [string, 'null'] } buyer: oneOf: - $ref: '#/components/schemas/Buyer' - type: 'null' items: type: array minItems: 1 items: { $ref: '#/components/schemas/LineItem' } payments: type: array minItems: 1 items: { $ref: '#/components/schemas/Payment' } reference: oneOf: - $ref: '#/components/schemas/ReceiptCanonicalReference' - type: 'null' transactionOccurredAt: { type: [string, 'null'], format: date-time } commercialFooter: type: [string, 'null'] maxLength: 2000 description: Non-fiscal text rendered only outside the marked fiscal boundary. buyerDetails: description: The buyer's company-registry facts stored with the operation; printed under the buyer identification in every representation. Absent or null when none were resolved. oneOf: - $ref: '#/components/schemas/FiscalBuyerDetails' - type: 'null' FiscalBuyerDetails: type: object additionalProperties: false required: [legalName, taxIdentifier, source, resolvedAt] properties: legalName: { type: string, maxLength: 350 } taxIdentifier: { type: string, pattern: '^[0-9]{9}$' } registrationNumber: { type: [string, 'null'], pattern: '^[0-9]{8}$' } address: { type: [string, 'null'], maxLength: 300 } city: { type: [string, 'null'], maxLength: 100 } source: { type: string, enum: [nbs-jrr] } resolvedAt: { type: string, format: date-time } BuyerRegistryLookup: type: object additionalProperties: false required: [source, taxIdentifier, legalName, resolvedAt, fromStore] properties: source: { type: string, enum: [nbs-jrr] } taxIdentifier: { type: string, pattern: '^[0-9]{9}$' } legalName: { type: string, maxLength: 350 } registrationNumber: { type: [string, 'null'], pattern: '^[0-9]{8}$' } address: { type: [string, 'null'], maxLength: 300 } city: { type: [string, 'null'], maxLength: 100 } resolvedAt: { type: string, format: date-time } fromStore: type: boolean description: True when the answer came from the organization's stored registry answer (younger than 24 hours) rather than a fresh register call. ReceiptCanonicalReference: type: object additionalProperties: false required: [source] properties: source: { type: string, enum: [BOKA, EXTERNAL] } fiscalDocumentId: { type: [string, 'null'], format: uuid } pfrNumber: { type: [string, 'null'] } pfrTime: { type: [string, 'null'], format: date-time } ReceiptCanonicalPfr: type: object additionalProperties: false required: - invoiceNumber - sdcTime - requestedBy - signedBy - invoiceCounter - invoiceCounterExtension - totalCounter - transactionTypeCounter - totalAmount - taxGroupRevision - taxItems - businessName - tin - locationName - address - district - verificationUrl - officialJournal - officialJournalSha256 - opaqueFiscalDataStored properties: invoiceNumber: { type: string } sdcTime: { type: string, format: date-time } requestedBy: { type: string } signedBy: { type: string } invoiceCounter: { type: string } invoiceCounterExtension: { type: string } totalCounter: { type: integer } transactionTypeCounter: { type: integer } totalAmount: { type: number } taxGroupRevision: { type: integer } taxItems: type: array minItems: 1 items: { $ref: '#/components/schemas/ReceiptCanonicalTaxItem' } businessName: { type: string } tin: { type: string } locationName: { type: string } address: { type: string } district: { type: string } mrc: { type: [string, 'null'] } verificationUrl: { type: string, format: uri } officialJournal: { type: string } officialJournalSha256: { type: string, pattern: '^[0-9a-f]{64}$' } opaqueFiscalDataStored: type: boolean const: true description: Confirms presence in the hashed original response without exposing opaque values. ReceiptCanonicalTaxItem: type: object additionalProperties: false required: [categoryType, label, amount, rate, categoryName, taxableAmountPerLabel] properties: categoryType: { type: integer, minimum: 0, maximum: 2 } label: { type: string } amount: { type: number, minimum: 0 } rate: { type: number, minimum: 0 } categoryName: { type: string } taxableAmountPerLabel: type: number minimum: 0 description: | Boka-derived, not signed by the V-PFR. The v3 Create Invoice response carries only label, categoryName, categoryType, rate and amount per tax item; the v4 protocol (deployed only in Eswatini) defines this field as the sum of the POS-supplied per-item taxable amounts. BokaPOS computes it the same way from the stored receipt lines: the gross of every line carrying the label, minus that label's V-PFR tax amount. For a single-label line this is exactly the official value; a line with several labels contributes its full gross to each of them. RefundWorkflowCreate: type: object additionalProperties: false required: [taxpayerId, businessPremiseId, clientReference, original, cashier, buyer, items, payments] properties: taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } clientReference: { type: string } original: { $ref: '#/components/schemas/BokaDocumentReference' } cashier: { $ref: '#/components/schemas/Cashier' } buyer: { $ref: '#/components/schemas/Buyer' } items: type: array minItems: 1 items: { $ref: '#/components/schemas/RefundWorkflowLineItem' } payments: type: array minItems: 1 items: { $ref: '#/components/schemas/Payment' } RefundWorkflowLineItem: type: object additionalProperties: false required: [originalLineIndex, name, quantity, unitPrice, taxLabels] properties: originalLineIndex: type: integer minimum: 0 description: Zero-based index of the exact line on the identified original receipt. catalogProductId: type: string format: uuid description: Optional; must match the Boka-stored original line when source is BOKA. name: { type: string, minLength: 1, maxLength: 2048 } unitOfMeasure: type: string minLength: 1 maxLength: 50 description: "Required on every item except the codebook advance literals (10: Аванс (Ђ) and siblings), which are prescribed verbatim without a unit. The API refuses any other item without one (422, Items.UnitOfMeasure) and composes it into the signed item name as name/unit." quantity: type: number minimum: 0.001 maximum: 99999999999.999 multipleOf: 0.001 description: Quantity returned from this original line. unitPrice: type: number minimum: 0 maximum: 999999999999999999999999.99 multipleOf: 0.01 description: Must match the final gross unit price on the identified original line when source is BOKA. unitPriceBeforeDiscount: type: number minimum: 0 maximum: 999999999999999999999999.9999 multipleOf: 0.0001 description: Optional Boka-local pre-discount price; when the source line has it, the value must match exactly and remain greater than unitPrice. gtin: { type: string, minLength: 8, maxLength: 14 } taxLabels: type: array minItems: 1 uniqueItems: true items: { type: string } RefundWorkflow: type: object additionalProperties: false required: - id - taxpayerId - businessPremiseId - clientReference - status - original - refund - createdAt - updatedAt properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } clientReference: { type: string } status: type: string enum: [REFUND_PENDING, REFUND_OUTCOME_UNKNOWN, COPY_PENDING, COPY_OUTCOME_UNKNOWN, COMPLETED, FAILED] original: { $ref: '#/components/schemas/RefundWorkflowOriginal' } refund: { $ref: '#/components/schemas/FiscalDocument' } cashRefundCopy: oneOf: - $ref: '#/components/schemas/FiscalDocument' - type: 'null' failureCode: { type: [string, 'null'] } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } RefundWorkflowOriginal: type: object additionalProperties: false required: [source, fiscalDocumentId, pfrNumber, pfrTime, invoiceType, transactionType] properties: source: { type: string, const: BOKA } fiscalDocumentId: { type: string, format: uuid } pfrNumber: { type: string } pfrTime: { type: string, format: date-time } invoiceType: { $ref: '#/components/schemas/InvoiceType' } transactionType: { $ref: '#/components/schemas/TransactionType' } ProformaTrainingWorkflowCreate: type: object additionalProperties: false required: [taxpayerId, businessPremiseId, clientReference, invoiceType, transactionType, cashier, items, payments] properties: taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } clientReference: { type: string, minLength: 1, maxLength: 200 } invoiceType: type: string enum: [PROFORMA, TRAINING] transactionType: { $ref: '#/components/schemas/TransactionType' } cashier: { $ref: '#/components/schemas/Cashier' } buyer: { $ref: '#/components/schemas/Buyer' } original: { $ref: '#/components/schemas/BokaDocumentReference' } items: type: array minItems: 1 items: { $ref: '#/components/schemas/LineItem' } payments: type: array minItems: 1 items: { $ref: '#/components/schemas/Payment' } commercialFooter: { type: string, maxLength: 2000 } metadata: type: object additionalProperties: { type: string } description: Non-fiscal caller metadata. ProformaTrainingWorkflow: type: object additionalProperties: false required: - id - taxpayerId - businessPremiseId - clientReference - invoiceType - transactionType - state - document - createdAt - updatedAt properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } clientReference: { type: string } invoiceType: type: string enum: [PROFORMA, TRAINING] transactionType: { $ref: '#/components/schemas/TransactionType' } state: type: string enum: [PENDING, COMPLETED, OUTCOME_UNKNOWN, REJECTED, FAILED] original: oneOf: - $ref: '#/components/schemas/ProformaTrainingWorkflowOriginal' - type: 'null' document: { $ref: '#/components/schemas/FiscalDocument' } failureCode: { type: [string, 'null'] } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } ProformaTrainingWorkflowOriginal: type: object additionalProperties: false required: [source, fiscalDocumentId, pfrNumber, pfrTime, invoiceType, transactionType] properties: source: { type: string, const: BOKA } fiscalDocumentId: { type: string, format: uuid } pfrNumber: { type: string } pfrTime: { type: string, format: date-time } invoiceType: type: string enum: [PROFORMA, TRAINING] transactionType: { $ref: '#/components/schemas/TransactionType' } ProformaTrainingWorkflowConflict: type: object additionalProperties: false required: [code] properties: code: { type: string } workflowId: { type: string, format: uuid } sourceFiscalDocumentId: { type: string, format: uuid } FiscalUnavailableError: type: object additionalProperties: false required: [code, fiscalized] properties: code: { type: string } fiscalized: { type: boolean, const: false } AdvanceCaseCreate: type: object additionalProperties: false required: [taxpayerId, businessPremiseId, clientReference] properties: taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } clientReference: { type: string, minLength: 1, maxLength: 200 } externalAdvance: oneOf: - $ref: '#/components/schemas/ExternalAdvance' - type: 'null' description: | Advances collected before eFiscalization that this case closes. The first Advance Sale then references the last pre-fiscal document as XXXXXXXX-XXXXXXXX-, or the close starts with the Advance Refund referencing it when no Advance Sale was fiscalized, and the closing Advance Refund sums pre-fiscal and fiscal advances alike. ExternalAdvance: type: object additionalProperties: false required: [amount, taxLabel, paymentType, lastDocumentNumber, lastDocumentTime] properties: amount: type: number exclusiveMinimum: 0 multipleOf: 0.01 description: Sum of every advance collected before eFiscalization; at most two decimals. taxLabel: type: string description: Tax label of the future supply; must be a prescribed advance label. paymentType: { $ref: '#/components/schemas/PaymentType' } lastDocumentNumber: type: string minLength: 1 maxLength: 20 pattern: '^[0-9]{1,20}$' description: | Number of the last pre-fiscal advance document, digits only (1 to 20). It is sent as the `` part of the reference `XXXXXXXX-XXXXXXXX-`; the official examples are `17`, `121` and `159`, and the V-PFR rejects any other form. Anything but digits returns 422 with the `ExternalAdvance.LastDocumentNumber` validation key. lastDocumentTime: type: string format: date-time description: Issue date of that document; must precede the request. AdvanceCancellationCreate: type: object additionalProperties: false required: [cashier, advanceSaleFiscalDocumentId] properties: cashier: { $ref: '#/components/schemas/Cashier' } advanceSaleFiscalDocumentId: type: string format: uuid description: The latest fiscalized Advance Sale of the open case; nothing else can be cancelled. AdvancePaymentCreate: type: object additionalProperties: false required: [cashier, items, payments, commercialFooter] properties: cashier: { $ref: '#/components/schemas/Cashier' } buyer: { $ref: '#/components/schemas/Buyer' } paymentOccurredAt: type: string format: date-time description: | Actual earlier payment time for the documented wire-transfer case. When supplied, it must precede the fiscalization attempt and at least one payment must be Wire Transfer. items: type: array minItems: 1 items: { $ref: '#/components/schemas/LineItem' } payments: type: array minItems: 1 items: { $ref: '#/components/schemas/Payment' } commercialFooter: type: string minLength: 1 maxLength: 2000 description: Mandatory non-fiscal commercial area for the Advance Sale. AdvanceCaseClose: type: object additionalProperties: false required: [cashier, finalItems, remainingPayments] properties: cashier: { $ref: '#/components/schemas/Cashier' } buyer: { $ref: '#/components/schemas/Buyer' } finalItems: type: array minItems: 1 items: { $ref: '#/components/schemas/LineItem' } remainingPayments: type: array minItems: 1 items: { $ref: '#/components/schemas/Payment' } description: | Must equal the final amount less the stored advance amount. Supply one explicit zero-amount element when the remaining balance is zero, because Create Invoice requires at least one payment element. commercialFooter: { type: string, maxLength: 1500 } AdvanceCase: type: object additionalProperties: false required: [id, taxpayerId, businessPremiseId, clientReference, state, advanceSales, cancellations, cancelledAdvanceSaleIds, createdAt, updatedAt] properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } clientReference: { type: string } externalAdvance: oneOf: - $ref: '#/components/schemas/ExternalAdvance' - type: 'null' state: type: string enum: - OPEN - ADVANCE_SALE_OUTCOME_UNKNOWN - CLOSING - ADVANCE_REFUND_OUTCOME_UNKNOWN - ADVANCE_REFUND_FISCALIZED_SALE_PENDING - FINAL_SALE_OUTCOME_UNKNOWN - CLOSED - FAILED advanceSales: type: array items: { $ref: '#/components/schemas/FiscalDocument' } cancellations: type: array description: Standalone Advance Refunds that cancelled an erroneous Advance Sale of this case, in issue order. items: { $ref: '#/components/schemas/FiscalDocument' } cancelledAdvanceSaleIds: type: array description: | Ids of the Advance Sales in `advanceSales` that a fiscalized cancellation removed from the chain. The remaining fiscalized Advance Sales are the effective chain: they are what the next Advance Sale references, what a closure refunds, and what the next cancellation targets. items: { type: string, format: uuid } advanceRefund: oneOf: - $ref: '#/components/schemas/FiscalDocument' - type: 'null' finalSale: oneOf: - $ref: '#/components/schemas/FiscalDocument' - type: 'null' failureCode: { type: [string, 'null'] } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } AdvanceCasePage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/AdvanceCase' } AdvanceCloseResult: type: object additionalProperties: false required: [case] properties: case: { $ref: '#/components/schemas/AdvanceCase' } ReceiptDeliveryLanguage: type: string enum: [sr-Cyrl, sr-Latn, en] description: Language of the message; the receipt itself is the official journal rendered into the attached PDFs. ReceiptDeliveryAttachment: type: string enum: [a4, 80mm, 58mm, png] description: A PDF representation attached to the message. The official verification link is always in the message; attachments are convenience copies. ReceiptDeliveryAttachments: type: array items: { $ref: '#/components/schemas/ReceiptDeliveryAttachment' } maxItems: 4 uniqueItems: true description: Returned in canonical order (a4, 80mm, 58mm, png). An empty list sends the link only. ReceiptDeliveryCreate: type: object additionalProperties: false required: [fiscalDocumentId, channel, recipient] properties: fiscalDocumentId: { type: string, format: uuid } channel: { type: string, const: EMAIL } recipient: { type: string, format: email, maxLength: 320 } language: oneOf: - $ref: '#/components/schemas/ReceiptDeliveryLanguage' - type: 'null' description: Defaults to the taxpayer's setting. attachments: oneOf: - $ref: '#/components/schemas/ReceiptDeliveryAttachments' - type: 'null' description: Defaults to the taxpayer's setting. ReceiptDelivery: type: object additionalProperties: false required: [id, fiscalDocumentId, channel, recipient, language, attachments, status, attempts, failureCode, createdAt, deliveredAt, updatedAt] properties: id: { type: string, format: uuid } fiscalDocumentId: { type: string, format: uuid } channel: { type: string, const: EMAIL } recipient: { type: string } language: { $ref: '#/components/schemas/ReceiptDeliveryLanguage' } attachments: { $ref: '#/components/schemas/ReceiptDeliveryAttachments' } status: { type: string, enum: [QUEUED, SENDING, DELIVERED, FAILED] } attempts: { type: integer, minimum: 0 } failureCode: { type: [string, 'null'] } createdAt: { type: string, format: date-time } deliveredAt: { type: [string, 'null'], format: date-time } updatedAt: { type: string, format: date-time } ReceiptDeliveryPremiseSenderName: type: object additionalProperties: false required: [businessPremiseId, senderDisplayName] properties: businessPremiseId: { type: string, format: uuid } senderDisplayName: { type: string, maxLength: 120 } ReceiptDeliverySettingsWrite: type: object additionalProperties: false description: | The E-mail receipts module customization. Templates accept the placeholders {receiptNumber}, {kind}, {total}, {issuer}, {premise} and {date}; Serbian templates serve both scripts. The official verification link and the legal line are always in the message and cannot be templated away. The platform mailbox stays the From address; the sender display name is the issuer's name shown to the customer. required: [mode, language] properties: mode: type: string enum: [disabled, email] description: disabled means the taxpayer delivers receipts through its own system. language: { $ref: '#/components/schemas/ReceiptDeliveryLanguage' } attachments: oneOf: - $ref: '#/components/schemas/ReceiptDeliveryAttachments' - type: 'null' description: Omitted or null means the default, a4 and 80mm; an empty list means no attachments. senderDisplayName: { type: [string, 'null'], maxLength: 120 } replyToAddress: { type: [string, 'null'], maxLength: 320 } internalCopyAddress: type: [string, 'null'] maxLength: 320 description: Receives a blind copy of every receipt e-mail; never used for test messages. includeQrCode: { type: boolean, default: false } subjectSr: { type: [string, 'null'], maxLength: 200 } subjectEn: { type: [string, 'null'], maxLength: 200 } introSr: { type: [string, 'null'], maxLength: 2000 } introEn: { type: [string, 'null'], maxLength: 2000 } footerSr: { type: [string, 'null'], maxLength: 1000 } footerEn: { type: [string, 'null'], maxLength: 1000 } premiseSenderNames: type: [array, 'null'] items: { $ref: '#/components/schemas/ReceiptDeliveryPremiseSenderName' } ReceiptDeliverySettings: type: object additionalProperties: false required: [taxpayerId, mode, language, attachments, configured, updatedAt, senderDisplayName, replyToAddress, internalCopyAddress, includeQrCode, subjectSr, subjectEn, introSr, introEn, footerSr, footerEn, premiseSenderNames, placeholders, defaultTemplates] properties: taxpayerId: { type: string, format: uuid } mode: { type: string, enum: [disabled, email] } language: { $ref: '#/components/schemas/ReceiptDeliveryLanguage' } attachments: { $ref: '#/components/schemas/ReceiptDeliveryAttachments' } configured: type: boolean description: false when no settings row exists yet (the defaults above apply). updatedAt: { type: [string, 'null'], format: date-time } senderDisplayName: { type: [string, 'null'] } replyToAddress: { type: [string, 'null'] } internalCopyAddress: { type: [string, 'null'] } includeQrCode: { type: boolean } subjectSr: { type: [string, 'null'] } subjectEn: { type: [string, 'null'] } introSr: { type: [string, 'null'] } introEn: { type: [string, 'null'] } footerSr: { type: [string, 'null'] } footerEn: { type: [string, 'null'] } premiseSenderNames: type: array items: { $ref: '#/components/schemas/ReceiptDeliveryPremiseSenderName' } placeholders: type: array items: { type: string } description: The placeholders the templates accept. defaultTemplates: type: object additionalProperties: false description: The built-in text per language, used when a stored template is empty; every placeholder appears in it. required: [subjectSr, introSr, footerSr, subjectEn, introEn, footerEn] properties: subjectSr: { type: string } introSr: { type: string } footerSr: { type: string } subjectEn: { type: string } introEn: { type: string } footerEn: { type: string } ReceiptDeliveryPreviewRequest: type: object additionalProperties: false required: [settings] properties: settings: { $ref: '#/components/schemas/ReceiptDeliverySettingsWrite' } language: oneOf: - $ref: '#/components/schemas/ReceiptDeliveryLanguage' - type: 'null' ReceiptDeliveryTestMessageRequest: type: object additionalProperties: false required: [recipient] properties: recipient: { type: string, maxLength: 320 } language: oneOf: - $ref: '#/components/schemas/ReceiptDeliveryLanguage' - type: 'null' Operation: type: object additionalProperties: false required: [id, kind, status, fiscalized, failureCode, retryable, resourceUrl, createdAt, updatedAt] properties: id: { type: string, format: uuid } kind: { type: string, const: FISCAL_DOCUMENT } status: { $ref: '#/components/schemas/FiscalDocumentStatus' } fiscalized: { type: boolean } failureCode: { type: [string, 'null'] } retryable: { type: boolean } resourceUrl: { type: string, format: uri-reference } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } ReceiptBrandingAssetUpload: type: object additionalProperties: false required: [taxpayerId, displayName, alternativeText, mediaType, fileBase64] properties: taxpayerId: { type: string, format: uuid } displayName: { type: string, minLength: 1, maxLength: 120 } alternativeText: { type: string, minLength: 1, maxLength: 200 } mediaType: { type: string, enum: [image/png, image/webp] } fileBase64: type: string format: byte description: Base64 encoded image; decoded input is limited to 2,000,000 bytes. ReceiptBrandingAsset: type: object additionalProperties: false required: [id, taxpayerId, displayName, alternativeText, width, height, colorSha256, thermalSha256, contentUrl, createdAt] properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } displayName: { type: string } alternativeText: { type: string } width: { type: integer, minimum: 1, maximum: 2048 } height: { type: integer, minimum: 1, maximum: 2048 } colorSha256: { type: string, pattern: '^[0-9a-f]{64}$' } thermalSha256: { type: string, pattern: '^[0-9a-f]{64}$' } contentUrl: { type: string, format: uri-reference } createdAt: { type: string, format: date-time } ReceiptBrandingAssetPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/ReceiptBrandingAsset' } ReceiptBrandingProfileWrite: type: object additionalProperties: false properties: headerLogoAssetId: { type: [string, 'null'], format: uuid } footerLogoAssetId: { type: [string, 'null'], format: uuid } displayName: { type: [string, 'null'], maxLength: 200 } website: { type: [string, 'null'], format: uri, maxLength: 300 } phone: { type: [string, 'null'], maxLength: 80 } email: { type: [string, 'null'], format: email, maxLength: 320 } thankYouSr: { type: [string, 'null'], maxLength: 300 } thankYouEn: { type: [string, 'null'], maxLength: 300 } supportSr: { type: [string, 'null'], maxLength: 500 } supportEn: { type: [string, 'null'], maxLength: 500 } preferredPaperFormat: { type: string, enum: [80mm, 58mm, a4], default: 80mm } a4Language: type: string enum: [sr-Cyrl, sr-Latn, en] default: sr-Cyrl description: Language used by the structured A4 receipt presentation. Signed PFR identifiers remain unchanged. a4AccentPalette: type: string enum: [boka-blue, adriatic, plum, forest, graphite] default: boka-blue description: Reviewed accessible accent palette for the A4 presentation; thermal output remains monochrome. a4Template: type: string enum: [branded, classic] default: branded description: Branded preserves the configurable color presentation; classic uses a neutral monochrome TAP-inspired document layout. ReceiptBrandingRevision: type: object additionalProperties: false required: [id, taxpayerId, preferredPaperFormat, a4Language, a4AccentPalette, a4Template, contentSha256, createdAt] properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: [string, 'null'], format: uuid } headerLogoAssetId: { type: [string, 'null'], format: uuid } footerLogoAssetId: { type: [string, 'null'], format: uuid } displayName: { type: [string, 'null'] } website: { type: [string, 'null'], format: uri } phone: { type: [string, 'null'] } email: { type: [string, 'null'], format: email } thankYouSr: { type: [string, 'null'] } thankYouEn: { type: [string, 'null'] } supportSr: { type: [string, 'null'] } supportEn: { type: [string, 'null'] } preferredPaperFormat: { type: string, enum: [80mm, 58mm, a4] } a4Language: { type: string, enum: [sr-Cyrl, sr-Latn, en] } a4AccentPalette: { type: string, enum: [boka-blue, adriatic, plum, forest, graphite] } a4Template: { type: string, enum: [branded, classic] } contentSha256: { type: string, pattern: '^[0-9a-f]{64}$' } createdAt: { type: string, format: date-time } ReceiptBrandingProfile: type: object additionalProperties: false required: [taxpayerId, businessPremiseId, configured, inherited, revision] properties: taxpayerId: { type: string, format: uuid } businessPremiseId: { type: [string, 'null'], format: uuid } configured: { type: boolean } inherited: { type: boolean } revision: oneOf: - $ref: '#/components/schemas/ReceiptBrandingRevision' - type: 'null' OperatorPreferenceWrite: type: object additionalProperties: false required: [cashierId] properties: cashierId: { type: string, minLength: 1, maxLength: 50 } cashierDisplayName: { type: [string, 'null'], maxLength: 200 } language: type: [string, 'null'] enum: [sr, en, null] description: Portal language of this member's account; null leaves it unchanged. OperatorPreference: type: object additionalProperties: false required: [cashierId, cashierDisplayName, language, updatedAt] properties: cashierId: { type: [string, 'null'], maxLength: 50 } cashierDisplayName: { type: string, minLength: 1, maxLength: 200 } language: type: [string, 'null'] enum: [sr, en, null] description: Portal language chosen for the account; null follows the browser. updatedAt: { type: [string, 'null'], format: date-time } SandboxDataVisibility: type: string enum: [auto, shown, hidden] description: | Whether customer humans see the organization's sandbox data (the BOKA sandbox taxpayer and premise, the sandbox element and every sandbox document) next to production data. `auto` hides it once the organization is production-ready. OrganizationSettings: type: object additionalProperties: false required: [sandboxDataVisibility, effectiveSandboxDataVisibility, productionReady, sandboxDataVisibilityUpdatedAt] properties: sandboxDataVisibility: { $ref: '#/components/schemas/SandboxDataVisibility' } effectiveSandboxDataVisibility: type: string enum: [shown, hidden] description: What customer humans currently get, after resolving `auto`. productionReady: type: boolean description: True once an active production security element or an active production API credential exists. sandboxDataVisibilityUpdatedAt: type: [string, 'null'] format: date-time description: When a human last changed the setting; null while the default applies. OrganizationSettingsWrite: type: object additionalProperties: false required: [sandboxDataVisibility] properties: sandboxDataVisibility: { $ref: '#/components/schemas/SandboxDataVisibility' } ProductCreate: type: object additionalProperties: false required: [taxpayerId, sku, name, grossUnitPrice, taxLabels, unitOfMeasure] properties: taxpayerId: { type: string, format: uuid } sku: { type: string, minLength: 1, maxLength: 100 } gtin: { type: [string, 'null'], maxLength: 32 } name: { type: string, minLength: 1, maxLength: 500 } unitOfMeasure: { type: string, minLength: 1, maxLength: 50 } grossUnitPrice: { type: number, minimum: 0, multipleOf: 0.01 } taxLabels: type: array minItems: 1 uniqueItems: true items: { type: string } Product: type: object additionalProperties: false required: [id, taxpayerId, sku, name, grossUnitPrice, taxLabels, isActive, createdAt, updatedAt] properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } sku: { type: string } gtin: { type: [string, 'null'] } name: { type: string } unitOfMeasure: { type: [string, 'null'] } grossUnitPrice: { type: number, minimum: 0 } taxLabels: type: array items: { type: string } isActive: { type: boolean } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } ProductUpdate: type: object additionalProperties: false required: [sku, name, grossUnitPrice, taxLabels, isActive, unitOfMeasure] properties: sku: { type: string, minLength: 1, maxLength: 100 } gtin: { type: [string, 'null'], maxLength: 32 } name: { type: string, minLength: 1, maxLength: 500 } unitOfMeasure: { type: string, minLength: 1, maxLength: 50 } grossUnitPrice: { type: number, minimum: 0, multipleOf: 0.01 } taxLabels: type: array minItems: 1 uniqueItems: true items: { type: string } isActive: { type: boolean } ProductPage: type: object additionalProperties: false required: [items, nextCursor] properties: items: type: array items: { $ref: '#/components/schemas/Product' } nextCursor: { type: [string, 'null'], format: uuid } ProductImportResult: type: object additionalProperties: false required: [created, updated, total] properties: created: { type: integer, minimum: 0 } updated: { type: integer, minimum: 0 } total: { type: integer, minimum: 1, maximum: 1000 } CurrentTaxConfiguration: type: object additionalProperties: false required: [source, environment, taxpayerId, businessPremiseId, currentTaxGroupId, validFrom, fetchedAt, labels] properties: source: { type: string, enum: [PFR] } environment: type: string enum: [sandbox, production] description: Environment of the active security element the configuration was read with; sandbox unlocks the sandbox-only advance item names. taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } currentTaxGroupId: { type: integer, minimum: 0 } validFrom: type: string description: Supplier-provided current-group validity value preserved without inventing a timezone. fetchedAt: { type: string, format: date-time } labels: type: array minItems: 1 items: { $ref: '#/components/schemas/TaxLabel' } TaxLabel: type: object additionalProperties: false required: [label, category, categoryType, rate, activeFrom] properties: label: { type: string } category: { type: string } categoryType: { type: integer, minimum: 0, maximum: 2 } rate: { type: number } activeFrom: type: string description: Supplier-provided current-group validity value preserved without inventing a timezone. TaxpayerCreate: type: object additionalProperties: false required: [taxIdentifier, legalName] description: The optional registry profile is a human-confirmed prefill from the company registry lookup; it is never fiscal input. properties: taxIdentifier: { type: string, pattern: '^[0-9]{9}$' } legalName: { type: string } registrationNumber: type: string nullable: true pattern: '^[0-9]{8}$' description: Serbian registration number (matični broj), usually prefilled from the company registry lookup. address: { type: string, nullable: true, maxLength: 300 } city: { type: string, nullable: true, maxLength: 100 } municipality: { type: string, nullable: true, maxLength: 100 } activityCode: { type: string, nullable: true, pattern: '^[0-9]{4}$' } activityName: { type: string, nullable: true, maxLength: 150 } vatStatus: type: [string, 'null'] enum: [in_vat, not_in_vat, null] description: Owner-declared VAT status. not_in_vat restricts every fiscal command to the non-VAT tax label (category N-TAX, label А in production); null means not declared and restricts nothing. TaxpayerUpdate: type: object additionalProperties: false required: [legalName, status] description: Registry profile fields omitted from the request stay unchanged; a blank string clears a field. A closed taxpayer is immutable in every field. properties: legalName: { type: string, minLength: 1, maxLength: 300 } status: { type: string, enum: [active, suspended, closed] } registrationNumber: type: string nullable: true pattern: '^[0-9]{8}$' description: Serbian registration number (matični broj), usually prefilled from the company registry lookup. address: { type: string, nullable: true, maxLength: 300 } city: { type: string, nullable: true, maxLength: 100 } municipality: { type: string, nullable: true, maxLength: 100 } activityCode: { type: string, nullable: true, pattern: '^[0-9]{4}$' } activityName: { type: string, nullable: true, maxLength: 150 } vatStatus: type: [string, 'null'] enum: [in_vat, not_in_vat, null] description: Owner-declared VAT status. not_in_vat restricts every fiscal command to the non-VAT tax label (category N-TAX, label А in production); null means not declared and restricts nothing. Taxpayer: type: object additionalProperties: false required: [id, taxIdentifier, legalName, environment, status, createdAt, updatedAt] properties: id: { type: string, format: uuid } taxIdentifier: { type: string, pattern: '^[0-9]{9}$' } legalName: { type: string } environment: $ref: '#/components/schemas/FiscalEnvironment' status: { type: string, enum: [active, suspended, closed] } registrationNumber: type: string nullable: true pattern: '^[0-9]{8}$' description: Serbian registration number (matični broj), usually prefilled from the company registry lookup. address: { type: string, nullable: true, maxLength: 300 } city: { type: string, nullable: true, maxLength: 100 } municipality: { type: string, nullable: true, maxLength: 100 } activityCode: { type: string, nullable: true, pattern: '^[0-9]{4}$' } activityName: { type: string, nullable: true, maxLength: 150 } vatStatus: type: [string, 'null'] enum: [in_vat, not_in_vat, null] description: Owner-declared VAT status. not_in_vat restricts every fiscal command to the non-VAT tax label (category N-TAX, label А in production); null means not declared and restricts nothing. createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } TaxpayerPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/Taxpayer' } BusinessPremiseCreate: type: object additionalProperties: false required: [puIdentifier, name, commerceMode] properties: puIdentifier: { type: string } name: { type: string } commerceMode: type: string const: distance description: The V-PFR-only approved version rejects in-person use. paymentMode: $ref: '#/components/schemas/PremisePaymentMode' PremisePaymentMode: type: string enum: [all, restricted] default: all description: >- Per-premise payment mode. The official rule is that one installation works either with every payment type or only with the restricted set (OTHER, CASH, WIRE_TRANSFER, VOUCHER). A payment outside the premise's set is refused with 422 PAYMENT_TYPE_NOT_ALLOWED_ON_PREMISE before any fiscal reservation, on every kind and transaction type. BusinessPremiseUpdate: type: object additionalProperties: false required: [name, status] properties: name: { type: string, minLength: 1, maxLength: 200 } status: { type: string, enum: [active, suspended, closed] } paymentMode: $ref: '#/components/schemas/PremisePaymentMode' BusinessPremise: type: object additionalProperties: false required: [id, taxpayerId, puIdentifier, name, commerceMode, environment, paymentMode, status, createdAt, updatedAt] properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } puIdentifier: { type: string } name: { type: string } commerceMode: { type: string, const: distance } environment: $ref: '#/components/schemas/FiscalEnvironment' paymentMode: $ref: '#/components/schemas/PremisePaymentMode' status: { type: string, enum: [active, suspended, closed] } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } BusinessPremisePage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/BusinessPremise' } CustomerRole: type: string enum: [customer_owner, customer_administrator, customer_operator, customer_viewer] StaffRole: type: string enum: [boka_support, boka_operations, boka_security_administrator, boka_platform_administrator, boka_root] StaffSupportContextCreate: type: object additionalProperties: false required: [organizationId, caseReference, reason] properties: organizationId: { type: string, format: uuid } caseReference: { type: string, minLength: 3, maxLength: 100 } reason: { type: string, minLength: 10, maxLength: 500 } purpose: type: string enum: [customer-support-read, customer-data-edit] default: customer-support-read description: Omitted means read-only. `customer-data-edit` requires the Senior Operations or Root role. StaffSupportContext: type: object additionalProperties: false required: - id - organizationId - organizationName - purpose - authority - caseReference - reason - status - createdAt - expiresAt - closedAt properties: id: { type: string, format: uuid } organizationId: { type: string, format: uuid } organizationName: { type: string } purpose: { type: string, enum: [customer-support-read, customer-data-edit] } authority: { type: string, enum: [read-only, read-write] } caseReference: { type: string } reason: { type: string } status: { type: string, enum: [active, expired, closed] } createdAt: { type: string, format: date-time } expiresAt: { type: string, format: date-time } closedAt: { type: [string, 'null'], format: date-time } HumanAccessAuditEvent: type: object additionalProperties: false required: - id - eventType - resourceType - resourceId - outcome - occurredAt - organizationId - organizationName - actorKind - actorDisplayName - actorIsSelf properties: id: { type: string, format: uuid } eventType: { type: string } resourceType: { type: string } resourceId: { type: string, format: uuid } outcome: { type: string, enum: [accepted, rejected, succeeded, unknown] } occurredAt: { type: string, format: date-time } organizationId: { type: [string, 'null'], format: uuid } organizationName: { type: [string, 'null'] } actorKind: { type: string, enum: [staff, customer, system, unknown] } actorDisplayName: { type: [string, 'null'] } actorIsSelf: { type: boolean } HumanAccessAuditPage: type: object additionalProperties: false required: [items, nextCursor, visibility] properties: items: type: array items: { $ref: '#/components/schemas/HumanAccessAuditEvent' } nextCursor: { type: [string, 'null'] } visibility: { type: string, enum: [self, platform] } OrganizationAuditEvent: type: object additionalProperties: false required: - id - eventType - resourceType - resourceId - outcome - occurredAt - taxpayerId - businessPremiseId - actorKind properties: id: { type: string, format: uuid } eventType: { type: string } resourceType: { type: string } resourceId: type: [string, 'null'] format: uuid description: Withheld for security-element upload-session events. outcome: { type: string, enum: [success, failure] } occurredAt: { type: string, format: date-time } taxpayerId: { type: [string, 'null'], format: uuid } businessPremiseId: { type: [string, 'null'], format: uuid } actorKind: type: string enum: [organization, boka-staff] description: Whether the organization's own people or machine clients made the change, or BOKA staff through an audited support context. Staff identity is never exposed here. OrganizationAuditPage: type: object additionalProperties: false required: [items, nextCursor, visibility] properties: items: type: array items: { $ref: '#/components/schemas/OrganizationAuditEvent' } nextCursor: { type: [string, 'null'] } visibility: { type: string, const: organization } MembershipStatus: type: string enum: [pending-invitation, active, suspended] MembershipInvitationStatus: type: [string, 'null'] description: Safe managed-identity workflow status. Null means no standard workflow exists for this legacy or elevated membership. enum: - pending-identity - identity-preparing - identity-prepared - invitation-dispatching - invitation-sent - invitation-outcome-unknown - identity-conflict - identity-control-plane-failed - cancelled - active - null CompanyRegistryLookup: type: object additionalProperties: false description: One company aggregated from the NBS unified account register (one source row per bank account). Company fields come from the most recently updated row; every account is listed. required: [source, taxIdentifier, registrationNumber, legalName, anyAccountBlocked, accounts] properties: source: { type: string, const: nbs-jrr } taxIdentifier: { type: string, pattern: '^[0-9]{9}$' } registrationNumber: { type: string, pattern: '^[0-9]{8}$' } legalName: { type: string } address: { type: string, nullable: true } city: { type: string, nullable: true } municipalityCode: { type: string, nullable: true } municipality: { type: string, nullable: true } activityCode: { type: string, nullable: true, pattern: '^[0-9]{4}$' } activityName: { type: string, nullable: true } anyAccountBlocked: type: boolean description: True when any listed account carries a non-zero blockade status. accounts: type: array items: { $ref: '#/components/schemas/CompanyRegistryAccount' } registryUpdatedAt: { type: string, format: date-time, nullable: true } CompanyRegistryAccount: type: object additionalProperties: false required: [accountNumber, bankName, statusId, blockadeStatusId, blocked] properties: accountNumber: { type: string, description: Formatted account number, for example 160-0000000012345-67 } bankName: { type: string } statusId: { type: integer, description: NBS account status code (0 = active) } blockadeStatusId: { type: integer, description: NBS blockade status code (0 = not blocked) } blocked: { type: boolean } CustomerProvisioningCreate: type: object additionalProperties: false required: [organizationName, ownerEmail, ownerDisplayName] properties: organizationName: { type: string, minLength: 1, maxLength: 200 } ownerEmail: { type: string, format: email, maxLength: 320 } ownerDisplayName: { type: string, minLength: 1, maxLength: 200 } taxpayer: description: Optional customer taxpayer created atomically inside the new organization. Staff prefill it from the company registry lookup and confirm or edit it; the PIB is immutable afterwards. nullable: true allOf: - $ref: '#/components/schemas/TaxpayerCreate' StaffCustomerDirectoryEntry: type: object additionalProperties: false required: - customerProvisioningId - organizationId - organizationName - provisioningStatus - ownerMembershipStatus - createdAt - updatedAt - lastFailureCode properties: customerProvisioningId: { type: string, format: uuid } organizationId: { type: string, format: uuid } organizationName: { type: string } provisioningStatus: type: string enum: - pending-identity - identity-prepared - invitation-dispatching - invitation-sent - invitation-outcome-unknown - identity-conflict - identity-control-plane-failed - active ownerMembershipStatus: type: string enum: [pending-invitation, active, suspended] createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } lastFailureCode: type: [string, 'null'] description: >- BOKA-owned safe code behind an identity-conflict, identity-control-plane-failed, or invitation-outcome-unknown state (for example MANAGED_IDENTITY_CUSTOMER_SCOPE_MISMATCH or MANAGED_IDENTITY_USER_PROFILE_INCOMPLETE); never raw identity-provider detail. Null while the flow is healthy. StaffCustomerDirectoryPage: type: object additionalProperties: false required: [items, nextCursor] properties: items: type: array items: { $ref: '#/components/schemas/StaffCustomerDirectoryEntry' } nextCursor: { type: [string, 'null'], format: uuid } CustomerProvisioning: type: object additionalProperties: false required: - id - organizationId - organizationName - ownerMembershipId - ownerEmail - ownerDisplayName - ownerRole - ownerMembershipStatus - status - createdAt - updatedAt - lastFailureCode properties: id: { type: string, format: uuid } organizationId: { type: string, format: uuid } organizationName: { type: string } ownerMembershipId: { type: string, format: uuid } ownerEmail: { type: string, format: email } ownerDisplayName: { type: string } ownerRole: { type: string, const: customer_owner } ownerMembershipStatus: type: string enum: [pending-invitation, active] status: type: string enum: - pending-identity - identity-prepared - invitation-dispatching - invitation-sent - invitation-outcome-unknown - identity-conflict - identity-control-plane-failed - active createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } taxpayerId: type: string format: uuid nullable: true description: The customer taxpayer created with the organization, or null when staff skipped it. lastFailureCode: type: [string, 'null'] description: >- BOKA-owned safe code behind an identity-conflict, identity-control-plane-failed, or invitation-outcome-unknown state; never raw identity-provider detail. Null while the flow is healthy. OrganizationMembershipCreate: type: object additionalProperties: false required: [email, displayName, role] properties: email: { type: string, format: email, maxLength: 320 } displayName: { type: string, minLength: 1, maxLength: 200 } role: allOf: - $ref: '#/components/schemas/CustomerRole' description: customer_owner is reserved for the future step-up ownership flow. OrganizationMembershipUpdate: type: object additionalProperties: false required: [displayName, role, status] properties: displayName: { type: string, minLength: 1, maxLength: 200 } role: { $ref: '#/components/schemas/CustomerRole' } status: { $ref: '#/components/schemas/MembershipStatus' } OrganizationMembership: type: object additionalProperties: false required: [id, email, displayName, role, status, identityLinked, invitationStatus, createdAt, updatedAt] properties: id: { type: string, format: uuid } email: { type: string, format: email } displayName: { type: string } role: { $ref: '#/components/schemas/CustomerRole' } status: { $ref: '#/components/schemas/MembershipStatus' } identityLinked: { type: boolean } invitationStatus: { $ref: '#/components/schemas/MembershipInvitationStatus' } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } OrganizationMembershipPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/OrganizationMembership' } StaffMembershipCreate: type: object additionalProperties: false required: [email, displayName, role] properties: email: { type: string, format: email, maxLength: 320 } displayName: { type: string, minLength: 1, maxLength: 200 } role: { $ref: '#/components/schemas/StaffRole' } StaffMembershipUpdate: type: object additionalProperties: false required: [displayName, role, status] properties: displayName: { type: string, minLength: 1, maxLength: 200 } role: { $ref: '#/components/schemas/StaffRole' } status: { $ref: '#/components/schemas/MembershipStatus' } StaffMembership: type: object additionalProperties: false required: [id, email, displayName, role, status, identityLinked, invitationStatus, createdAt, updatedAt] properties: id: { type: string, format: uuid } email: { type: string, format: email } displayName: { type: string } role: { $ref: '#/components/schemas/StaffRole' } status: { $ref: '#/components/schemas/MembershipStatus' } identityLinked: { type: boolean } invitationStatus: { $ref: '#/components/schemas/MembershipInvitationStatus' } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } StaffMembershipPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/StaffMembership' } SecurityElementActivationJob: type: object additionalProperties: false required: [id, securityElementId, status, proofInvoiceType, requestedAt] properties: id: { type: string, format: uuid } securityElementId: { type: string, format: uuid } status: { type: string, enum: [queued, running, succeeded, failed] } proofInvoiceType: { type: string, enum: [training] } failureCode: { type: [string, 'null'] } requestedAt: { type: string, format: date-time } startedAt: { type: [string, 'null'], format: date-time } completedAt: { type: [string, 'null'], format: date-time } fiscalOperationId: { type: [string, 'null'], format: uuid } pfrResponseSha256: { type: [string, 'null'], pattern: '^[0-9a-f]{64}$' } receiptVerificationSha256: { type: [string, 'null'], pattern: '^[0-9a-f]{64}$' } taxLabel: { type: [string, 'null'] } taxGroupRevision: { type: [integer, 'null'] } SecurityElementActivationJobPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/SecurityElementActivationJob' } StaffSecurityElement: type: object additionalProperties: false required: - id - organizationId - organizationName - taxpayerId - taxpayerPib - taxpayerLegalName - businessPremiseId - premisePuIdentifier - premiseName - environment - jid - status - certificateNotBefore - certificateNotAfter - createdAt - latestActivationJob properties: id: { type: string, format: uuid } organizationId: { type: string, format: uuid } organizationName: { type: string } taxpayerId: { type: string, format: uuid } taxpayerPib: { type: string } taxpayerLegalName: { type: string } businessPremiseId: { type: string, format: uuid } premisePuIdentifier: { type: string } premiseName: { type: string } environment: { type: string, enum: [sandbox, production] } jid: { type: string } status: { type: string, enum: [pending-verification, active, suspended, retired] } certificateNotBefore: { type: string, format: date-time } certificateNotAfter: { type: string, format: date-time } createdAt: { type: string, format: date-time } latestActivationJob: oneOf: - $ref: '#/components/schemas/SecurityElementActivationJob' - type: 'null' StaffSecurityElementPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/StaffSecurityElement' } StaffSandboxElementCreate: type: object additionalProperties: false required: [pfxPassword, pak] description: | Staff-only pool intake. Supply exactly one of archiveBase64 (the original supplier ZIP, parsed with the same bounded reader as customer intake) or pfxBase64, plus the PFX password and PAK. JID, PIB, and the legal name are read from the certificate when omitted; when supplied they must match the certificate. Premise fields default to informational BOKA values. Secrets travel once over TLS and are immediately protected into the dedicated pool custody store. properties: archiveBase64: { type: string, format: byte, description: 'Original supplier ZIP, at most 2 MB.' } pfxBase64: { type: string, format: byte } pfxPassword: { type: string, pattern: '^[A-Z0-9]{8}$' } pak: { type: string, pattern: '^[A-Z0-9]{6}$' } jid: { type: string, pattern: '^[A-Z0-9]{8}$' } taxpayerPib: { type: string, pattern: '^[0-9]{9}$' } taxpayerLegalName: { type: string, maxLength: 200 } premisePuIdentifier: { type: string, maxLength: 100 } premiseName: { type: string, maxLength: 200 } StaffSandboxElement: type: object additionalProperties: false required: - id - jid - taxpayerPib - taxpayerLegalName - premisePuIdentifier - premiseName - certificateThumbprint - certificateSubject - certificateIssuer - certificateSerialNumber - certificateNotBefore - certificateNotAfter - status - assignmentCount - createdAt - updatedAt properties: id: { type: string, format: uuid } jid: { type: string } taxpayerPib: { type: string } taxpayerLegalName: { type: string } premisePuIdentifier: { type: string } premiseName: { type: string } certificateThumbprint: { type: string } certificateSubject: { type: string } certificateIssuer: { type: string } certificateSerialNumber: { type: string } certificateNotBefore: { type: string, format: date-time } certificateNotAfter: { type: string, format: date-time } status: { type: string, enum: [verified, retired] } assignmentCount: { type: integer, minimum: 0 } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } StaffSandboxElementPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/StaffSandboxElement' } StaffSandboxAssignment: type: object required: [id, organizationId, organizationName, securityElementId, createdAt] properties: id: { type: string, format: uuid } organizationId: { type: string, format: uuid } organizationName: { type: string } securityElementId: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } createdAt: { type: string, format: date-time } StaffSandboxAssignmentPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/StaffSandboxAssignment' } ApiCredential: type: object additionalProperties: false required: [id, organizationId, environment, clientId, displayName, scopes, status, createdAt, updatedAt] properties: id: { type: string, format: uuid } organizationId: { type: string, format: uuid } environment: { $ref: '#/components/schemas/FiscalEnvironment' } clientId: { type: string } displayName: { type: string } scopes: type: array items: { type: string } status: { type: string, enum: [pending, active, revoked, failed] } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } revokedAt: { type: string, format: date-time, nullable: true } secret: type: string nullable: true description: Present only in the creation response; never retrievable again. ApiCredentialPage: type: object additionalProperties: false required: [items] properties: items: type: array items: { $ref: '#/components/schemas/ApiCredential' } FiscalEnvironment: type: string enum: [sandbox, production] description: | The Stripe-style fiscal environment. Machine credentials carry exactly one environment and may only use security elements of that environment; sandbox elements are BOKA-assigned and route to the TaxCore sandbox, production elements are the customer's own. Taxpayers and business premises carry the environment too: the BOKA sandbox taxpayer and premise created by a pool assignment are sandbox, everything a human registers is production, and setup registered by a machine credential belongs to that credential's environment. A premise inherits its taxpayer's environment and a security element can only be bound to a premise of its own. SecurityElementUploadSession: type: object required: [id, uploadUrl, encryptionKey, environment, expiresAt, status] properties: id: { type: string, format: uuid } uploadUrl: { type: string, format: uri-reference } environment: { $ref: '#/components/schemas/FiscalEnvironment' } encryptionKey: type: object description: Public RSA JWK used to encrypt the complete upload payload. additionalProperties: false required: [kty, use, alg, kid, n, e] properties: kty: { type: string, const: RSA } use: { type: string, const: enc } alg: { type: string, const: RSA-OAEP-256 } kid: { type: string } n: { type: string } e: { type: string } expiresAt: { type: string, format: date-time } status: { type: string, enum: [pending, completed] } SecurityElementUploadPayload: type: object additionalProperties: false required: [uploadSessionId, jid, pfxPassword, pak] description: Plaintext schema before compact-JWE encryption; never send this object directly. oneOf: - required: [archiveBase64] - required: [pfxBase64] properties: uploadSessionId: type: string format: uuid description: Must equal the session ID in the one-time upload URL. jid: { type: string, pattern: '^[A-Z0-9]{8}$' } archiveBase64: type: string format: byte description: Original supplier ZIP; preferred for owner self-service onboarding. pfxBase64: type: string format: byte description: Extracted PFX for controlled integration clients; mutually exclusive with archiveBase64. pfxPassword: type: string pattern: '^[A-Z0-9]{8}$' writeOnly: true pak: type: string pattern: '^[A-Z0-9]{6}$' writeOnly: true SecurityElement: type: object additionalProperties: false required: - id - taxpayerId - businessPremiseId - environment - jid - certificateThumbprint - certificateSubject - certificateIssuer - certificateSerialNumber - certificateNotBefore - certificateNotAfter - certificateExpiryStatus - replacementRecommended - status - createdAt - updatedAt properties: id: { type: string, format: uuid } taxpayerId: { type: string, format: uuid } businessPremiseId: { type: string, format: uuid } environment: { $ref: '#/components/schemas/FiscalEnvironment' } jid: { type: string } certificateThumbprint: { type: string } certificateSubject: { type: string } certificateIssuer: { type: string } certificateSerialNumber: { type: string } certificateNotBefore: { type: string, format: date-time } certificateNotAfter: { type: string, format: date-time } certificateExpiryStatus: type: string enum: [not-yet-valid, current, warning, critical, expired] description: Server-evaluated public-certificate time status using the configured 30-day warning and 7-day critical windows by default. replacementRecommended: type: boolean description: True for a non-retired element whose public certificate is inside the warning window or expired; this is not proof of revocation or V-PFR status. status: type: string enum: [pending-verification, active, suspended, retired] createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } FiscalDocumentPage: type: object required: [items, nextCursor] properties: items: type: array items: { $ref: '#/components/schemas/FiscalDocument' } nextCursor: { type: [string, 'null'] } Conflict: type: object additionalProperties: false required: [code, message] properties: code: { type: string } message: { type: string } IdempotencyConflict: type: object additionalProperties: false required: [code] description: | `IDEMPOTENCY_KEY_REUSED_WITH_DIFFERENT_REQUEST` names the original operation. `IDEMPOTENCY_KEY_REUSED_IN_OTHER_ENVIRONMENT` means the organization already used this key with a credential of the other fiscal environment; keys are unique per organization across both environments, so choose a new key rather than retrying. properties: code: { type: string } operationId: { type: string, format: uuid } message: { type: string } ErrorCode: type: object additionalProperties: false required: [code] properties: code: { type: string } message: { type: string } ValidationProblem: type: object additionalProperties: false required: [type, title, status, errors] properties: type: { type: string, format: uri-reference } title: { type: string } status: { type: integer } errors: type: object additionalProperties: type: array items: { type: string } LicenseState: type: string enum: [none, active, suspended, not-started, expired] description: Customer-facing licence state on the Belgrade calendar date of the request. LicensePriceList: type: object additionalProperties: false description: Prices in RSD with VAT included. The base price is per active production security element per month; modules are per organization per month. required: [basePricePerElement, includedDocuments, overageDocumentPrice, advanceModulePrice, emailModulePrice, vatRate] properties: basePricePerElement: { type: number } includedDocuments: { type: integer } overageDocumentPrice: { type: number } advanceModulePrice: { type: number } emailModulePrice: { type: number } vatRate: { type: number, description: Fraction, 0.2 for 20% VAT. } License: type: object additionalProperties: false required: [id, status, startsOn, endsOn, advanceModuleEnabled, emailModuleEnabled, basePriceOverride, includedDocumentsOverride, overageDocumentPriceOverride, advanceModulePriceOverride, emailModulePriceOverride, effectivePrices, notes, createdAt, updatedAt] properties: id: { type: string, format: uuid } status: { type: string, enum: [active, suspended] } startsOn: { type: string, format: date } endsOn: { type: string, format: date, nullable: true } advanceModuleEnabled: { type: boolean } emailModuleEnabled: { type: boolean } basePriceOverride: { type: number, nullable: true } includedDocumentsOverride: { type: integer, nullable: true } overageDocumentPriceOverride: { type: number, nullable: true } advanceModulePriceOverride: { type: number, nullable: true } emailModulePriceOverride: { type: number, nullable: true } effectivePrices: { $ref: '#/components/schemas/LicensePriceList' } notes: { type: string, nullable: true, description: Internal staff notes; always null on customer reads. } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } LicenseSummary: type: object additionalProperties: false required: [state, productionFiscalizationAllowed, modules, startsOn, endsOn, prices] properties: state: { $ref: '#/components/schemas/LicenseState' } productionFiscalizationAllowed: { type: boolean } modules: type: object additionalProperties: false required: [advance, email] properties: advance: { type: boolean } email: { type: boolean } startsOn: { type: string, format: date, nullable: true } endsOn: { type: string, format: date, nullable: true } prices: { $ref: '#/components/schemas/LicensePriceList' } LicenseStatementLine: type: object additionalProperties: false required: [kind, securityElementId, jid, label, billedDays, periodDays, unitPrice, quantity, amount] properties: kind: { type: string, enum: [base-element, module-advance, module-email, overage] } securityElementId: { type: string, format: uuid, nullable: true } jid: { type: string, nullable: true } label: { type: string, nullable: true } billedDays: { type: integer } periodDays: { type: integer } unitPrice: { type: number } quantity: { type: number } amount: { type: number } LicenseStatement: type: object additionalProperties: false description: One Belgrade calendar month. Base and module lines are prorated by day; the included document allowance is not. Amounts are RSD with VAT included. required: [period, periodStart, periodEnd, licensed, licensedDays, periodDays, lines, fiscalizedDocuments, includedDocuments, overageDocuments, overageUnitPrice, grossTotal, netTotal, vatTotal] properties: period: { type: string, pattern: '^[0-9]{4}-[0-9]{2}$' } periodStart: { type: string, format: date } periodEnd: { type: string, format: date } licensed: { type: boolean } licensedDays: { type: integer } periodDays: { type: integer } lines: type: array items: { $ref: '#/components/schemas/LicenseStatementLine' } fiscalizedDocuments: { type: integer, description: Production documents with a signed V-PFR response in the month. } includedDocuments: { type: integer } overageDocuments: { type: integer } overageUnitPrice: { type: number } grossTotal: { type: number } netTotal: { type: number } vatTotal: { type: number } LicenseInvoice: type: object additionalProperties: false required: [id, period, invoiceNumber, issuedOn, dueOn, grossAmount, status, paidOn, note, createdAt, updatedAt] properties: id: { type: string, format: uuid } period: { type: string, pattern: '^[0-9]{4}-[0-9]{2}$' } invoiceNumber: { type: string, maxLength: 64 } issuedOn: { type: string, format: date } dueOn: { type: string, format: date, nullable: true } grossAmount: { type: number } status: { type: string, enum: [issued, paid, cancelled] } paidOn: { type: string, format: date, nullable: true } note: { type: string, nullable: true, maxLength: 500 } createdAt: { type: string, format: date-time } updatedAt: { type: string, format: date-time } LicensedElement: type: object additionalProperties: false required: [id, jid, premiseName, status, activatedAt, deactivatedAt] properties: id: { type: string, format: uuid } jid: { type: string } premiseName: { type: string } status: { type: string, enum: [pending-verification, active, suspended, retired] } activatedAt: { type: string, format: date-time, nullable: true } deactivatedAt: { type: string, format: date-time, nullable: true } Billing: type: object additionalProperties: false required: [organizationName, state, license, priceList, currentStatement, previousStatements, invoices, productionElements, today] properties: organizationName: { type: string } state: { $ref: '#/components/schemas/LicenseState' } license: nullable: true allOf: - $ref: '#/components/schemas/License' priceList: { $ref: '#/components/schemas/LicensePriceList' } currentStatement: { $ref: '#/components/schemas/LicenseStatement' } previousStatements: type: array items: { $ref: '#/components/schemas/LicenseStatement' } invoices: type: array items: { $ref: '#/components/schemas/LicenseInvoice' } productionElements: type: array items: { $ref: '#/components/schemas/LicensedElement' } today: { type: string, format: date } StaffLicenseWrite: type: object additionalProperties: false required: [status, startsOn, advanceModuleEnabled, emailModuleEnabled] properties: status: { type: string, enum: [active, suspended] } startsOn: { type: string, format: date } endsOn: { type: string, format: date, nullable: true } advanceModuleEnabled: { type: boolean } emailModuleEnabled: { type: boolean } basePriceOverride: { type: number, nullable: true } includedDocumentsOverride: { type: integer, nullable: true } overageDocumentPriceOverride: { type: number, nullable: true } advanceModulePriceOverride: { type: number, nullable: true } emailModulePriceOverride: { type: number, nullable: true } notes: { type: string, nullable: true, maxLength: 1000 } StaffLicenseInvoiceCreate: type: object additionalProperties: false required: [period, invoiceNumber, issuedOn, grossAmount] properties: period: { type: string, pattern: '^[0-9]{4}-[0-9]{2}$' } invoiceNumber: { type: string, maxLength: 64 } issuedOn: { type: string, format: date } grossAmount: { type: number } dueOn: { type: string, format: date, nullable: true } note: { type: string, nullable: true, maxLength: 500 } StaffLicenseInvoiceUpdate: type: object additionalProperties: false required: [invoiceNumber, issuedOn, grossAmount, status] properties: invoiceNumber: { type: string, maxLength: 64 } issuedOn: { type: string, format: date } grossAmount: { type: number } status: { type: string, enum: [issued, paid, cancelled] } dueOn: { type: string, format: date, nullable: true } paidOn: { type: string, format: date, nullable: true } note: { type: string, nullable: true, maxLength: 500 } StaffLicenseListItem: type: object additionalProperties: false required: [organizationId, organizationName, state, license, activeProductionElements, currentPeriod, unpaidInvoices] properties: organizationId: { type: string, format: uuid } organizationName: { type: string } state: { $ref: '#/components/schemas/LicenseState' } license: nullable: true allOf: - $ref: '#/components/schemas/License' activeProductionElements: { type: integer } currentPeriod: type: object additionalProperties: false required: [period, fiscalizedDocuments, estimatedGrossTotal] properties: period: { type: string, pattern: '^[0-9]{4}-[0-9]{2}$' } fiscalizedDocuments: { type: integer } estimatedGrossTotal: { type: number } unpaidInvoices: { type: integer } StaffLicensePage: type: object additionalProperties: false required: [items, priceList, today] properties: items: type: array items: { $ref: '#/components/schemas/StaffLicenseListItem' } priceList: { $ref: '#/components/schemas/LicensePriceList' } today: { type: string, format: date } StaffLicenseDetail: type: object additionalProperties: false required: [organizationId, organizationName, state, license, priceList, statements, invoices, productionElements, today] properties: organizationId: { type: string, format: uuid } organizationName: { type: string } state: { $ref: '#/components/schemas/LicenseState' } license: nullable: true allOf: - $ref: '#/components/schemas/License' priceList: { $ref: '#/components/schemas/LicensePriceList' } statements: type: array description: The current month first, then previous months, newest first. items: { $ref: '#/components/schemas/LicenseStatement' } invoices: type: array items: { $ref: '#/components/schemas/LicenseInvoice' } productionElements: type: array items: { $ref: '#/components/schemas/LicensedElement' } today: { type: string, format: date }