# Conventions, errors and rate limits

> Request and response formats, pagination, identifiers, dates and money, HTTP status codes, and the 60 requests per minute limit.

## Requests

- Base URL `https://app.fsaskaita.lt/api`.
- Headers on every request: `Authorization: Bearer <token>` and `Accept: application/json`.
- Invoice endpoints take a JSON body (`Content-Type: application/json`). Expense endpoints take `multipart/form-data` because a file travels with them.
- `PUT` and `PATCH` are equivalent on this API. Both replace the whole resource with the body you send, so always send every field.

## Responses

A single resource is wrapped in `data`:

```json
{ "data": { "id": "…", "…": "…" } }
```

A list is wrapped in `data` with a `meta` block:

```json
{
  "data": [ … ],
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 3,
    "path": "https://app.fsaskaita.lt/api/invoices",
    "per_page": 50,
    "to": 50,
    "total": 132
  }
}
```

Deletion returns `200` with an empty body.

## Pagination

Lists return 50 items per page, newest first, and accept only `?page=N`. There are no filters, sort parameters or page-size options. To sync, walk the pages until `current_page` equals `last_page`. `GET /currencies` is not paginated.

## Identifiers, dates, money

| Kind | Format | Example |
|---|---|---|
| Identifiers | UUID string | `"9c1f7b2e-3a6d-4d5e-9f0a-2b7c8d9e0f11"` |
| Dates in requests and responses | `YYYY-MM-DD` | `"2026-09-08"` |
| `created_at`, `updated_at` | Unix timestamp, seconds, integer | `1757318400` |
| Money | JSON number | `60.5` |
| Currency | uppercase ISO 4217 code accepted by `GET /currencies` | `"EUR"` |
| Invoice numbers | string, zero-padded to three digits in responses | `"007"` |

Enumerated values are lowercase snake_case strings, for example `vat_invoice` or `not_paid`. The reference lists the allowed values for every field.

## Errors

| Status | Meaning | Body |
|---|---|---|
| `401` | Missing, invalid or deleted token | `{"message":"Unauthenticated."}` |
| `404` | Unknown id, or the resource belongs to another business | `{"message":"…"}` |
| `422` | Validation failed | see below |
| `429` | Rate limit exceeded | `{"message":"Too Many Attempts."}` plus `Retry-After` |
| `500` | Unexpected server error | `{"message":"Server Error"}` |

Validation errors list every failing field. Nested fields use dot paths. Messages are in Lithuanian.

```json
{
  "message": "Laukas type yra privalomas. (and 2 more errors)",
  "errors": {
    "type": ["Laukas type yra privalomas."],
    "buyer.company_name": ["…"],
    "products.0.quantity": ["…"]
  }
}
```

There is no `403`. A token either works for its business or is rejected with `401`.

## Rate limits

Each business may make 60 requests per minute across all of its tokens. Every response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining`. When the limit is hit, the response is `429` with `Retry-After` in seconds and `X-RateLimit-Reset` as a Unix timestamp. Back off until then; retrying earlier only returns more `429`s.

## Idempotency and retries

The API has no idempotency key. A `POST` that timed out on the network may still have created the resource. Before retrying, list the newest items and check whether yours is there. `PUT` and `DELETE` are safe to retry.

## Localisation

Validation messages and the type label inside PDF file names are in Lithuanian unless the invoice `language` says otherwise. There is no `Accept-Language` negotiation.
