_Version: 1.0_

> This document is the LLM-friendly export of the API. It is regenerated on every request from the live OpenAPI spec. Use it as context when asking an AI assistant for help.

# fature.al API

fature.al is the invoicing and fiscalization platform for businesses in Albania. With this API your system
issues fiscalized invoices without dealing with certificates, XML signing, or SOAP yourself: send the
invoice as JSON and get back the document registered with the tax administration, with its fiscal
codes, the verification URL, and the PDF.

## Who it is for

- **Accounting and ERP software**, which issues the invoice where the sale happens.
- **Online stores**, which turn a paid order into an invoice at the same moment.
- **Point-of-sale, bar, and restaurant software**, with orders that are opened and closed with a single payment.
- **Software providers**, who install the same software at many businesses, each with its own
  fiscal identity.

## What you need before the first call

| You need | Where to get it |
| --- | --- |
| A fature.al account, with a subscription that includes the modules you will use | `fature.al`, or `demo.fature.al` for sandbox |
| An **API token** | In the fature.al dashboard, under **Settings > API tokens**. The token belongs to a user, not to the company |
| The app's **client id** and **client secret**, if you are a software provider | From fature.al, when your integration is registered. See "Identify your application" |
| The business's electronic certificate, uploaded to fature.al | The business gets it from e-Albania and uploads it once in the dashboard |
| For cash invoices: a fiscal device (TCR) linked to the token's user | Configured by the business in the dashboard, or by you with the registration endpoints |

The first useful call is `GET /account`: it tells you which business, business unit, and fiscal
device you are working with.

## Core concepts

### Fiscalization in a few lines

In Albania every invoice is reported in real time to the tax administration, in the central
fiscalization system (**CIS**). The system registers the invoice and returns its codes, which appear on
the document the buyer receives, together with a QR code that opens the verification page. When the CIS cannot be reached,
the invoice is issued anyway, stored, and fiscalized automatically as soon as the service is back. Invoices to
businesses can also go to the central e-invoicing platform, which delivers the electronic
document to the buyer.

fature.al does all of this: it signs with the business's certificate, communicates with the CIS and with
the e-invoicing platform, keeps the numbering, stores the document, and retries fiscalization when the service does not
respond.

### Who issues the invoice

| Entity | What it is | Where it appears in the API |
| --- | --- | --- |
| **Company** | The business issuing the invoices, identified by its **NIPT** (the same code is also called **NUIS**) | The token belongs to one of its users |
| **Business unit** | The place where the activity is carried out: the store, the office, the warehouse. It has a code issued by the tax administration, `businessUnitCode` | `businessCode` in the response |
| **Operator** | The person issuing the invoice, with the operator code issued by the tax administration | `operatorCode` in the response |
| **Fiscal device (TCR)** | The cash register registered with the tax administration, with its TCR code. Only cash invoices are issued from a TCR | `tcrCode` in the response |
| **Certificate** | The business's electronic certificate, used to sign every request to the tax administration | Uploaded once; not sent in requests |

The user's token carries all of them: the company, business unit, operator, and TCR are selected from it.
You do not send them in any invoicing request.

### Document types

| Document | Endpoint | When to use it |
| --- | --- | --- |
| **Cash invoice** | `POST /invoice/cash` | Payment is made on the spot, at the till: cash, card, company card, vouchers |
| **Non-cash invoice** | `POST /invoice/noncash` | Payment comes later, by transfer or on account |
| **E-invoice** | `POST /invoice/e-invoice` | The buyer is a business and the invoice is delivered through the e-invoicing platform |
| **Order invoice** | `POST /invoice/order` | Consumption has started, payment has not: a restaurant table |
| **Summary invoice** | `POST /invoice/summary` | Closes one or more orders with a single payment |
| **Bulk non-cash** | `POST /invoice/bulk-noncash` | Many non-cash invoices in one request, for periodic invoicing |
| **Warehouse transfer note (WTN)** | `POST /invoice/wtn` | Goods moving without being sold; required by law |

The request body differs from one type to another more than it seems: the same invoice sent to
two endpoints is rejected by one of them. Each endpoint states its own rules.

### Corrections, advance payments and non-fiscal documents

| Document | How it is issued |
| --- | --- |
| **Credit note** | `doc_type` `381` with `original_invoice_iic`, on `POST /invoice/noncash` or `POST /invoice/e-invoice` |
| **Debit note** | `doc_type` `383` with `original_invoice_iic`, on the same endpoints |
| **Corrective invoice** | `POST /invoice/cancel/{id}`: a full cancellation for any invoice, a partial one for cash invoices only |
| **Advance payment**, including a reservation or group deposit | A cash or non-cash invoice for the amount received, or an e-invoice with `doc_type` `386` and `process` `P4` |
| **Proforma, information bill** | Not issued by the API: they are not fiscal documents and are not registered with the CIS |

An advance payment has no type of its own at the CIS, and the API does not link it to the final
invoice: no field deducts it from the invoice issued when the service is completed.

Cash, non-cash and e-invoices, and every document in this table, have a ready-to-send example in
the Postman collection [document-examples.json](/docs/api/postman/document-examples.json).

### What you get back and what to store

Every issuance returns the same identifiers:

| Field | What it is | Store it? |
| --- | --- | --- |
| `id` | The invoice id in fature.al | Yes, or work with your own `internalId` |
| `number` | The invoice number, such as `12/2026` | Yes; it is shown to the buyer |
| `iic` | The invoice identification code, computed from the signature. By law it is called **NSLF** | Yes; it is the key for correction notes and summary invoices |
| `fic` | The code the CIS returns when it registers the invoice. By law it is called **NIVF**. `null` while the invoice awaits fiscalization | Yes |
| `eic` | The invoice code on the e-invoicing platform. E-invoices only | Yes, for e-invoices |
| `verifyURL` | The verification page at the tax administration, the one the QR code opens | Yes, if you print yourself |
| `pdf` | The URL of the finished document | As needed |

`internalId` is the invoice number in your system. You send it with every issuance, and with it you can
find and cancel the invoice without keeping our `id`.

### Glossary

| Term | Meaning |
| --- | --- |
| **NIPT / NUIS** | Taxpayer identification number, such as `L62221018T`. Two names for the same code |
| **CIS** | The tax administration's central fiscalization system, where every invoice is registered |
| **IIC / NSLF** | The invoice identification code, 32 hexadecimal characters. The same code with two names |
| **FIC / NIVF** | The code the CIS returns when it registers the invoice, a UUID. The same code with two names |
| **EIC** | The e-invoice code on the central e-invoicing platform |
| **TCR** | The fiscal device (cash register) registered with the tax administration, and its code |
| **Business unit** | The place where the activity is carried out; it has the code `businessUnitCode` |
| **Operator code** | The code of the person issuing the invoice, issued by the tax administration |
| **Self-care** | The tax administration's portal where the business gets the business unit code and the operator code |
| **e-Albania** | The government portal from which the business gets its electronic certificate |
| **Cash register** | The working day on a TCR: opening balance, cash in and cash out, closing |
| **Walk-in customer** | The anonymous buyer in a retail sale. A cash invoice without `client` is issued to them |
| **Self-invoicing** | An invoice the buyer issues on behalf of the seller (`self_issue_type`) |
| **Credit note, debit note** | A document that corrects an issued invoice by lowering or raising its value (`doc_type` `381` or `383`) |
| **Warehouse transfer note (WTN)** | The document that accompanies goods in transit when there is no sale |
| **UBL** | The e-invoice XML format; `process` and `doc_type` are fields of it |

## Quick start

Three calls take you from the token to your first fiscalized invoice. Work in sandbox,
`https://demo.fature.al/api/v1`, where invoices have no fiscal value.

**1. Test the token.** `GET /ping` touches nothing:

```bash
curl https://demo.fature.al/api/v1/ping \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

```json
{"status": true, "data": {"pong": 1788547330, "ip": ["81.2.3.4"]}}
```

A `401` means the token is not getting through: check that the header is named `Authorization` and that
the word `Bearer` comes before the token.

**2. See who you are working as.** `GET /account` returns the user, the company, the business unit, and
the fiscal device code, confirming that the token belongs to the right business.

**3. Issue your first invoice.** A non-cash invoice is the simplest place to start, because it does not require
a fiscal device:

```bash
curl -X POST https://demo.fature.al/api/v1/invoice/noncash \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -H 'Content-Type: application/json' \
  -d '{
    "internalId": "TEST-0001",
    "payment_method": "TRANSFER",
    "client": {"name": "Klient Provë", "address": "Rruga e Kavajës 12", "city": "Tiranë"},
    "lines": [
      {"product_name": "Shërbim", "product_code": "SRV-01", "unit": "copë", "unit_code": "C62",
       "quantity": 1, "price": 1000, "total": 1000, "vat": 20}
    ]
  }'
```

```json
{
  "status": true,
  "data": {
    "invoice": {
      "id": 40312,
      "number": "1/2026",
      "iic": "8FE72E2ACD1C500A83F8C89B4E2E3E1D",
      "fic": "b3f1c0a2-4d5e-4f60-9a71-2c3d4e5f6071",
      "tcrCode": null,
      "businessCode": "bb123bb123",
      "operatorCode": "aa123aa123",
      "fiscalizedAt": "2026-09-13 10:42:07",
      "verifyURL": "https://efiskalizimi-app.tatime.gov.al/invoice-check/#/verify?iic=...",
      "pdf": "https://demo.fature.al/api/v1/invoice/print/40312"
    }
  }
}
```

The invoice also appears immediately in the fature.al dashboard. Store `id`, `number`, `iic`, and `fic` alongside
your sale.

## Authentication

Every request has two levels of identification: **who** makes it (the user, with a bearer token) and **which
software** makes it (your application, with a client id and client secret).

```
Authorization: Bearer {api_token}
X-Client-Id: ft_id_...
X-Client-Secret: ft_sk_...
```

You generate the token from **Settings > API tokens** in the fature.al dashboard, or get it from the
response of `POST /register`. It belongs to the business using your software and differs from one
business to the next.

The client id and client secret belong to **your application**, not to the business: they are the same for
all businesses using your software. An integration written for a single business works
with the token alone. See "Identify your application" below.

The token grants full access to the account. Keep it on the server, not in browser code or in
public apps, do not commit it to version control, and regenerate it immediately if you
suspect it has leaked.

## Servers and versions

Every path in this document is relative to the addresses below: `/invoice/cash` means
`https://fature.al/api/v1/invoice/cash`.

| Environment | URL |
| --- | --- |
| **Live** | `https://fature.al/api/v1` |
| **Sandbox** | `https://demo.fature.al/api/v1` |

Always build against sandbox and switch to live by changing only the base address.
Requests and responses are the same in both environments.

The version is part of the path. `v1` holds the entire API and is the version to build on.
`v2` exists only for invoices and clients, with payment as a list and the client as separate objects,
and is used alongside `v1`, not instead of it. No version is being retired. Within a version,
changes are additive only: an existing field does not change meaning and is not removed.

| Document | What it covers | Address |
| --- | --- | --- |
| **v1** (this document) | The entire invoicing and registration API | [`/docs/api`](/docs/api) |
| **v2** | Invoices with split payment and clients in the new format | [`/docs/api/v2`](/docs/api/v2) |
| **Partner** | Wolt orders for a single business unit | [`/docs/api/partner`](/docs/api/partner) |

Each document is also published as OpenAPI JSON (`/docs/api.json`) and as Markdown for AI assistants
(`/docs/api.md`).

## How to read a response

Every response is JSON. Read it in this order:

1. **The HTTP code.**
2. **On `200`, the `status` field.** `true`: the result is in `data`. `false`: the action was rejected,
   the reason is in `message` and, when there are details, in `errors`.
3. **On `422`, the `success` field.** This is the validation response and the only one that uses `success`
   instead of `status`. There `errors` is an object keyed by field, with the full field name, such as
   `lines.0.quantity` or `client.id.type`.
4. **On any other code**, `status: false` with `message` and `errors` (a list). The exceptions are `429`
   from the global limit and from cancellations, which return only `message`.

```json
{"status": true, "data": {"...": "..."}}
```

```json
{"status": false, "message": "Përshkrimi i gabimit", "errors": ["Mesazhi për përdoruesin"]}
```

```json
{"success": false, "message": "Te dhenat jo te sakta.", "errors": {"lines.0.quantity": ["..."]}}
```

### HTTP codes

| Code | Meaning | What to do |
| --- | --- | --- |
| `200` with `status: false` | The request was understood, but the action was rejected: user without a fiscal device, cash register without funds, NIPT already registered, rejection by the CIS | Read `message`. Do not retry without changing something |
| `400` | Incomplete data for fiscalization, usually the buyer's address or city, or a `total` that does not match | `errors` says exactly what is missing. The invoice was not saved; retry with the same `internalId` |
| `401` with `{"message": "Unauthenticated."}` | The token is missing, wrong, or revoked | Check the `Authorization` header |
| `401` with `status: false` | The module is not in the business's subscription: invoices, cash register, warehouse transfer notes, or purchase invoices | Regenerating the token does not help; the subscription must be extended |
| `403` | The subscription has expired, or the action is not allowed for this account | |
| `404` | The resource does not exist, or does not belong to your company | |
| `409` | A request with this `internalId` is still in progress | Wait a moment; read it with `POST /invoice/details/{internalId}` |
| `422` | Validation failed | Fix the data. Do not retry the same request |
| `429` | The rate limit was reached | Wait as long as `Retry-After` says, or one second when it is missing |
| `500` | Unexpected error | Retry later with the same `internalId` |
| `502` | The fiscalization service responded with an error (purchase invoices) | Retry later |
| `503` | The fiscalization service could not be reached and the invoice **was not saved** | Retry with the same `internalId` |

## internalId and idempotency

`internalId` (string, required on every issuance) is the invoice identifier in your system:
your invoice number or a UUID. It must be unique within your company for the calendar year.

- **It does not create duplicates.** When you send an `internalId` that already exists for this year, the server does not
  issue a second invoice: it returns the existing invoice with `200`, and if that invoice was awaiting fiscalization,
  it tries to complete it before returning it.
- **That is why retrying is safe.** A timeout or interruption during issuance does not mean the
  invoice failed; it may have been saved. Retry with the **same** `internalId`, never with a new one.
- **`409` means "still in progress".** If you retry while the first request has not finished (up
  to about 5 minutes), you get `409`. Wait a moment and retry, or read the state with
  `POST /invoice/details/{internalId}`.
- fature.al does not return `internalId` in the issuance response; keep the mapping between it and `id`,
  `number`, and `iic` yourself.

## The buyer on the invoice

The `client` object has three flows:

| Case | What you send | Result |
| --- | --- | --- |
| **Walk-in customer** | `client` is missing, or is `null` | The invoice is issued to the company's walk-in customer. Cash invoices only: a non-cash invoice without `client` is rejected with `422` |
| **Client stored in fature.al** | Only `client.internal_id` | The client is taken as stored; other fields are not read |
| **Client from your system** | `client.name` and the other details | The client is looked up by name and identifier. If it does not exist, it is created |

In the third flow:

- `client.name` becomes required as soon as you send any other client detail.
- `client.address` and `client.city` are needed for every named client, because they go to the CIS, which
  does not accept an invoice without them. It is enough for them to be stored on the client in fature.al; they do not need to
  be sent with every invoice. If you send only `client.address`, the stored city is not erased.
- `client.country` is an ISO 3166-1 alpha-3 code, such as `ALB`, `RKS`, or `ITA`. Without it, `ALB` is used.
- `client.id` identifies the buyer: `{"type": "NUIS", "id": "L62221018T"}` for Albanian businesses,
  `VAT` or `TAX` for foreign businesses, `ID`, `PASS`, or `SOC` for individuals. The NIPT is not verified
  with the tax administration during issuance; to verify it beforehand, use `GET /client/search`.

When a required detail is missing, the request is rejected with `400` and `errors` says exactly what must be
filled in, e.g. `Klientit "Kompani Provë SHPK" i mungon qyteti.` ("the client is missing a city"). Neither the invoice nor the client is saved;
retry with the same `internalId` after filling them in.

## Fiscalization when the tax administration does not respond

When the CIS cannot be reached, the invoice is saved and issued anyway: the response comes back with `200`, `status: true`,
and `fic: null`. fature.al retries fiscalization on its own every 15 minutes and fills in `fic` as soon as the CIS
is back. Do not treat it as an error and do not issue it again; read it later with
`POST /invoice/details/{internalId}`.

A `503` is the other case: the invoice **was not saved**. Retry with the same `internalId`.

## Rate limits

Limits are counted per token. When you exceed a limit, the response is `429 Too Many Requests`.

### Limits that return 429 today

| Limit | Value | Response body |
| --- | --- | --- |
| Global, every endpoint | 300 requests per minute per token; 120 per minute per IP without a token | `{"message": "Too Many Attempts."}` with the `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` headers |
| Cancellations, `POST /invoice/cancel/*` | 30 per minute and 600 per hour | The same |
| One request in progress at a time (see below) | For invoice issuance, a wait of up to 1 second; for clients, products, bank accounts, and warehouse transfer notes, immediate rejection | `{"status": false, "errors": ["..."]}`, without `Retry-After` |
| The same queue, but with `200` | `GET /products`, `GET /product/categories` | `200` with `status: false`. Read `status`, not just the code |

### Budget per endpoint group

These are the values the API is sized for. Today some of them are only logged when
exceeded, without rejection; build within them and handle `429`, because they will be enforced without notice.

| Group | Budget | Endpoints |
| --- | --- | --- |
| Standard | 240 per minute | Every endpoint outside the groups below |
| Fiscal | 30 per minute, 600 per hour | `POST /invoice/cash`, `/invoice/noncash`, `/invoice/e-invoice`, `/invoice/order`, `/invoice/summary`, `/invoice/wtn`, `/invoice/cancel/*`, `GET /purchase-invoices` |
| Bulk | 3 per minute, 30 per hour | `POST /invoice/bulk-noncash` |
| Printing | 30 per minute | `GET /invoice/print/{id}`, `/invoice/print-eic/{eic}`, `/invoice/wtn/print/{id}` |
| Reports | 30 per minute | `GET /reports/*`, `GET /dashboard/summary` |
| Search | 90 per minute | `GET /client/search` |
| Registration | 30 per minute, 600 per hour | `POST /register`, `POST /on-boarding/*` |

### One request in progress at a time

Invoice issuance is serialized, because the sequence number is MAX+1 per business and year, and for cash also
per TCR. A second request to the same endpoint waits up to 1 second for the first one to finish;
if it does not, it is rejected with `429`.

Clients, products, bank accounts, and warehouse transfer notes follow the same rule without waiting:
as long as a request from your token to that endpoint has not finished, the second one is rejected
immediately. Sequential calls have no limit other than the budget.

| Endpoints | Queue |
| --- | --- |
| `POST /invoice/cash`, `/invoice/order`, `/invoice/summary` | Per business and TCR, with waiting |
| `POST /invoice/noncash`, `/invoice/e-invoice`, `/invoice/bulk-noncash` | Per business, with waiting |
| `POST /invoice/cancel/*` | The queue of the type of invoice being canceled, with waiting |
| `GET /invoice`, `GET /invoice/{id}/details` | Per token, with waiting |
| `GET`, `POST`, `PUT` on `/clients`; `GET /bank-accounts`; `GET`, `POST`, `PUT` on `/products`; `GET /product/categories`; `GET` and `POST` on `/invoice/wtn` | Per token and endpoint, without waiting |

This is the limit multi-threaded integrations hit most often. The fix is not immediate
retrying, but queuing requests per business: parallelism does not produce more invoices, only more
waiting.

### Best practices

- Wait as long as `Retry-After` says. When it is missing, wait one second and retry with progressive backoff:
  1 s, 2 s, 4 s.
- For many non-cash invoices, use `POST /invoice/bulk-noncash`: the budget counts requests, not invoices.
- Cache the catalog, categories, and currencies, and do not read them in parallel from the same
  token.

## Pagination

Not all lists use the same pagination.

| Endpoint | Parameters | `total` | `limit` cap |
| --- | --- | --- | --- |
| `GET /invoice`, `GET /invoice/wtn`, `GET /cash-register/actions` | `limit`, `offset` | No | No cap |
| `GET /clients`, `GET /bank-accounts` | `limit`, `offset` | Yes | 100 |
| `GET /products` | `limit`, `offset` | Yes | 500. Without `limit`, the entire catalog is returned |
| `GET /purchase-invoices` | `page`, from 1 | No | |

Without `limit`, `20` is used, and without `offset`, `0` is used, except for products. The counters come in the
`pagination` object: `records` (how many came on this page), `total` (how many exist, where it is computed),
`limit`, and `offset`. When there is no `total`, stop when a page comes back shorter than `limit`. For
purchase invoices, repeat with `page + 1` until `items` comes back empty.

Lists are returned newest first. When reading a closed period, bound it with
`fromDate` and `toDate` so that new invoices do not shift the pages.

The product list keeps `pagination` **alongside** `data`, not inside it, and `data` remains a plain
list: `data[0]` is a product, whereas in the other lists the items are in `data.items`.

## Data formats

| Data | Format |
| --- | --- |
| Dates | `YYYY-MM-DD` |
| Date and time | `YYYY-MM-DD HH:MM:SS`, Albanian local time (CET/CEST), without a time zone |
| Amounts | JSON numbers, not strings, in the invoice currency. `*_all` fields are in lek |
| Currency | ISO 4217 code. Without `currency`, `ALL` is used. `exchange_rate` is how many lek one unit of the currency is worth |
| VAT rates | `0`, `6`, `10`, or `20`, as numbers |
| Booleans | `true` or `false` in JSON |
| NIPT | One letter, eight digits, one letter: `L62221018T` |
| Country | ISO 3166-1 alpha-3: `ALB`, `RKS`, `ITA` |
| Fiscal identifiers | `iic` 32 hexadecimal characters; `fic` and `eic` UUID |
| Encoding | UTF-8. Send `Content-Type: application/json` |

### Unit of measure codes

`unit_code`, on products and on e-invoice lines, accepts only the codes below (UN/ECE Rec. 20,
per the tax administration's list). The code is what is written on the fiscal document; the unit
text is derived from it and is not sent.

| Group | Codes |
| --- | --- |
| Count | `C62` unit, `XPP` piece, `PR` pair, `DZN` dozen, `SET` set, `IE` person, `E55` use, `E54` trip, `QB` page, `D68` word, `H93` percent |
| Weight | `KGM` kg, `58` net kg, `E4` gross kg, `GRM` g, `DTN` quintal, `TNE` tonne, `DT` dry ton |
| Volume | `LTR` liter, `MLT` milliliter, `MTQ` m³ |
| Length and area | `MTR` meter, `CMT` cm, `MMT` mm, `LM` linear meter, `KMT` km, `MTK` m² |
| Time | `SEC` second, `MIN` minute, `HUR` hour, `DAY` day, `MON` month, `ANN` year, `E49` working day, `OT` overtime hour |
| Other | `KWH` kWh, `MSK` m/s², `M4` monetary value |
| Packaging | `XBX` box, `XPK` pack, `XPX` pallet, `XRO` roll, `XBG` bag, `XBO` bottle, `XCT` carton, `XCR` crate, `XSA` sack, `XCA` can, `XBA` barrel |

## Identify your application

If your software is used by more than one business, your application is identified by a pair of
credentials that you send with every request, alongside the user's token:

```
X-Client-Id: ft_id_a1b2c3d4e5f6g7h8i9j0k1l2
X-Client-Secret: ft_sk_...
```

- The **client id** starts with `ft_id_` and is public. The **client secret** starts with `ft_sk_` and is
  secret: treat it like a password.
- Both belong to your application, not to the business. They are not generated per business and must not
  end up in code that is distributed and can be read by others.
- You get your first credentials from fature.al when your integration is registered. After that, you view and
  regenerate them yourself under **Settings > Developer** in your fature.al account, where you can also see how many
  users and how many businesses have worked through your application in the last 24 hours.

The secret is shown **only once**, at the moment it is generated, and cannot be read afterward.
Store it immediately.

If you lose it or suspect it has leaked, regenerate it from the same screen. **Regeneration is
immediate, with no overlap period:** the old secret stops working on the first request that
arrives after it. So prepare your installations to pick up the new secret, and regenerate it only
when you are ready to roll it out immediately. The client id does not change, so you only have to replace
a single value.

### Keep sending the User-Agent header too

In addition to the credentials, send the `User-Agent` header with every request in the format
`<application name>/<build version>`:

```
User-Agent: YourAppName/2.4.1
```

- The **application name** must always be the same: on every request, for every business, and in
  every version of the application. This name is your integration's identifier, so do not change it
  and do not vary it by company, device, or environment. Use letters, digits, or the characters
  `_ . + -`, start with a letter, and do not include spaces.
- The **build version** changes with every release of your application, e.g. `2.4.1` or
  `2026.07.13`. No spaces and no `/`.

The credentials tell us which application is talking, while the User-Agent tells us which version of it is
in use, which helps us react faster when you report a problem. If the credentials are
missing, the User-Agent remains the only way to identify your integration.

### Transition to enforcement

If the headers are **not sent at all**, the request proceeds normally and the integration is identified only by
the User-Agent. Each software provider is notified in advance of the date from which credentials become
required for its application; only after that date are requests without credentials rejected with
`401 Unauthorized`.

If the headers **are sent but fail verification**, the request is rejected immediately with `401 Unauthorized`,
regardless of any date. If you do not have credentials yet, do not send the headers empty or with placeholder
values.

| Cause | Rejected | `message` |
| --- | --- | --- |
| Headers missing | Only after the enforcement date | Kjo kerkese duhet te dergoje header-at X-Client-Id dhe X-Client-Secret. |
| Client id not recognized | Always | Client ID nuk njihet. |
| Client secret wrong or missing | Always | Client secret nuk eshte i sakte. |
| App suspended | Always | Ky aplikacion integrimi eshte pezulluar. |

Start sending the pair now: requests with valid credentials work exactly as before.

## Company registration by partners

Software providers can open their clients' accounts themselves with the **Company registration**
group. The group is closed by default and opened on request. The order of steps:

1. `POST /register`: creates the company and returns the first user's token and the unit id.
2. `POST /on-boarding/certificate`: uploads the electronic certificate, without which nothing can be
   signed.
3. `POST /on-boarding/branch/{id}`: sets the `businessUnitCode` on the unit.
4. `POST /on-boarding/fiscal-device`: registers the TCR with the tax administration and returns its code. Only for
   cash invoices.
5. `POST /on-boarding/user/{id}`: sets the `operatorCode` and `fiscalTcrCode` on the user.
6. `POST /on-boarding/bank-account`: the bank account for non-cash invoices and e-invoices.

In sandbox, the NIPT `L62221018T` on `POST /register` goes through a test flow that does not create a new
company. Details are in the group.

## Postman and other tools

We do not ship a Postman collection, because it would go stale every time the API changes. Import the
OpenAPI document directly; it is generated on every request and is always up to date:

**Postman** > *Import* > *Link* > `https://fature.al/docs/api.json`

The same URL works for Insomnia, Bruno, Swagger UI or any client generator. For v2 and for the
partner API, use `/docs/api/v2.json` and `/docs/api/partner.json`. After importing, set
`Authorization: Bearer <api_token>`, `X-Client-Id` and `X-Client-Secret` as collection variables
and select the Sandbox server until the integration is ready.

## Changelog

Every addition to the API, newest first, with the date it landed in the code. No existing field
has changed meaning and no endpoint has been removed; see also *Servers and versions*.

| Date | Change |
| --- | --- |
| 2026-09-28 | The warehouse transfer note returns the fiscalization time, not just the date |
| 2026-09-24 | v2: warehouse transfer notes, with a required `internalId` |
| 2026-09-14 | v2: `payment_methods[].amount` accepts any number, including 0 and negatives, like the CIS |
| 2026-09-12 | v2: invoice payment as a `payment_methods` list, in issue, cancel, list, details, bulk and PDF |
| 2026-09-07 | Cancellations get their own rate limit: 30 per minute and 600 per hour per token |
| 2026-08-26 | `POST /on-boarding/branch`: partners create business units |
| 2026-08-04 | App credentials: the `X-Client-Id` and `X-Client-Secret` headers |
| 2026-07-22 | Wolt Partner API, with its own document |
| 2026-07-15 | `GET /purchase-invoices` |
| 2026-07-07 | `GET /reports/cash-register/closing` |
| 2026-06-09 | v2: clients in the new format, `/api/v2/clients` |
| 2026-05-19 | `PUT /clients/{id}` and `PUT /products/{id}`; `GET /currencies` and `GET /exchange-rates` |
| 2026-04-24 | `GET /products/{id}/details` |
| 2026-04-22 | `GET /bank-accounts`; `GET /dashboard/summary` |
| 2026-04-21 | Reports under `/reports`; `GET /cash-register/balance` |
| 2026-04-20 | Cash register: `deposit`, `withdraw` and the `actions` list |
| 2026-04-19 | Clients (list, details, create); `POST /products`; warehouse transfer notes (WTN) |
| 2026-02-26 | Order and summary invoices |
| 2026-02-19 | `GET /invoice/{id}/details` |
| 2025-02-04 | `GET /invoice` |
| 2023-12-11 | `POST /invoice/details/{internalId}` |
| 2023-10-31 | `POST /invoice/cancel-by-internal-id/{internalId}` |
| 2023-10-17 | Cash register: opening and closing the day; `GET /register` |
| 2023-09-28 | Company registration by partners and onboarding: certificate, unit, user, fiscal device, bank account |
| 2023-09-23 | `GET /client/search` |
| 2023-01-30 | `POST /invoice/bulk-noncash` |
| 2022-12-02 | First version: issuing cash, non-cash and e-invoices |

## Support

Integration questions: [info@fature.al](mailto:info@fature.al). If you are a software house and want
to bring your clients onto fature.al, write to us: we will give you app credentials and a
test environment.

## Servers

- `https://fature.al/api/v1` - Live
- `https://demo.fature.al/api/v1` - Sandbox

## Endpoints

### GET /account

**Summary:** Get the account details

Returns the token's user and the fiscal context every invoice issued with it gets:
the company, the business unit, the fiscal device, the rights and the subscription modules.

Use it after `GET /ping` to confirm which business you are working with, and before the first cash
invoice to see whether the user has a fiscal device. None of these values are sent in
invoicing requests; they are resolved from the token.

| Field | What it is |
| --- | --- |
| `name`, `email` | The user who holds the token |
| `company`, `nipt` | The name and NIPT of the company issuing the invoices |
| `fiscalCode` | The TCR code of the user's fiscal device. Empty string when there is none; cash invoices and cash register actions are then rejected |
| `branch` | The user's business unit: `name`, `address` and `city` |
| `companyBranches` | All of the company's units, as an object with the address as key and the name as value |
| `setupFinished` | `true` when the company has completed the setup steps and can issue invoices |
| `permissions` | `createArticles` and `createClients`: whether the user can create products and clients. Without them, `POST /products` and `POST /clients` are rejected with `403` |
| `configs.openDayAlways0` | `true` when the company always opens the day on the cash register with a balance of `0` |
| `vatConfigs` | `issuerInVat` says whether the company is in the VAT scheme. `vat_rates` are the rates accepted on invoice lines: `20`, `10`, `6` and `0` in the scheme, only `0` outside it. `vat_exempts` are the accepted exemption types, with `code` and `label` |
| `subscription` | `cash`, `noncash`, `einvoice` and `wtn`: which modules the subscription includes. The endpoint of a missing module responds `401` with `status: false` |

Store `nipt` and `fiscalCode` alongside the integration's configuration, so a token swapped
by mistake is caught before it issues invoices on behalf of another business.

**Tags:** Account

**Responses:**

- `200` - The token's user and their fiscal context: the company, the business unit, the fiscal device, the rights and the subscription modules.
- `401`

---

### GET /bank-accounts

**Summary:** List bank accounts

Returns the company's active bank accounts, with the default account first and then
newest to oldest, with text search.

Use it to choose the account that goes on a non-cash invoice or an e-invoice: store
the `id` for `bank_account`, or the `iban` for `bank_account_iban`. The list rarely changes, so
cache it.

- Pagination with `limit` and `offset`. `limit` is capped at `100`; a larger value is lowered without
  an error. `pagination` returns `total`, so continue with `offset += limit` until `offset`
  reaches `total`.
- `query` searches by partial match on the bank name, IBAN, SWIFT, holder and
  notes.
- A second request from the same token, while the first has not finished, is rejected immediately with
  `429`.

Each account comes with `bank_name`, `iban`, `swift`, `holder`, `currency_code` (ISO 4217),
`is_default`, `notes`, `valid_from` and `valid_to` when the account has a validity period, and
`created_at` (ISO 8601).

**Tags:** Bank accounts

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `limit` | query | no | How many accounts to return per page, up to 100. |
| `offset` | query | no | Which account to start from, counting from 0. |
| `query` | query | no | Search in the bank name, IBAN, SWIFT, holder or notes. |

**Responses:**

- `200` - The company's active bank accounts, with the default first, and `pagination` with `total`; `items` is empty when the company has no accounts.
- `401`
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### GET /cash-register/actions

**Summary:** List cash register actions

Returns the cash register history, newest first, filtered by type and date:
cash ins, cash outs, and day openings and closings.

Use it to reconcile the shift and to read the fiscal state of an action that was
saved while the CIS could not be reached.

| `type` | Action |
| --- | --- |
| `MONEY_IN` | Cash in |
| `MONEY_OUT` | Cash out |
| `OPEN_DAY` | Opening the day |
| `CLOSE_DAY` | Closing the day |

- Several types are separated by commas: `type=MONEY_IN,MONEY_OUT` returns only money movements.
- The closing from `POST /cash-register/close-balance` appears as `MONEY_OUT` with the description
  "Mbyllje turni/xhiro ditore" (shift/daily close), not as `CLOSE_DAY`.
- `fromDate` and `toDate` limit by action date, both inclusive.
- Pagination with `limit` and `offset`. `pagination` does not return `total`, so stop when a page
  comes back shorter than `limit`.

`amount` is always positive, in ALL; `action_type` gives the direction. `fiscal_status` and
`fiscal_fic` say whether the action has been fiscalized.

**Tags:** Cash register

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `limit` | query | no | How many actions to return per page. |
| `offset` | query | no | Which action to start from, counting from 0. |
| `type` | query | no | Filter by type: `MONEY_IN`, `MONEY_OUT`, `OPEN_DAY` or `CLOSE_DAY`, several types separated by commas. |
| `fromDate` | query | no | Start date, `YYYY-MM-DD`. |
| `toDate` | query | no | End date, `YYYY-MM-DD`. |

**Responses:**

- `200` - The period's actions, newest first, with `pagination`; `items` is empty when there are no actions.
- `401`
- `500` - Unexpected server error.

---

### GET /cash-register/balance

**Summary:** Get the cash register balance

Returns the balance of the fiscal device (TCR) of the token's user, in ALL, together with its
`fiscal_tcr_code`.

Without `toDate`, the current balance is returned. With `toDate`, the balance at the end of that day is returned, for
reconciliation with a past day. The balance is cached for 5 seconds, so a just-recorded
action may show up a moment later.

- The user must have a TCR.

**Tags:** Cash register

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `toDate` | query | no | The date, `YYYY-MM-DD`, up to which the cash register balance is calculated. Without it, the latest balance is returned. |

**Responses:**

- `200` - When the user has no fiscal device, the response is returned with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `500` - Unexpected server error.

---

### POST /cash-register/close-balance

**Summary:** Close the day on the cash register

Closes the working day on the fiscal device (TCR) of the token's user: takes the whole
balance out of the cash register and returns the amount that went out.

Call it at the end of the shift, without first checking whether the day is open: when it is not,
it is opened automatically with `0` and closed immediately. The request has no body.

- The user must have a TCR.

`balance` is the amount in ALL that went out of the cash register; `0` when the cash register was empty. The cash out appears in
`GET /cash-register/actions` as a `MONEY_OUT` action with the description "Mbyllje turni/xhiro ditore" (shift/daily close); when the cash register was empty, no cash out is recorded. When the CIS cannot be reached, the closing
is saved and is fiscalized automatically later; the response is the same.

**Tags:** Cash register

**Responses:**

- `200` - When the user has no fiscal device or when closing fails, the response is returned with HTTP 200 and `status: false`; the reason is in `message`.
- `401`

---

### POST /cash-register/deposit

**Summary:** Record a cash in

Records money that enters the cash register without being a sale, such as a collection from a client or an opening
float, as a `MONEY_IN` action on the fiscal device (TCR) of the token's user.

For a sale, issue a cash invoice, not a cash in. If the day has not been opened yet, it is opened automatically with
`0`.

- `amount` is in ALL and must be greater than `0`.
- `id_client`, when sent, must be the `id` of a client of your company.
- The user must have a TCR.

The response carries the action's `id`, its numbers (`transaction_no`, `cash_action_no`) and
fiscal state (`fiscal_status`, `fiscal_uuid`, `fiscal_fic`). When the CIS cannot be reached,
the action is saved and is fiscalized automatically later: the response comes with `status: true`,
`fiscal_status: UNFISCALISED` and no `id`; read it afterwards in `GET /cash-register/actions`.

**Tags:** Cash register

**Request body content types:** application/json

**Responses:**

- `200` - When the user has no fiscal device, when `id_client` does not belong to your company, or when saving fails, the response is returned with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.

---

### POST /cash-register/open-balance

**Summary:** Open the day on the cash register

Opens the working day on the fiscal device (TCR) of the token's user with the opening
`balance`, in ALL, and records it as an `OPEN_DAY` action.

Call it at the start of the shift, before the first cash invoice. It is not required: the first
cash invoice, a cash in, a cash out or closing the day open the day automatically with `0`. Use it when the day
starts with money in the cash register and you want the balance to be accurate.

- `balance` is optional; without it, `0` is used. A negative value is rejected.
- The user must have a TCR.

The response returns `balance` as it was accepted. When the CIS cannot be reached, the opening is saved and
is fiscalized automatically later; the response is the same.

**Tags:** Cash register

**Request body content types:** application/json

**Responses:**

- `200` - When the user has no fiscal device, when `balance` is negative, or when saving fails, the response is returned with HTTP 200 and `status: false`; the reason is in `message`.
- `401`

---

### POST /cash-register/withdraw

**Summary:** Record a cash out

Records money that leaves the cash register, such as a supplier payment or a withdrawal during the day, as a
`MONEY_OUT` action on the fiscal device (TCR) of the token's user.

To withdraw the whole balance at the end of the shift, use `POST /cash-register/close-balance`.
If the day has not been opened yet, it is opened automatically with `0`.

- `amount` is in ALL and must be greater than `0`; the sign comes from the endpoint, not
  from the number.
- The cash register must hold at least `amount`; when it does not, the cash out is rejected.
- `id_client`, when sent, must be the `id` of a client of your company.
- The user must have a TCR.

The response has the same format as the cash in. When the CIS cannot be reached, the action is saved and
is fiscalized automatically later: the response comes with `status: true`, `fiscal_status: UNFISCALISED`
and no `id`; read it afterwards in `GET /cash-register/actions`.

**Tags:** Cash register

**Request body content types:** application/json

**Responses:**

- `200` - When the user has no fiscal device, when the cash register does not hold as much money as `amount`, when `id_client` does not belong to your company, or when saving fails, the response is returned with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.

---

### GET /client/search

**Summary:** Look up a business by NIPT

Looks up a business in the fiscal registry by NIPT and returns its name, address, city and
country.

Use it to fill in a new client's details from a single field, or to
catch a wrong NIPT before it ends up on an invoice. For clients you have already saved in
fature.al, use `GET /clients` with `query`.

`verified` says whether the registry confirmed the business. The two rejection responses are not the
same thing:

- `404`: the NIPT is not in the registry.
- `503`: the registry could not be reached and the NIPT was left unchecked. Retry later.

**Tags:** Clients

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `nuis` | query | yes | The NIPT to look up in the fiscal registry: one letter, eight digits, one letter. Lowercase letters are accepted. Required. |

**Responses:**

- `200` - The business found, in `client`, with `verified: true`.
- `401`
- `422` - The NIPT is missing or not in the X00000000X format. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `404` - The NIPT is not in the fiscal registry.
- `503` - The fiscal registry could not be reached and the NIPT was left unchecked. Retry later.

---

### GET /clients

**Summary:** List clients

Returns the company's clients, newest first, with text search. Each client comes
with the same fields as in `GET /clients/{id}/details`.

Use it to find the `id` of a saved client, or to sync your
records with those in fature.al.

- Pagination with `limit` and `offset`. `limit` is capped at `100`; a larger value is lowered without
  an error. `pagination` returns `total`, so continue with `offset += limit` until `offset`
  reaches `total`.
- `query` searches by partial match on the company name, first name, last name, NIPT,
  document number, email, phone and client number.
- A second request from the same token, while the first has not finished, is rejected immediately with
  `429`.

`company_type` is returned `null` for a person, and `first_name` and `surname` for a company;
`name` always holds the full name.

**Tags:** Clients

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `limit` | query | no | How many clients to return per page, up to 100. |
| `offset` | query | no | Which client to start from, counting from 0. |
| `query` | query | no | Search in the company name, first name, last name, NIPT, document number, email, phone or client number. |

**Responses:**

- `200` - The page's clients in `items`, with `pagination`; `items` is empty when no client matches.
- `401`
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### POST /clients

**Summary:** Create a client

Creates a new client for the company, a company or a person, and returns its `id`.

Use it when you keep your client records in fature.al. When you need the client for only one
invoice, send it in the invoice's `client` object; this endpoint is not needed.

`client_type` determines which fields are required:

| `client_type` | Required fields |
| --- | --- |
| `COMPANY` | `company_name`, `nipt`, `address`, `city` |
| `PERSON` | `name`, `surname`, `document_type`, `document_number` |

- For an Albanian business, call `GET /client/search` first: the name, address and city come
  prefilled from the registry.
- The NIPT is unique within the company; a second client with the same NIPT is rejected with `409`.
- A second request from the same token, while the first has not finished, is rejected immediately with
  `429`.

Store the returned `id`: you use it to read and update the client later.

**Tags:** Clients

**Request body content types:** application/json

**Responses:**

- `200` - The newly created client in `client`, with its `id`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `409` - A client with this NIPT already exists.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### PUT /clients/{id}

**Summary:** Update a client

Updates an existing client and returns its data after the change.

The request body has the same format and the same required fields per `client_type`
as `POST /clients`.

- The new NIPT cannot match another client's; it is rejected with `409`.
- A second request from the same token, while the first has not finished, is rejected immediately with
  `429`.

**Tags:** Clients

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The client's id in fature.al. |

**Request body content types:** application/json

**Responses:**

- `200` - The client after the change, in `client`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `404` - The client was not found, or does not belong to your company.
- `409` - Another client with this NIPT already exists.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### GET /clients/{id}/details

**Summary:** Get a client by id

Returns a single client with all fields, a company or a person.

Use it when you have stored the `id` from the list or from creation and need the
up-to-date data. `company_type` is returned `null` for a person, and `first_name` and `surname` for
a company; `name` always holds the full name.

**Tags:** Clients

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The client's id in fature.al. |

**Responses:**

- `200` - The requested client in `client`, with all fields.
- `401`
- `404` - The client was not found, or does not belong to your company.
- `500` - Unexpected server error.

---

### GET /currencies

**Summary:** List currencies

Returns the currencies configured for the company, with `ALL` always first and the rest by
code.

Use it to fill the currency picker in your system: `code` is the ISO 4217 code you
send as `currency` on invoices and products. Without `currency`, `ALL` is used. For the day's
rate, call `GET /exchange-rates`.

- `id` is the currency's id in fature.al; `name` holds the same value as `code`.
- The list changes only when the business adds a currency from the dashboard, so cache it.

**Tags:** Currencies

**Responses:**

- `200` - The currencies configured for the company, with `ALL` first and the rest by code.
- `401`

---

### GET /dashboard/summary

**Summary:** Get the daily summary

Returns a quick view of today for the home screen, in three objects:

| Object | Contents |
| --- | --- |
| `shift` | `is_open`, `opened_at`, `cash_in_drawer` and `currency` for the user's fiscal device. Without a device, `is_open` is `false` and `cash_in_drawer` is `0`. |
| `today` | `invoice_count`, `gross_total` and `hourly_series`, one point for every two hours with `hour` and `gross`. |
| `week` | `series`, one point for each of the last 7 days with `date` and `gross`, and `wow_delta_percent`, the percentage change against the previous 7 days, `null` when those have no sales. |

Sales count cash invoices, non-cash invoices and e-invoices, excluding order invoices and
canceled invoices. Amounts are in ALL.

The response is cached for 5 seconds per user, so it is not a source for reconciliation:
for exact figures over a period use the reports.

**Tags:** Dashboard

**Responses:**

- `200` - The daily summary in `data`, in three objects: `shift`, `today` and `week`; the figures can be up to 5 seconds old from the cache.
- `401`
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /exchange-rates

**Summary:** Get the day's exchange rates

Returns the day's selling rate for each currency, in lek per unit of the currency, from the
selected source. The data comes from kursi.al and is cached on the server for one hour.

Use it to suggest `exchange_rate` when you issue an invoice in a foreign currency. The rate that
goes on the invoice is the one you send, not this one.

| `source` | Source |
| --- | --- |
| `BOA` | Bank of Albania, official rate |
| `BKT` | Banka Kombëtare Tregtare |
| `Iliria98` | Iliria '98 currency exchange |
| `ADON` | Adon currency exchange |

- `source` is case-sensitive: `bkt` is not recognized. An unrecognized source does not return an error;
  `BOA` is used, and `source` in the response says which source was used.
- `rates` is an object with the currency code as key and the rate as value, always with `ALL: 1`.
  `EUR: 98.5` means one euro is worth 98.5 lek.
- `fetchedAt` is the response time, ISO 8601.

When kursi.al cannot be reached, `rates` holds only `ALL: 1`, with no error, for up to one hour. Do not
treat it as a rate: keep the last complete snapshot and use that.

**Tags:** Currencies

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `source` | query | no | The rate source: `BOA`, `BKT`, `Iliria98` or `ADON`, with the letters exactly as shown. Any other value falls back to `BOA`. |

**Responses:**

- `200` - The day's selling rate for each currency from the `source`, always with `ALL: 1`; when kursi.al cannot be reached, `rates` holds only `ALL: 1`.
- `401`

---

### GET /invoice

**Summary:** List invoices

Returns the company's invoices, newest first, filtered by type, date and a
search text. Each invoice comes with the buyer, the amounts and the payment, but without the lines; for the lines,
call `GET /invoice/{id}/details`.

- Pagination with `limit` and `offset`. `pagination` does not return a `total`, so stop when a page
  comes back shorter than `limit`.
- When you read a closed period, bound it with `fromDate` and `toDate`, so new invoices do not
  shift the pages.
- A second request from the same token, while the first has not finished, waits up to one second
  and is then rejected with `429`.

**Tags:** Invoices

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `limit` | query | no | How many invoices to return per page. |
| `offset` | query | no | Which invoice to start from, counting from 0. |
| `type` | query | no | Filter by type: `CASH`, `NONCASH` or `EINVOICE`, several types separated by commas. Any other value is ignored without an error; if none remains valid, the filter is not applied at all. |
| `fromDate` | query | no | Start date, `YYYY-MM-DD`. |
| `toDate` | query | no | End date, `YYYY-MM-DD`. |
| `query` | query | no | Search by invoice number, buyer name or NIPT. |

**Responses:**

- `200` - The period's invoices in `items`, newest first, with `pagination`; `items` is empty when there are no invoices.
- `401`
- `422` - The dates are not in `YYYY-MM-DD` format.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### POST /invoice/bulk-noncash

**Summary:** Issue bulk non-cash invoices

Issues many non-cash invoices in a single request, for periodic invoicing. Each element of
`invoices` has the same fields as the `POST /invoice/noncash` request body, `internalId` included, and
is issued under the same rules.

The response is an object with one entry per `internalId`, and the HTTP status stays `200`
even when some invoice fails:

- check `status` inside each entry separately, not the HTTP code;
- retry only the `internalId`s that failed, not the whole bulk;
- invoices are issued one by one, in order, so a large bulk takes as long as the sum of the issuances.

## Example: group advance payment

One invoice for each member who pays their own share.

```json
{
  "invoices": [
    {
      "internalId": "DEP-GRP-0007-01",
      "payment_method": "TRANSFER",
      "client": {
        "name": "Arben Hoxha"
      },
      "lines": [
        {
          "product_name": "Advance, group G-0007",
          "product_code": "DEP-GRP",
          "unit": "piece",
          "quantity": 1,
          "price": 15000,
          "total": 15000,
          "vat": 6
        }
      ]
    },
    {
      "internalId": "DEP-GRP-0007-02",
      "payment_method": "TRANSFER",
      "client": {
        "name": "Elira Kola"
      },
      "lines": [
        {
          "product_name": "Advance, group G-0007",
          "product_code": "DEP-GRP",
          "unit": "piece",
          "quantity": 1,
          "price": 15000,
          "total": 15000,
          "vat": 6
        }
      ]
    }
  ]
}
```

**Tags:** Invoices

**Request body content types:** application/json

**Responses:**

- `200` - An object with one entry per `internalId`. An invoice that fails carries the error format inside its own entry, and the response status stays 200.
- `401`

---

### POST /invoice/cancel-by-internal-id/{internalId}

**Summary:** Cancel an invoice by internalId

Cancels the invoice by looking it up with the `internalId` you sent when you issued it, so you do not
need to keep the fature.al `id` next to your sale. Behaves the same as cancellation by id:

- with no body, the whole invoice is canceled;
- with `lines`, only part is canceled, possible **only for cash invoices**;
- lines must match on `product_code` and `product_name`, and the quantity can only decrease;
- an invoice with a discount on the whole invoice can only be canceled in full.

The response is the corrective document, with its own `iic` and `fic`. When the CIS cannot be reached,
the cancellation is saved with `fic: null` and is fiscalized automatically later.

**Tags:** Invoices

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `internalId` | path | yes | Your identifier for the invoice, sent when it was issued. |

**Request body content types:** application/json

**Responses:**

- `200` - The corrective document, with its own `iic` and `fic`; `fic` is `null` when the CIS could not be reached and fiscalization will complete automatically.
- `401`
- `422` - The invoice cannot be canceled in its current state, or the lines sent do not match the original. `errors` says why.
- `404` - The invoice was not found, or does not belong to your company.
- `500` - Unexpected server error, including when the cancellation could not be registered with the CIS.
- `429` - The cancellation limit was reached, or another cancellation of the same type is still in progress. When the cancellation limit is hit, the response carries only `message` and the `Retry-After` header.

---

### POST /invoice/cancel/{id}

**Summary:** Cancel an invoice by id

Cancels the invoice by issuing a corrective document to the CIS, which gets a new sequence number.
The original is marked canceled only when the cancellations cover all of it.

## Full or partial

| Body | What happens |
| --- | --- |
| Empty | The whole invoice is canceled, whatever its type |
| With `lines` | Only the part sent is canceled. **Cash invoices only** |

## Partial cancellation rules

- Every line must exist on the original invoice with the same `product_code` and
  `product_name`.
- The quantity can only decrease or stay the same; products you do not send remain sold.
- When the same product was invoiced on two lines at different prices, add `price` to
  pick the line.
- An invoice with a discount on the whole invoice can only be canceled in full: the discount is spread
  across all VAT rates, and a subset of the lines cannot carry its own share without getting
  the VAT wrong.

## Example: partial cancellation

One breakfast is returned from a cash invoice; the rest of the invoice stays sold.

```json
{
  "lines": [
    {
      "product_name": "Breakfast",
      "product_code": "MNG-01",
      "quantity": 1
    }
  ]
}
```

The response is the corrective document, with its own `iic` and `fic`. When the CIS cannot be reached,
the cancellation is saved with `fic: null` and is fiscalized automatically later. Cancellations have their own
rate limit: 30 per minute and 600 per hour per token.

**Tags:** Invoices

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The invoice's id in fature.al. |

**Request body content types:** application/json

**Responses:**

- `200` - The corrective document, with its own `iic` and `fic`; `fic` is `null` when the CIS could not be reached and fiscalization will complete automatically.
- `401`
- `422` - The invoice cannot be canceled in its current state, or the lines sent do not match the original. `errors` says why.
- `404` - The invoice was not found, or does not belong to your company.
- `500` - Unexpected server error, including when the cancellation could not be registered with the CIS.
- `429` - The cancellation limit was reached, or another cancellation of the same type is still in progress. When the cancellation limit is hit, the response carries only `message` and the `Retry-After` header.

---

### POST /invoice/cash

**Summary:** Issue a cash invoice

Issues an invoice paid on the spot from the fiscal device (TCR) linked to the token's
user: cash, card, check, company card or vouchers. If the day has not been opened on the cash register
yet, it opens automatically with a balance of `0`.

The token's user must have a TCR; without one, the request is rejected with `200` and
`status: false`.

## The buyer is optional

Without `client`, the invoice is issued to the walk-in customer, which is the normal case at a till. As soon as
you send even one client detail, `client.name`, `client.address` and
`client.city` are required, because the address and city go to the CIS. The address and city can also
come from a client saved earlier in fature.al, so you do not need to send them on every
invoice. When one is missing, the request is rejected with `400` and `errors` says exactly what needs to be
filled in.

## Fields that depend on the payment method

Two fields are required for only one payment method, and rejected for any other:

| `payment_method` | Required field | Limit |
| --- | --- | --- |
| `COMPANY` | `company_card` | Up to 50 characters |
| `SVOUCHER` | `vouchers` | Up to 20, in the form number-year-NIPT, no duplicates |

`company_card` with `BANKNOTE`, or `vouchers` with `CARD`, are rejected: they have no place in the
fiscal document, so they are not silently accepted.

## Discount

A discount on the whole invoice is sent with `invoice_discount_type` and `invoice_discount_value`
together; a line discount with `lines[].discount`, as a percentage of the unit price, with
the full `price` and the `total` already reduced. Both can be on the same invoice, and the
full rules are in the request fields.

## Self-invoicing is paid from the cash register

With `self_issue_type`, the seller is paid for the invoice in cash from the cash register, so the cash register must hold at
least the invoice total, in the invoice currency. When it does not, the invoice is not saved and not
fiscalized: the response comes back with HTTP 200, `status: false` and a `message` with the cash register balance
and the invoice amount. Record a cash in and send the invoice again with the same
`internalId`.

## Example: advance payment at the front desk

An advance paid in cash or by card is invoiced as a cash sale, for the amount received.

```json
{
  "internalId": "DEP-2026-0031",
  "payment_method": "CARD",
  "notes": "Advance for reservation R-88213, 12 to 15 October",
  "lines": [
    {
      "product_name": "Advance, reservation R-88213",
      "product_code": "DEP-REZ",
      "unit": "piece",
      "quantity": 1,
      "price": 10000,
      "total": 10000,
      "vat": 6
    }
  ]
}
```

## After issuing

The response carries `iic`, `fic`, `tcrCode` and `pdf`. When the CIS cannot be reached, the invoice is saved and
`fic` is `null` until fiscalization completes on its own.

**Tags:** Invoices

**Request body content types:** application/json

**Responses:**

- `200` - When the user has no fiscal device, when the cash register does not cover a self-invoice, or when the invoice is rejected after validation by the CIS or while saving, the response comes back with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field. Only a missing `internalId` comes back in the standard error format.
- `400` - Incomplete or invalid data for fiscalization. `errors` says exactly what needs to be filled in. The invoice was not saved; retry with the same `internalId`.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `409` - A request with this `internalId` is still in progress. Wait a moment and retry, or read the state with `POST /invoice/details/{internalId}`.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.
- `503` - The fiscalization service could not be reached and the invoice was not saved. Retry with the same `internalId`.

---

### POST /invoice/details/{internalId}

**Summary:** Get an invoice by internalId

Returns the issue result of the invoice you sent with this `internalId`: `id`, `number`,
`iic`, `fic`, the fiscal codes and `pdf`. Use it to read the state after a timeout or
after a `409`, and to see whether `fic` was filled in after a deferred fiscalization.

For the buyer, the amounts and the lines, use `GET /invoice/{id}/details`.

**Tags:** Invoices

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `internalId` | path | yes | Your identifier for the invoice, sent when it was issued. |

**Responses:**

- `200` - The issue result of the invoice with this `internalId`, the same one the issue call returned.
- `401`
- `404` - No invoice with this `internalId` in your company.
- `422` - `internalId` is empty.
- `500` - Unexpected server error.

---

### POST /invoice/e-invoice

**Summary:** Issue an e-invoice

Issues an electronic invoice to a business: it is fiscalized with the CIS and sent to the
central e-invoicing platform, which delivers it to the buyer as a UBL document.

Use it when the buyer is a business with a NIPT and the invoice must reach them electronically. For
a sale with deferred payment that does not go to the e-invoicing platform, use
`POST /invoice/noncash`.

## The buyer

`client` is required: `client.id` of type `NUIS` (or `VAT` and `TAX` for a foreign
business), `client.name`, and `client.address` with `client.city`, either in the request or saved
earlier on the client in fature.al. When a value is missing, the request is rejected with `400` and
`errors` says exactly what needs to be filled in. Every line requires `unit_code`.

## Document type

`doc_type` is the document type and `process` its UBL profile. Both are
required; for a regular invoice, send `380` and `P1`. Validation also accepts the other document types
and profiles of the UBL standard, listed in the request fields:

| Document | `doc_type` | `process` | Requires `original_invoice_iic` |
| --- | --- | --- | --- |
| Sales invoice | `380` | `P1` | No |
| Credit note | `381` | `P9` | **Yes** |
| Debit note | `383` | `P9` | **Yes** |

The credit note and the debit note correct another invoice: `original_invoice_iic` is
the `iic` that invoice returned to you. It goes as BillingReference in the UBL document and as the correction
reference to the CIS, and without it the request is rejected with `422`. To reverse a full invoice,
use cancellation, not a credit note: it marks the original as reversed.

## Discount

A discount on the whole invoice is sent with `invoice_discount_type` and `invoice_discount_value`
together; a line discount with `lines[].discount`, as a percentage of the unit price, with
the full `price` and the `total` already reduced. Both can be on the same invoice, and the
full rules are in the request fields. In the UBL document the discount appears as
AllowanceCharge, with one entry per VAT rate.

## Examples

### Advance payment from a business

`doc_type` `386` is the prepayment invoice and `process` `P4` the advance-payment profile. Invoice the amount received, at the VAT rate of the service being prepaid.

```json
{
  "internalId": "DEP-2026-0032",
  "doc_type": "386",
  "process": "P4",
  "payment_method": "TRANSFER",
  "client": {
    "nuis": "L62221018T",
    "name": "Ei3 Software Solution shpk",
    "address": "Rruga e Kavajës 12",
    "city": "Tiranë",
    "country": "ALB"
  },
  "lines": [
    {
      "product_name": "Advance, reservation R-88214",
      "product_code": "DEP-REZ",
      "unit": "piece",
      "unit_code": "C62",
      "quantity": 1,
      "price": 30000,
      "total": 30000,
      "vat": 6
    }
  ]
}
```

### Credit note

The lines are only the difference, with positive values.

```json
{
  "internalId": "NK-2026-0038",
  "doc_type": "381",
  "process": "P9",
  "original_invoice_iic": "8FE72E2ACD1C500A83F8C89B4E2E3E1D",
  "payment_method": "TRANSFER",
  "client": {
    "nuis": "L62221018T",
    "name": "Ei3 Software Solution shpk",
    "address": "Rruga e Kavajës 12",
    "city": "Tiranë",
    "country": "ALB"
  },
  "lines": [
    {
      "product_name": "Discount for service interruption",
      "product_code": "SLL-01",
      "unit": "day",
      "unit_code": "DAY",
      "quantity": 1,
      "price": 2000,
      "total": 2000,
      "vat": 20
    }
  ]
}
```

## After issuing

The response carries `iic`, `fic`, `eic` and `pdf`. When the CIS cannot be reached, the invoice is saved and `fic`
is `null` until fiscalization completes on its own. When the e-invoicing platform cannot be reached
but the invoice was fiscalized, `eic` is missing until the submission is retried.

**Tags:** Invoices

**Request body content types:** application/json

**Responses:**

- `200` - When the invoice is rejected after validation, by the CIS or while saving, the response comes back with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field. Only a missing `internalId` comes back in the standard error format.
- `400` - Incomplete or invalid data for fiscalization. `errors` says exactly what needs to be filled in. The invoice was not saved; retry with the same `internalId`.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `409` - A request with this `internalId` is still in progress. Wait a moment and retry, or read the state with `POST /invoice/details/{internalId}`.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.
- `503` - The fiscalization service could not be reached and the invoice was not saved. Retry with the same `internalId`.

---

### POST /invoice/noncash

**Summary:** Issue a non-cash invoice

Issues an invoice with deferred payment: bank transfer, check, offset, or any payment
not made at the cash register. It is fiscalized with the CIS but does not go to the e-invoicing platform; when
the buyer must receive it electronically, use `POST /invoice/e-invoice`.

## The buyer

`client` is required: either `client.internal_id` of a client saved in fature.al,
or `client.name` with the other details; without either, the request is rejected with `422`. `client.address`
and `client.city` go to the CIS, so they are needed either in the request or saved earlier on the
client; when one is missing, the request is rejected with `400` and `errors` says exactly what needs to be
filled in.

## Bank account

The account where payment is expected is printed on the invoice in one of three ways:

| Field | Meaning |
| --- | --- |
| `bank_account` | The id of an account saved in fature.al |
| `bank_account_iban` | The account is looked up by IBAN |
| `bankAccount` | The full account is sent and saved as a company account; an existing IBAN is reused |

## Credit note and debit note

A non-cash invoice is corrected by another non-cash invoice. Send `doc_type` together with
`original_invoice_iic`, the `iic` the original invoice returned to you:

| `doc_type` | Document | Effect |
| --- | --- | --- |
| Missing, or `380` | Regular invoice | None |
| `381` | Credit note | Lowers the amount the buyer owes you |
| `383` | Debit note | Raises the amount the buyer owes you |

The original must be a fiscalized non-cash invoice, not canceled, and still correctable.
The lines you send are the correction lines, with positive values: the direction is set by
`doc_type`, not by the sign of the numbers. When the corrections cover the whole invoice, the original
is marked as reversed. To reverse a full invoice, use cancellation, not a credit
note.

## Discount and fee

A discount on the whole invoice is sent with `invoice_discount_type` and `invoice_discount_value`
together; a line discount with `lines[].discount`, as a percentage of the unit price, with
the full `price` and the `total` already reduced. The full rules are in the request fields.
The `fee` stays outside the discount: the discount applies to the line total, and the fee is added to the reduced
total.

## Examples

### Credit note

The lines are only the difference, with positive values.

```json
{
  "internalId": "NK-2026-0037",
  "doc_type": "381",
  "original_invoice_iic": "8FE72E2ACD1C500A83F8C89B4E2E3E1D",
  "payment_method": "TRANSFER",
  "client": {
    "name": "Alpha SHPK"
  },
  "lines": [
    {
      "product_name": "Discount, one unused night",
      "product_code": "DHM-2",
      "unit": "night",
      "quantity": 1,
      "price": 9000,
      "total": 9000,
      "vat": 6
    }
  ]
}
```

### Debit note

```json
{
  "internalId": "ND-2026-0012",
  "doc_type": "383",
  "original_invoice_iic": "8FE72E2ACD1C500A83F8C89B4E2E3E1D",
  "payment_method": "TRANSFER",
  "client": {
    "name": "Alpha SHPK"
  },
  "lines": [
    {
      "product_name": "Minibar, not invoiced",
      "product_code": "MNB-01",
      "unit": "piece",
      "quantity": 4,
      "price": 500,
      "total": 2000,
      "vat": 20
    }
  ]
}
```

### Advance payment by transfer

An advance payment has no type of its own at the CIS: it is invoiced as a sale, for the amount received. The API does not link it to the final invoice and does not deduct it from it.

```json
{
  "internalId": "DEP-2026-0033",
  "payment_method": "TRANSFER",
  "client": {
    "name": "Arben Hoxha"
  },
  "notes": "Advance for reservation R-88215",
  "lines": [
    {
      "product_name": "Advance, reservation R-88215",
      "product_code": "DEP-REZ",
      "unit": "piece",
      "quantity": 1,
      "price": 20000,
      "total": 20000,
      "vat": 6
    }
  ]
}
```

## After issuing

The response carries `iic`, `fic` and `pdf`. When the CIS cannot be reached, the invoice is saved and `fic` is
`null` until fiscalization completes on its own.

**Tags:** Invoices

**Request body content types:** application/json

**Responses:**

- `200` - When the invoice is rejected after validation, by the CIS or while saving, the response comes back with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field. Only a missing `internalId` comes back in the standard error format.
- `400` - Incomplete or invalid data for fiscalization. `errors` says exactly what needs to be filled in. The invoice was not saved; retry with the same `internalId`.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `409` - A request with this `internalId` is still in progress. Wait a moment and retry, or read the state with `POST /invoice/details/{internalId}`.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.
- `503` - The fiscalization service could not be reached and the invoice was not saved. Retry with the same `internalId`.

---

### POST /invoice/order

**Summary:** Issue an order invoice

Records consumption that has started without settling payment, like a restaurant table. It works
in tandem with the summary invoice: the order opens the table, the summary pays it. It is issued from the
user's TCR, like a cash invoice.

- Do not send `payment_method`: `ORDER` is set automatically.
- Keep the `iic` you get back: the order is closed with it in `POST /invoice/summary`.
- A table can have several open orders at once.
- A discount on the whole invoice is not accepted here; it goes on the summary invoice, which
  copies the lines from the orders. A line discount, `lines[].discount`, is stored on the order
  line and carries over to the summary invoice, so the table pays exactly what the order showed.

**Tags:** Invoices

**Request body content types:** application/json

**Responses:**

- `200` - When the user has no fiscal device, or when the order is rejected after validation by the CIS or while saving, the response comes back with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field. Only a missing `internalId` comes back in the standard error format.
- `400` - Incomplete or invalid data for fiscalization. `errors` says exactly what needs to be filled in. The invoice was not saved; retry with the same `internalId`.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `409` - A request with this `internalId` is still in progress. Wait a moment and retry, or read the state with `POST /invoice/details/{internalId}`.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.
- `503` - The fiscalization service could not be reached and the invoice was not saved. Retry with the same `internalId`.

---

### GET /invoice/print-eic/{eic}

**Summary:** Download the e-invoice PDF by EIC

Returns the official e-invoice PDF from the e-invoicing platform, by the EIC code that issuing
returned to you.

Check `Content-Type` before saving it as a PDF: an EIC that is not found returns the text
`Document not found.` with status `200` and `text/html`, not a PDF.

**Tags:** Invoices

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `eic` | path | yes | The e-invoice EIC code, a UUID. |

**Responses:**

- `200` - The e-invoice PDF. Check `Content-Type` before saving it: an EIC that is not found is still returned with status 200, but as `text/html`.
- `401`

---

### GET /invoice/print/{id}

**Summary:** Download the invoice document

Returns the invoice document to give to the buyer or to print. Without parameters, the default
format for the type is returned: a thermal receipt for cash invoices (58 or 80 mm, per the
user's configuration), an A4 document for non-cash invoices and estimates, and the official PDF
from the e-invoicing platform for e-invoices.

With the `format` parameter you get the same versions the fature.al dashboard offers:

| `format` | Invoice type | Result | Content-Type |
| --- | --- | --- | --- |
| `a4` | Cash | A4 invoice document | application/pdf |
| `thermal` | Non-cash | Thermal receipt | application/pdf |
| `v2` | Non-cash, estimate | A4 document, wide version | application/pdf |
| `receipt` | Estimate | Thermal receipt | application/pdf |
| `local` | E-invoice | A4 document generated by fature.al, without requesting the official PDF | application/pdf |
| `local-thermal` | E-invoice | Thermal receipt generated by fature.al | application/pdf |
| `html` | Cash | The thermal receipt as HTML, to send to the printer yourself | text/html |
| `json` | All | The document data as JSON, to format the invoice yourself | application/json |

`lang` (`en`, `it`, `de`) translates the A4 document of non-cash invoices, estimates and
e-invoices, and combines with `format`. Without it, the document is returned in Albanian. Thermal receipts and
cash invoices always stay in Albanian, because they are fiscal documents.

For cash invoices, `copy_only=1` returns the copy of the receipt instead of the original.

A `format` that does not apply to the invoice type does not return an error: the default format of
that type is returned.

**Tags:** Invoices

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The invoice's id in fature.al. |
| `copy_only` | query | no | Cash invoices only: returns the copy of the receipt instead of the original. |
| `format` | query | no | The document version per the table above: `a4`, `thermal`, `v2`, `receipt`, `local`, `local-thermal`, `html`, `json`. |
| `lang` | query | no | The language of the A4 document: `en`, `it` or `de`. Without it, the document is returned in Albanian. |

**Responses:**

- `200` - The content type depends on `format`: PDF by default, `text/html` with `format=html`, and the invoice document as JSON with `format=json`, ready for you to format yourself.
- `401`
- `404` - The invoice was not found, or does not belong to your company.

---

### POST /invoice/summary

**Summary:** Issue a summary invoice

Settles payment for one or more order invoices with a single document, issued from the
user's TCR.

- `order_invoices` holds the `iic` values of the orders being closed, at least one. They must be order
  invoices of this company, not yet closed; otherwise the request is rejected with `400` and
  `errors` says which one.
- Lines are not sent: they are taken from the orders themselves, with their discounts.
- `payment_method` accepts only `BANKNOTE` or `CARD`.
- The table discount is set here, with `invoice_discount_type` and `invoice_discount_value`.

The response lists the closed orders in `settledOrderInvoices`.

**Tags:** Invoices

**Request body content types:** application/json

**Responses:**

- `200` - When the user has no fiscal device, or when the invoice is rejected after validation by the CIS or while saving, the response comes back with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field. Only a missing `internalId` comes back in the standard error format.
- `400` - An order was not found, is not an order invoice, or is already closed; or the data is incomplete for fiscalization. `errors` says exactly which. The invoice was not saved; retry with the same `internalId`.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `409` - A request with this `internalId` is still in progress. Wait a moment and retry, or read the state with `POST /invoice/details/{internalId}`.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.
- `503` - The fiscalization service could not be reached and the invoice was not saved. Retry with the same `internalId`.

---

### GET /invoice/wtn

**Summary:** List warehouse transfer notes

Returns the company's warehouse transfer notes, newest first, filtered by creation
date. Each note comes complete, with `origin`, `destination`, `carrier`, `fiscal` and `lines`,
so the lines do not need a second call.

- Pagination with `limit` and `offset`. `pagination` does not return `total`, so stop when a page
  comes back shorter than `limit`.
- A second request from the same token, while the first has not finished, is rejected immediately with
  `429`. The slot is released as soon as the response returns, or after 30 seconds if the request was interrupted.

`fiscal.status` is `FISCALIZED` or `UNFISCALIZED`, and `print` is the address of the note's
print data.

**Tags:** Warehouse transfer notes

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `limit` | query | no | How many notes to return per page. |
| `offset` | query | no | Which note to start from, counting from 0. |
| `fromDate` | query | no | The start date, `YYYY-MM-DD`, by creation date. |
| `toDate` | query | no | End date, `YYYY-MM-DD`. |

**Responses:**

- `200` - The period's notes, with `pagination`; `items` is empty when there are no notes.
- `401`
- `422` - The dates are not in `YYYY-MM-DD` format.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### POST /invoice/wtn

**Summary:** Issue a warehouse transfer note

Issues a warehouse transfer note (WTN) and fiscalizes it with the CIS. The note is not a sale: it accompanies
goods moving from one point to another, and the law requires it even when nothing is sold.

Lines are sent as `invoice_lines`, not `lines` as on invoices, and each line has
`product_name`, `product_code`, `unit` and `quantity`.

## Required fields

- `vehPlates`, the vehicle's license plate;
- `valueOfGoods`, the value of the goods in transit;
- `invoice_lines`, at least one line.

## Allowed values

| Field | Values | Default |
| --- | --- | --- |
| `type` | `WTN`, `SALE` | `WTN` |
| `transaction` | `TRANSFER`, `EXAMINATION`, `SALES`, `DOOR` | `TRANSFER` |
| `vehOwnership` | `OWNER`, `THIRDPARTY` | `OWNER` |
| `startPoint`, `destinPoint` | `ANOTHER`, `CUSTOMS`, `EXHIBITION`, `OTHER`, `SALE`, `STORE`, `WAREHOUSE` | `WAREHOUSE`, `STORE` |
| `carrier_id_type` | `NUIS`, `ID` | `NUIS` |

The departure and arrival addresses (`startCity`, `startAddr`, `destinCity`, `destinAddr`) and
the carrier (`carrier_name`, `carrier_id_num`, `carrier_town`, `carrier_address`) are taken from
your business unit and company when not sent. `startDateTime` and `destinDateTime` take the issue
time when missing.

## After issuing

The response carries `id`, `number`, `iic`, `fic`, `fiscalStatus`, `verifyURL` and `print`. Store
the `id` and the `iic`. When the CIS cannot be reached, the note is saved unfiscalized, with
`fic: null` and `fiscalStatus: UNFISCALIZED`; fiscalization is done later from the fature.al
dashboard, and you read the state in `fiscal.status` with `GET /invoice/wtn/{id}/details`. When
the CIS rejects the note, it is not saved and the response is `500` with the reason in `message`.

A second request from the same token while the first is still running is rejected immediately with
`429`. The slot frees up as soon as the response is returned, or after 30 seconds if the request was interrupted.

**Tags:** Warehouse transfer notes

**Request body content types:** application/json

**Responses:**

- `200` - When the CIS cannot be reached, the note is saved unfiscalized: the response carries `invoice` with `fic: null` and `fiscalStatus: UNFISCALIZED`, and fiscalization happens later.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error, or the CIS rejected the note; the reason is in `message`. The note was not saved.

---

### GET /invoice/wtn/print/{id}

**Summary:** Get the warehouse transfer note for printing

Returns the note's data as document-ready JSON, not as a PDF: the company with its logo,
the carrier, departure and arrival, and in `invoice` the number, fiscal codes, lines,
`amounts` and `verifyUrl`.

Use it when you format the document yourself, in your own printing system. The address of this
endpoint comes in the `print` field of the list and of the details.

**Tags:** Warehouse transfer notes

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The id of the warehouse transfer note in fature.al. |

**Responses:**

- `200` - The note document as JSON, ready for you to format: the company with its logo, the carrier, departure, arrival and `invoice` with the lines and fiscal codes.
- `401`
- `404` - The note was not found, or it does not belong to your company.

---

### GET /invoice/wtn/{id}/details

**Summary:** Get a warehouse transfer note by id

Returns the full note by its `id` in fature.al: the vehicle, the departure and
arrival points, the carrier, the fiscal codes and the goods lines. It is the same object the
list returns.

**Tags:** Warehouse transfer notes

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The id of the warehouse transfer note in fature.al. |

**Responses:**

- `200` - The full note in `invoice`: departure and arrival points, the carrier, the fiscal codes and the goods lines.
- `401`
- `404` - The note was not found, or it does not belong to your company.
- `500` - Unexpected server error.

---

### GET /invoice/{id}/details

**Summary:** Get an invoice by id

Returns the full invoice by its `id` in fature.al: the buyer, the amounts, the payment, the fiscal
codes and the lines.

When the invoice is not found, or does not belong to your company, `data.invoice` is `null` with
status `200`. To find it by your own number, use `POST /invoice/details/{internalId}`.

**Tags:** Invoices

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The invoice's id in fature.al. |

**Responses:**

- `200` - The full invoice in `data.invoice`, or `null` when it is not found.
- `401`
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### POST /on-boarding/bank-account

**Summary:** Create a bank account

Saves a bank account of the company. The account is printed on non-cash invoices and on e-invoices
as the place where payment is expected, so it is needed before the first non-cash invoice.

- `iban` and `currency` (the currency code, e.g. `ALL`) are what define the account;
  `name`, `holder`, `swift` and `notes` are descriptive.
- Spaces in `iban` are removed and letters are uppercased before saving.
- An IBAN the company already has does not create a second account: the existing one is returned.
- An unknown currency is rejected with `200` and `status: false`.

Store the returned `id`: with it, or with the IBAN, the account is selected on non-cash invoices and on
e-invoices.

**Tags:** Company registration

**Request body content types:** application/json

**Responses:**

- `200` - When the currency is not recognized, or when saving fails, the response comes back with HTTP 200 and `status: false`; the reason is in `message` or in `errors`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.

---

### POST /on-boarding/branch

**Summary:** Create a business unit

Creates a new business unit for the company. Used when the company has more than one point of
sale: the first unit is created by `POST /register` itself, and for that one you only set the code with
`POST /on-boarding/branch/{id}`.

- `name`, `type`, `administrator` and `address` are required.
- `type` is `main` for the headquarters or `secondary` for a secondary unit. The company has
  a single headquarters, so `main` is rejected with `409` when one already exists.
- The number of units depends on the subscription; when the limit is reached, the request is rejected with `403`.
- `businessUnitCode` can be sent now or later with `POST /on-boarding/branch/{id}`, but
  without it the unit cannot issue invoices.

Store the returned `id`: the unit's users and fiscal devices are linked to it.

**Tags:** Company registration

**Request body content types:** application/json

**Responses:**

- `200` - When saving fails for another reason, the response comes back with HTTP 200 and `status: false`; the reason is in `message` or in `errors`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `409` - The company already has a headquarters.

---

### POST /on-boarding/branch/{id}

**Summary:** Update a business unit

Sets or changes the name, address, administrator and code of a business unit. The
usual step is `businessUnitCode`, the business unit code obtained from self-care: without it the unit cannot
issue invoices.

- `name`, `businessUnitCode`, `administrator` and `address` are all optional; a field
  that is not sent stays as it is.
- The body is not validated separately, so there is no `422` response.

A unit that is not found, or does not belong to your company, comes back with `200` and
`status: false`.

**Tags:** Company registration

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The id of the business unit. |

**Request body content types:** application/json

**Responses:**

- `200` - When the unit is not found, does not belong to your company, or saving fails, the response comes back with HTTP 200 and `status: false`; the reason is in `message` or in `errors`.
- `401`

---

### POST /on-boarding/certificate

**Summary:** Upload the electronic certificate

Uploads the company's electronic certificate (`.p12` or `.pfx`) together with its password.
Every document sent to the CIS is signed with it, so it is the first step after registration.

- `certificate_file` is the certificate file and `password` is its password.
- The password must open the certificate.
- The NIPT inside the certificate must match the company's NIPT.

The response returns `expiresAt`, the certificate's expiry date. Store it: after that date
fiscalization stops until a new certificate is uploaded.

In sandbox any certificate and any password is accepted, and `expiresAt` is one year from today.

**Tags:** Company registration

**Request body content types:** multipart/form-data

**Responses:**

- `200` - When the password is wrong, when the certificate's NIPT does not match the company, or when saving fails, the response comes back with HTTP 200 and `status: false`; the reason is in `message` or in `errors`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.

---

### POST /on-boarding/fiscal-device

**Summary:** Register a fiscal device

Registers a fiscal device (TCR) with the CIS for a business unit and returns its code.
Needed only for cash invoices; non-cash invoices and e-invoices have no TCR.

- Done after the certificate has been uploaded and the unit has a `businessUnitCode`; without them the CIS
  rejects the registration.
- `branchId`, `name` and `fromDate` (`YYYY-MM-DD`) are required. `toDate` closes
  the device on that date; without it the device stays active indefinitely.
- When the unit has reached the number of active devices it is allowed, the request is rejected with `403`.

Store `fiscalTcrCode` and set it on the user who will issue cash invoices, with
`POST /on-boarding/user/{id}`.

When the CIS rejects the registration, the device is not saved and the response comes back with `200` and
`status: false`. When the CIS cannot be reached, the device is also not saved, but the response comes back
with `status: true` and `fiscalTcrCode: null`: check the value, not only `status`, and
try again later.

**Tags:** Company registration

**Request body content types:** application/json

**Responses:**

- `200` - When the CIS rejects the registration, or when saving fails, the response comes back with HTTP 200 and `status: false`; the reason is in `message` or in `errors`. When the CIS cannot be reached, `status` is `true` and `fiscalTcrCode` is `null`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `403` - The subscription has expired, or the action is not allowed for this account.

---

### POST /on-boarding/user

**Summary:** Create a user

Creates an operator user for the company and returns its token. Every person who issues
invoices has their own user, because the operator code and the fiscal device belong to the user.

- `email` is required and unique across all of fature.al, not only within the company.
- `branchId` must be a unit of your company.
- `operatorCode` is obtained from self-care. For cash invoices also send `fiscalTcrCode`, the code
  returned by `POST /on-boarding/fiscal-device`; a code that is not found leaves the user without a device,
  without an error.
- `name` and `phone` are optional.
- The body is not validated separately, so every rejection comes with `200` and `status: false`.

Store the `token`: it is returned only once and it is the one this user calls the API with.

**Tags:** Company registration

**Request body content types:** application/json

**Responses:**

- `200` - When `email` is missing or in use, when the unit is not found, or when saving fails, the response comes back with HTTP 200 and `status: false`; the reason is in `message` or in `errors`.
- `401`

---

### POST /on-boarding/user/{id}

**Summary:** Update a user

Sets the name, unit, operator code and fiscal device of a user. This is where the
first user, the one created by `POST /register`, is linked to `operatorCode` and, for cash invoices, to
`fiscalTcrCode`.

- `operatorCode` is the operator code obtained from self-care; without it the user cannot issue
  invoices.
- `fiscalTcrCode` is the code returned by `POST /on-boarding/fiscal-device` and must be sent on
  every call: without it, or with a code not found among the company's devices, the user
  is left without a fiscal device, without an error.
- `branchId` must be a unit of your company; otherwise the request is rejected with `200` and
  `status: false`.
- `name`, `branchId` and `operatorCode` that are not sent stay as they are.
- The body is not validated separately, so there is no `422` response.

**Tags:** Company registration

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The id of the user. |

**Request body content types:** application/json

**Responses:**

- `200` - When the user or the unit is not found, or saving fails, the response comes back with HTTP 200 and `status: false`; the reason is in `message` or in `errors`.
- `401`

---

### GET /ping

**Summary:** Test the connection

Returns the server time and the IP addresses the request arrived from. Reads and changes
nothing in the account.

Use it to test the base URL and the token before the first real call, and to
see which IP you arrive from when you configure an IP allowlist. To see which
business the token works with, call `GET /account`.

- `pong` is a Unix timestamp in seconds.
- `ip` is the list of the request's IP addresses, as the server sees them.

A `401` means the token is not getting through: check the `Authorization` header.

**Tags:** Ping

**Responses:**

- `200` - The server time in `pong` and the request's IP addresses in `ip`.
- `401`

---

### GET /product/categories

**Summary:** List product categories

Returns all of the company's product categories in a single response, without pagination,
each with `id`, `name` and `color`.

Use it to get the `id` you send as `id_category` when you filter `GET /products` or
when you create a product. The list rarely changes, so cache it.

This endpoint returns neither `429` nor `500`. A second request from the same token, while the first
has not finished, as well as any unexpected error, are returned with HTTP 200 and `status: false`; the reason
is in `message`. Read `status`, not just the HTTP code.

**Tags:** Products

**Responses:**

- `200` - The company's categories in `data`, empty when there are none; or `status: false` with the reason in `message`.
- `401`

---

### GET /products

**Summary:** List products

Returns the company's products and services that are for sale, by ascending `id`, filtered
by text, category and kind. Deleted products and those not for sale are left out.

Use it to build a till or a catalog, or to find a product's `id`.
Each product comes with its code, name, unit, prices, VAT rate and `id_category`; for
the category object and for `service`, call `GET /products/{id}/details`.

- `query` searches by partial match on name, code and description.
- `id_category` keeps only the products of one category; `service` `true` only services,
  `false` only inventory items.
- Pagination with `limit` and `offset`. `limit` is capped at `500`; a larger value is lowered without
  an error. Without `limit` the whole catalog is returned, `pagination.limit` is `null` and `offset`
  is ignored.
- `pagination` returns `total`, so continue with `offset += limit` until `offset` reaches
  `total`.

Unlike the other lists, `data` is the product list itself and `pagination` sits
next to it, not inside it.

This endpoint returns neither `429` nor `500`. A second request from the same token, while the first
has not finished, as well as any unexpected error, are returned with HTTP 200 and `status: false`; the reason
is in `message`. Read `status`, not just the HTTP code.

**Tags:** Products

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `query` | query | no | Search in the product's name, code or description. |
| `id_category` | query | no | Only the products of this category, by `id` from `GET /product/categories`. |
| `limit` | query | no | How many products to return per page, up to 500. Without it the whole catalog is returned. |
| `offset` | query | no | Which product to start from, counting from 0. Applies only together with `limit`. |
| `service` | query | no | `true` returns only services, `false` only inventory items. Without it, both are returned. |

**Responses:**

- `200` - The page's products in `data`, with `pagination` next to it; or `status: false` with the reason in `message`.
- `401`

---

### POST /products

**Summary:** Create a product

Creates a new product or service in the company's catalog and returns its `id`.

Use it when you keep the catalog in your own system and want the product to appear in fature.al as well.

- `name` is unique within the company; a second product with the same name is rejected with
  `409`.
- `code` is generated automatically when you do not send it; store the one returned to you.
- `unit` is not sent: the unit is derived from `unit_code`, the code the fiscal document carries.
- `vat_rate` accepts `0`, `6`, `10` or `20`; for an exempt product also send
  `vat_exempt_type`.
- A second request from the same token, while the first has not finished, is rejected immediately with
  `429`.

The product appears immediately in `GET /products`. Store the returned `id` for details and
updates.

**Tags:** Products

**Request body content types:** application/json

**Responses:**

- `200` - The newly created product in `product`, with its `id` and code.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `409` - A product with this name already exists.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### PUT /products/{id}

**Summary:** Update a product

Updates an existing product and returns its data after the change.

The request body has the same format and the same required fields as `POST /products`,
with the same rules for `unit_code` and `vat_rate`.

- The new name or code cannot match another product's; it is rejected with `409`.
- A price change does not affect issued invoices: they keep the price they were sold at.
- A second request from the same token, while the first has not finished, is rejected immediately with
  `429`.

**Tags:** Products

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The product's id in fature.al. |

**Request body content types:** application/json

**Responses:**

- `200` - The product after the change, in `product`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `403` - The subscription has expired, or the action is not allowed for this account.
- `404` - The product was not found, or does not belong to your company.
- `409` - Another product with this name or this code already exists.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### GET /products/{id}/details

**Summary:** Get a product by id

Returns a single product with all fields: code, name, unit with its fiscal code,
prices, VAT rate with the exemption type, `service` and the category as an object.

Use it when you have the `id` from the list or from creation and need the fields the list does not
carry.

**Tags:** Products

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `id` | path | yes | The product's id in fature.al. |

**Responses:**

- `200` - The requested product in `product`, with all fields.
- `401`
- `404` - The product was not found, or does not belong to your company.
- `500` - Unexpected server error.

---

### GET /purchase-invoices

**Summary:** List purchase invoices

Returns the invoices that suppliers have issued to your company, read from the CIS. The supplier
does not send you anything: the documents are fetched from the central fiscalization system, each with the
full data, the seller, the buyer, the items, VAT and payment methods.

Use it to reconcile purchases and deductible VAT without any manual step.

- Pagination uses `page`, starting at `1`, not `limit` and `offset`, because the list comes from
  the CIS and not from fature.al. Repeat with `page + 1` until `items` comes back empty.
- Without `fromDate` and `toDate` only today is read, not the whole history.
- `fic` narrows the list to a single invoice.
- `pagination.records` is the CIS count for the page. A document whose details cannot be
  reached is dropped from `items` without an error, so `items` can be shorter than `records`.
- The details of each invoice are cached for one hour.

Store `iic` and `fic`: they let you find the invoice again, and `fic` also serves as a filter here.

**Tags:** Purchase invoices

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `page` | query | no | The page of the list from the CIS, starting at 1. Repeat with `page + 1` until `items` comes back empty. |
| `fromDate` | query | no | Start date, `YYYY-MM-DD`. Defaults to today. |
| `toDate` | query | no | End date, `YYYY-MM-DD`. Defaults to today. |
| `fic` | query | no | Return only the invoice with this FIC. |

**Responses:**

- `200` - The page's purchase invoices in `items`, each one complete, with `pagination`; `items` is empty when the page has no invoices.
- `401`
- `422` - The dates are not in `YYYY-MM-DD` format.
- `500` - Unexpected server error.
- `502` - The CIS responded with an error; the reason is in `message`.
- `503` - The fiscalization service could not be reached. Try again later.

---

### GET /register

**Summary:** Check registration access

Returns `pong` when the token's account is allowed to register companies. Call it before the
first step, to know whether the group is open to you.

The group is closed by default and is opened on request, either for the token of a single
operator or for all operators of your company.

**Tags:** Company registration

**Responses:**

- `200` - When your account is not authorized for registration, the response comes back with HTTP 200 and `status: false`; the reason is in `message`.
- `401`

---

### POST /register

**Summary:** Register a company

Creates a new company in fature.al, with the first user, the first business unit,
the walk-in customer, the currencies and an active subscription. It is the first step of registration; the other
steps are called with the token it returns.

Use it only for businesses that are not yet on fature.al. For an existing company
use the `POST /on-boarding/*` endpoints directly with its token.

- The token's account must be authorized for registration; otherwise the request is rejected
  with `200` and `status: false`.
- `nuis` must be a NIPT not yet registered on fature.al and known to the tax
  administration. Lowercase letters are accepted and converted to uppercase.
- `email` becomes the email and username of the first user, so it must be unused on
  fature.al.
- `lastNonCashEInvoiceNumber` sets the number from which the numbering of non-cash invoices
  and e-invoices continues for the current year.

The response carries `user.token`, the API token of the first user, and `branch.id`, the id of the
business unit. Store both: the token is shown only here and is used for all the other
steps, while the business unit id is needed for `POST /on-boarding/branch/{id}`,
`POST /on-boarding/user` and `POST /on-boarding/fiscal-device`.

## Sandbox

In sandbox, the NIPT `L62221018T` goes through a test flow: no new company is created, only
a user and a business unit under the sandbox company, and `user.token` is a real token
for the other steps. The suffix `+sandbox-XXXXXXXX` is added to the email, so you can send the
same email as many times as you like. In live this NIPT is treated like any other NIPT.

**Tags:** Company registration

**Request body content types:** application/json

**Responses:**

- `200` - When the account is not authorized for registration, when the NIPT is already registered or is not found at the tax administration, or when the email is in use, the response comes back with HTTP 200 and `status: false`; the reason is in `message`.
- `401`
- `422` - The data failed validation. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.

---

### GET /reports/by-operator

**Summary:** Get sales by operator

How many invoices each operator issued and how much value they generated in the period, with `operator_code` next to the
name.

Use it to compare shifts or for sales-based bonuses.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |

**Responses:**

- `200` - The period's operators in `items`, highest gross value first; `items` is empty when the period has no sales.
- `401`
- `422` - `from` or `to` is missing, or is not in the `YYYY-MM-DD` format. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /reports/by-tcr

**Summary:** Get sales by fiscal device

How many invoices each fiscal device (TCR) issued and how much value it generated in the period.

A business with several units uses it to split sales by point of sale, because each TCR
belongs to a single unit.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |

**Responses:**

- `200` - The period's fiscal devices in `items`, highest gross value first; `items` is empty when the period has no cash invoices.
- `401`
- `422` - `from` or `to` is missing, or is not in the `YYYY-MM-DD` format. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /reports/cash-register

**Summary:** Get the cash register balance for a day

The balance of the user's cash register for a single day: the opening, the closing and the expected
closing, together with sales by payment method and money movements.

The expected closing, `expected_closing`, is what the register should hold if everything was
recorded. The difference from the actual closing is exactly what reconciliation looks for.

- Without `date`, today is used. The format is `YYYY-MM-DD`.
- The report belongs to the fiscal device of the token's user. Without a fiscal device, or when
  the ALL currency is not configured, the cash register is not available and the response comes back with
  `200` and `status: false`.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `date` | query | no | The report day, `YYYY-MM-DD`. Defaults to today. |

**Responses:**

- `200` - The day's cash register balance in `data`, with `sales_by_method` and `movements`; without a fiscal device, or when the ALL currency is not configured, the response comes back with HTTP 200 and `status: false`, with the reason in `errors`.
- `401`
- `422` - `date` is not in the `YYYY-MM-DD` format. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### GET /reports/cash-register/closing

**Summary:** Get the cash register closing report

The end-of-day report, the one printed when the shift closes: sales totals, products
sold and money movements, with the shift's `opened_at` and `closed_at`.

The difference from the cash register balance is the focus: this report belongs to the shift that closed, not to the day as a
period.

- Without `date`, today is used. The format is `YYYY-MM-DD`.
- The report belongs to the fiscal device of the token's user. Without a fiscal device, or when
  the ALL currency is not configured, the cash register is not available and the response comes back with
  `200` and `status: false`.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `date` | query | no | The report day, `YYYY-MM-DD`. Defaults to today. |

**Responses:**

- `200` - The shift closing report in `data`, with `sales`, `sales_by_method`, `products_sold` and `movements`; without a fiscal device, or when the ALL currency is not configured, the response comes back with HTTP 200 and `status: false`, with the reason in `errors`.
- `401`
- `422` - `date` is not in the `YYYY-MM-DD` format. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.
- `500` - Unexpected server error.

---

### GET /reports/daily-trend

**Summary:** Get the daily trend

One point for each day of the period, ready for a chart.

| `metric` | What the point measures |
| --- | --- |
| `gross` | Gross sales value, the default |
| `count` | Number of invoices |

Days without sales are returned with the value `0` and are not dropped from the series, so the chart is not
distorted. Each point carries `date`, `count` and `gross`, and `metric` is echoed back.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |
| `metric` | query | no | What the series measures: `gross` the gross value, `count` the number of invoices. Defaults to `gross`. |

**Responses:**

- `200` - One point for each day of the period in `series`, in chronological order, with `0` for days without sales; `metric` is echoed back.
- `401`
- `422` - `from` or `to` is missing or is not in the `YYYY-MM-DD` format, or `metric` is neither `gross` nor `count`. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /reports/reversals

**Summary:** List canceled invoices

The invoices canceled in the period, each with `original_invoice_id`, the original invoice it
corrects, and `reversed_at`. `totals` holds their count and value.

- `limit` accepts `1` to `500`, with `100` as the default.
- Use it to see how much and why invoices are canceled: a high number of cancellations is usually
  a sign of a problem in the sales process.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |
| `limit` | query | no | How many rows to return, from 1 to 500. |

**Responses:**

- `200` - The period's canceled invoices in `items`, most recent cancellation first, up to `limit`, with `totals` over all cancellations; `items` is empty when there are no cancellations.
- `401`
- `422` - `from` or `to` is missing or is not in the `YYYY-MM-DD` format, or `limit` is outside `1` to `500`. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /reports/sales-summary

**Summary:** Get the sales summary

Sales totals for a period: the number of invoices, gross, net, VAT and the average invoice
value, broken down at once by payment method, invoice type, VAT rate
and day.

Use it for periodic reconciliation or for a sales dashboard. For a single cash register day
use `GET /reports/cash-register`.

The totals include only invoices that are not canceled. `cancelled` holds the count and value of the invoices
canceled in the period, so you do not need a second call to check them.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |

**Responses:**

- `200` - The period's sales summary in `data`: `totals`, the breakdowns by payment method, invoice type, VAT rate and day, and `cancelled`; the breakdowns are empty when the period has no sales.
- `401`
- `422` - `from` or `to` is missing, or is not in the `YYYY-MM-DD` format. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /reports/top-clients

**Summary:** List the top clients

The period's clients ranked by invoiced value, with the number of invoices and the gross amount for
each.

- `limit` accepts `1` to `500`, with `20` as the default.
- The walk-in customer, that is invoices without a named buyer, is not included in the ranking.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |
| `limit` | query | no | How many rows to return, from 1 to 500. |

**Responses:**

- `200` - The period's clients in `items`, highest gross value first, up to `limit`; `items` is empty when the period has no sales.
- `401`
- `422` - `from` or `to` is missing or is not in the `YYYY-MM-DD` format, or `limit` is outside `1` to `500`. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /reports/top-products

**Summary:** List the best-selling products

The period's products ranked by revenue or by quantity sold, with `quantity`
and `revenue` for each.

| Parameter | Values | Default |
| --- | --- | --- |
| `sort` | `revenue`, `quantity` | `revenue` |
| `limit` | `1` to `200` | `20` |

`sort` and `limit` are echoed back together with `period`.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |
| `limit` | query | no | How many products to return, from 1 to 200. |
| `sort` | query | no | The ordering: `revenue` by revenue, `quantity` by quantity. Defaults to `revenue`. |

**Responses:**

- `200` - The period's products in `items`, best-selling first according to `sort`, up to `limit`; `items` is empty when the period has no sales.
- `401`
- `422` - `from` or `to` is missing or is not in the `YYYY-MM-DD` format, or `limit` and `sort` are outside the allowed values. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /reports/vat

**Summary:** Get the VAT report

The VAT collected on sales against the VAT paid on purchases for the period, with the resulting
difference in `summary.vat_due`.

The rows are split into two groups, both for sales and for purchases in `purchases`:

- `taxable`, by rate: `0`, `6`, `10`, `20`;
- `exempt`, by exemption type.

Purchases come from the purchase invoices registered by the CIS, so the report covers both
sides without asking you to import anything.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |

**Responses:**

- `200` - The VAT report in `data`: sales in `totals`, `taxable` and `exempt`, purchases in `purchases`, and the difference in `summary.vat_due`.
- `401`
- `422` - `from` or `to` is missing, or is not in the `YYYY-MM-DD` format. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

### GET /reports/wtn

**Summary:** Get the warehouse transfer notes report

The period's warehouse transfer notes (WTN), grouped by fiscal status in `by_status`, together
with the destinations used and the value of the goods moved.

An unfiscalized note here needs attention: the goods moved without the document the law
requires.

**Tags:** Reports

**Parameters:**

| Name | In | Required | Description |
|------|----|----------|-------------|
| `from` | query | yes | Start date, `YYYY-MM-DD`, inclusive. Required. |
| `to` | query | yes | End date, `YYYY-MM-DD`, inclusive. Required. When it is before `from`, the two are swapped. |

**Responses:**

- `200` - The period's warehouse transfer notes in `data`: `totals`, the breakdown by fiscal status in `by_status` and the destination cities in `destinations`; `by_status` and `destinations` are empty when the period has no notes.
- `401`
- `422` - `from` or `to` is missing, or is not in the `YYYY-MM-DD` format. This response uses the `success` field, not `status`, and `errors` is an object keyed by field.
- `429` - Rate limit reached. From the global limit, the response carries only `message` and the `Retry-After` header; from the endpoint's limit, it has the standard error format.

---

## Full OpenAPI specification

For complete schemas, examples and request/response bodies, fetch the JSON spec:

```
https://fature.al/docs/api/en.json
```
