---
name: nexus-api
description: >-
  Complete zero-thinking integration manual for the NEXUS REST API
  (https://api.arcadi.nexus) — a public, read-only API over ~4 million Romanian
  legal entities (firme/companies, ONG/NGOs, schools), 11.7M financial
  statements (bilanțuri), administrators/representatives, fiscal & VAT status,
  CAEN activity codes, plus change webhooks. READ THIS to integrate NEXUS into
  ANY app or platform: validating a Romanian CUI/CIF before invoicing, looking
  up a company, searching firms by any field, pulling multi-year financials,
  resolving a person's companies, or subscribing to change notifications.
  Auto-load for any task mentioning: NEXUS API, api.arcadi.nexus, Romanian
  company/firm lookup, CUI/CIF validation, bilanț / cifră de afaceri data,
  ONRC/ANAF/MFP company data via API, nxs_live_ keys, X-API-Key, or "verify a
  Romanian company". The integrating instance needs exactly ONE thing from its
  user to start: the nxs_live_ API key.
---

# NEXUS API — Integration Manual

> **Zero-thinking integration manual.** A Claude instance that reads this file gains the *total* ability to integrate the NEXUS API into any application, in any stack, asking its user for only **one** thing: the `nxs_live_…` API key. Everything else — base URL, auth, every endpoint, every parameter, every response shape, error handling, rate limits, pagination, webhooks, CUI validation, ready-to-paste code — is prescribed below. Prefer the explicit values written here over your defaults. When in doubt, the contract in this file is authoritative.

---

## 0. TL;DR (read this first, act in 60 seconds)

1. **Ask the user for one thing:** their NEXUS API key, format `nxs_live_…`. They generate it at `https://arcadi.nexus/api` → tab **"Chei API"** → *Continue with ARKNET*. Nothing else is needed.
2. **Store it server-side only** as an env var (e.g. `NEXUS_API_KEY`). Never ship it to a browser/client.
3. **Base URL:** `https://api.arcadi.nexus`. All data endpoints live under `/v1`. Always HTTPS, always JSON.
4. **Auth:** send header `X-API-Key: nxs_live_…` on every `/v1/*` request. (`/healthz` needs no key.)
5. **First call** (validate a Romanian company by its fiscal code — the #1 use case):
   ```bash
   curl "https://api.arcadi.nexus/v1/firme/12345678/validate" -H "X-API-Key: $NEXUS_API_KEY"
   ```
6. **Read responses** from the `data` field. Errors come in `{"eroare": {...}}`. Field names are **Romanian** (domain language) — that's intentional, not a bug.

That's the whole loop: get key → put it in `X-API-Key` → call `/v1/...` → read `data`.

---

## 1. What NEXUS is (mental model)

NEXUS is a database of **every legal entity in Romania**, exposed as a clean read-only REST API. Think of it as a programmatic ONRC + ANAF + MFP (the Romanian trade registry + tax authority + finance ministry), unified and queryable.

What's inside (live magnitudes, approximate):

| Resource | Count | What it is |
|---|---:|---|
| **Firme** (companies) | ~3.97M | Every Romanian company, keyed by **CUI** (fiscal code). ~1.98M active, ~1.99M struck-off (`radiata`). |
| **Bilanțuri** (financial statements) | ~11.7M | Annual financials 2009–2024 (revenue, profit, employees, balance sheet). |
| **Persoane** (people) | ~2.17M | Administrators / representatives / shareholders. A person links to all the firms they run. |
| **firma_persoane** | ~3.6M | The firm↔person links (who runs what, in what role). |
| **ONG** (NGOs) | ~95k | Non-profit organizations + their own financial statements. |
| **Unități de învățământ** (schools) | ~18k | Public/private education units (MEN registry), linked to their legal-person CUI. |
| **CAEN secundare** | ~18M | All authorized activity codes per firm (Rev. 2 + Rev. 3). |
| **perioade_tva** | ~934k | VAT registration history. |

**Read-only.** The API never mutates company data. The only writes that happen are internal bookkeeping (usage logs, your webhook subscriptions).

**Data freshness & sources:** ONRC (trade registry, monthly) → identity, registration code, address, legal form. ANAF (tax authority, weekly rolling) → phone/fax/IBAN, VAT status, inactive status, e-Factura, fiscal office. MFP → annual financial statements. The API surfaces the merged result; you don't need to know the pipeline.

---

## 2. The ONE thing you must get from the user: the API key

ARKNET/NEXUS issues API keys to the user's own apps. The key looks like:

```
nxs_live_AbCdEf012345-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

**How the user obtains it (tell them this if they don't have one):**
1. Go to `https://arcadi.nexus/api`.
2. Open the **"Chei API"** tab.
3. Click **"Continue with ARKNET"** and authenticate (their `differentiator + 6-digit code`, e.g. `jd123456`).
4. Name the key (e.g. `contabilitate-prod`), pick a rate limit, click generate.
5. **The raw key is shown exactly once.** They must copy it immediately.

You (the integrating instance) **never mint keys** — you only consume one the user pastes in. (There is a server-to-server admin mint endpoint, but it's gated by a separate admin token held only by the key-gen portal; it is out of scope for integrators — see §16.)

### The non-negotiable security rule

```
╔══════════════════════════════════════════════════════════════════════════╗
║  The nxs_live_… key is a SERVER-SIDE secret. NEVER expose it to a browser. ║
║                                                                            ║
║  ❌ NOT in any frontend bundle (.js/.ts/.jsx/.vue/.svelte under src/)       ║
║  ❌ NOT in env vars prefixed VITE_ / NEXT_PUBLIC_ / PUBLIC_                 ║
║  ❌ NOT in client fetch() calls, NOT in mobile app binaries                ║
║  ✅ Only in server env (process.env / platform secret store)               ║
║  ✅ Call NEXUS from YOUR backend; your frontend calls YOUR backend         ║
╚══════════════════════════════════════════════════════════════════════════╝
```

If your app is purely client-side, add a thin server proxy (see §14). Before shipping, grep your build output for `nxs_live_` and confirm zero matches.

---

## 3. Conventions that apply to EVERY endpoint

### 3.1 Base URL & versioning
- Base: `https://api.arcadi.nexus`
- All data endpoints are under **`/v1`**. The version is in the path; a future `/v2` would coexist.
- `/healthz` (no version, no auth) is the only public endpoint.

### 3.2 Authentication
- Header: **`X-API-Key: nxs_live_…`** on every `/v1/*` request.
- Missing/malformed key → **401** `NEAUTORIZAT`.
- Key not recognized → **401**.
- Key revoked → **403** `INTERZIS`.
- The key is hashed (sha256) server-side; the server never stores it in clear and never echoes it back.

### 3.3 Success envelope
**Single resource:**
```json
{
  "data": { /* the object */ },
  "meta": { "versiune_api": "v1", "generat_la": "2026-05-26T10:00:00.000000+00:00" }
}
```
**Collection (lists / search):**
```json
{
  "data": [ /* array of items */ ],
  "paginare": { "limit": 20, "offset": 0, "are_pagina_urmatoare": true, "cursor_urmator": null, "total": null },
  "meta": { "versiune_api": "v1", "generat_la": "..." }
}
```
Some endpoints add extra `meta` keys (e.g. search adds `sort`/`order`; batch adds `solicitate`/`gasite`; top-firme adds `an`/`sort`/`nota`). Always read your payload from **`data`**.

### 3.4 Error envelope (uniform, no stack traces ever)
```json
{
  "eroare": {
    "cod": "PARAMETRU_INVALID",
    "mesaj": "an_financiar este obligatoriu când filtrezi/sortezi după indicatori financiari.",
    "detalii": [ { "camp": "an_financiar", "problema": "..." } ],
    "status": 422,
    "request_id": "01J..."
  }
}
```
- `cod` — a **stable machine code** (switch on this, not on the message text).
- `mesaj` — human-readable Romanian message (safe to surface to users).
- `detalii` — optional array with per-field problems (validation), else `null`.
- `request_id` — echo this when reporting a problem; it's also returned as the `X-Request-Id` response header.

**Stable error codes:**

| `cod` | HTTP | Meaning | What you do |
|---|---|---|---|
| `NEAUTORIZAT` | 401 | Key missing/invalid/malformed | Fix the `X-API-Key` header. Don't retry blindly. |
| `INTERZIS` | 403 | Key revoked | Get a new key from the user. |
| `NEGASIT` | 404 | Resource doesn't exist | Treat as "not found" (note: `/validate` returns 200 with `exista:false` instead). |
| `PARAMETRU_INVALID` | 400 / 422 | Bad/missing parameters | Read `detalii`, fix the request. |
| `PREA_MULTE_CERERI` | 429 | Rate limit exceeded | Back off; honor `Retry-After`. |
| `EROARE_INTERNA` | 500 | Server error | Retry with backoff; if it persists, report `request_id`. |
| `INDISPONIBIL` | 503 | Temporarily unavailable | Retry with backoff. |

### 3.5 HTTP status quick map
`200` ok · `400` malformed · `401` no/invalid key · `403` revoked key · `404` not found · `422` validation · `429` rate limit · `500` internal · `503` unavailable.

### 3.6 Rate limits
- Per-key, per-minute limit (token bucket). Default 120 rpm; configurable per key at mint time (1–6000).
- Every authenticated response carries:
  - `X-RateLimit-Limit` — your per-minute ceiling
  - `X-RateLimit-Remaining` — tokens left this window
  - `X-RateLimit-Reset` — seconds until a token frees up
- On exceed → **429** with `Retry-After: <seconds>` (and `X-RateLimit-Remaining: 0`). **Always honor `Retry-After`.**
- A larger **daily quota** is also associated with each key (returned by `GET /v1`); treat it as a soft cap and don't hammer.

### 3.7 Pagination
- List endpoints accept `limit` (1–100, default 20) and `offset` (0–10000).
- The response `paginare` block has **`are_pagina_urmatoare`** (boolean) — the reliable "is there more?" signal.
- `total` is currently `null` (the API does not compute exact totals on huge tables for speed) and `cursor_urmator` is `null` (offset-based). **Paginate by incrementing `offset` by `limit` until `are_pagina_urmatoare` is `false`.**
- `offset` is hard-capped at **10000**. For deeper result sets, **narrow your filters** instead of paging further.

### 3.8 Response headers you'll see
`X-Request-Id` (also `request_id` in errors), `X-RateLimit-*` (on authed calls), `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`. `/v1/*` responses are `Cache-Control: no-store` **except** nomenclatoare (reference data), which are `public, max-age=86400` (cache them 24h).

### 3.9 Language & types
- **Field names are Romanian.** A short glossary is in §15.
- Money/financial values are JSON **numbers** (RON). They may be `null` when not filed.
- Dates are ISO `YYYY-MM-DD`. Timestamps are ISO 8601 with timezone.
- Booleans for fiscal flags may be `true` / `false` / `null` (null = unknown/not enriched yet).

---

## 4. CUI validation (do this BEFORE you call — it's free and instant)

A Romanian **CUI** (a.k.a. CIF — fiscal code) has a built-in checksum. Validate locally to reject typos before spending an API call. (The API also validates, but client-side pre-checks are good manners and faster UX.)

**Algorithm:** control key `753217532`. Take the number's digits except the last (the check digit). Reverse them. Multiply position-wise by weights `[2,3,5,7,1,2,3,5,7]`. Sum, ×10, mod 11; if the result is 10, it becomes 0. Valid iff that equals the last digit. Length 2–10 digits, value > 0. A leading `RO` (VAT prefix) is not part of the number — strip it.

**Python**
```python
def is_valid_cui(cui) -> bool:
    s = str(cui).upper().removeprefix("RO").strip()
    if not s.isdigit() or not (2 <= len(s) <= 10) or int(s) <= 0:
        return False
    weights = [2, 3, 5, 7, 1, 2, 3, 5, 7]
    rev = [int(d) for d in s[:-1]][::-1]
    control = (sum(d * w for d, w in zip(rev, weights)) * 10) % 11
    control = 0 if control == 10 else control
    return control == int(s[-1])
```

**JavaScript / TypeScript**
```js
function isValidCui(cui) {
  const s = String(cui).toUpperCase().replace(/^RO/, "").trim();
  if (!/^\d{2,10}$/.test(s) || Number(s) <= 0) return false;
  const weights = [2, 3, 5, 7, 1, 2, 3, 5, 7];
  const body = s.slice(0, -1).split("").reverse().map(Number);
  let sum = 0;
  for (let i = 0; i < body.length; i++) sum += body[i] * weights[i];
  let control = (sum * 10) % 11;
  if (control === 10) control = 0;
  return control === Number(s.slice(-1));
}
```

**PHP**
```php
function is_valid_cui($cui): bool {
  $s = preg_replace('/^RO/i', '', trim((string)$cui));
  if (!preg_match('/^\d{2,10}$/', $s) || (int)$s <= 0) return false;
  $weights = [2, 3, 5, 7, 1, 2, 3, 5, 7];
  $body = array_reverse(str_split(substr($s, 0, -1)));
  $sum = 0;
  foreach ($body as $i => $d) $sum += (int)$d * $weights[$i];
  $control = ($sum * 10) % 11;
  if ($control === 10) $control = 0;
  return $control === (int)substr($s, -1);
}
```

> All example CUIs in this document (`12345678`, `87654321`) are **placeholders for illustration only** — they are not guaranteed to pass the checksum or to exist. Use real CUIs at call time.

---

## 5. Endpoint reference — META

### `GET /healthz` — liveness (no auth)
Health + DB connectivity. Use it for monitoring / smoke tests.
```bash
curl "https://api.arcadi.nexus/healthz"
```
```json
{ "status": "ok", "db": "ok", "versiune_api": "v1" }
```
`status` is `"ok"` or `"degraded"` (DB down).

### `GET /v1` — API index + your key's limits (auth)
Confirms your key works and reports its limits. Great for a one-time "is my integration wired correctly?" check.
```bash
curl "https://api.arcadi.nexus/v1" -H "X-API-Key: $NEXUS_API_KEY"
```
```json
{
  "data": {
    "api": "NEXUS Public API",
    "versiune": "v1",
    "documentatie": "/docs",
    "cheie": { "prefix": "nxs_live_AbCdEf01", "tier": "internal", "rate_limit_per_min": 300, "daily_quota": 100000 }
  },
  "meta": { "versiune_api": "v1", "generat_la": "..." }
}
```

> Interactive references are also live: **Swagger UI** at `https://api.arcadi.nexus/docs`, **ReDoc** at `/redoc`, raw spec at `/openapi.json`. They're auto-generated and always current. This manual is the curated, example-rich companion.

---

## 6. Endpoint reference — FIRME (companies)

All under `/v1/firme`, all require `X-API-Key`.

### 6.1 `GET /v1/firme/{cui}` — full company profile
The workhorse. Returns a composed profile. Control which sections come back with **`include`**.

**Path params:** `cui` (integer, required).

**Query params:**
| Param | Type | Notes |
|---|---|---|
| `include` | csv string | Sections to include. Default: `financiar_highlight,administratori,caen`. |
| `an_bilant` | integer | Pin a specific year for `financiar_highlight` (otherwise latest available). |

**Valid `include` tokens:** `financiar_highlight`, `administratori`, `administratori_curenti`, `caen`, `bilanturi`, `tva`, `sucursale`, `modificari`, or `all`.
- `administratori` = current + historical; `administratori_curenti` = only current (if you pass only this token).
- Unknown tokens → 422 listing the allowed set.

```bash
curl "https://api.arcadi.nexus/v1/firme/12345678?include=financiar_highlight,administratori,caen,bilanturi" \
  -H "X-API-Key: $NEXUS_API_KEY"
```

**Response (`data` shape — fictional):**
```json
{
  "data": {
    "cui": 12345678,
    "denumire": "ACME PROD S.R.L.",
    "cod_inmatriculare": "J12/100/2015",
    "euid": "ROONRC.J12/100/2015",
    "data_inmatriculare": "2015-03-12",
    "is_radiata": false,
    "forma_juridica": { "cod": "SRL", "denumire": "Societate cu răspundere limitată" },
    "stare_curenta": { "cod": 8, "denumire": "FUNCTIONARE", "data": "2015-03-12" },
    "caen_principal": { "cod": "6201", "versiune": 2, "denumire": "Activități de realizare a soft-ului la comandă (orientat client)" },
    "adresa": {
      "judet": "Cluj", "judet_cod": "12", "localitate": "Cluj-Napoca", "sector": null,
      "strada": "Str. Exemplu", "numar": "10", "bloc": null, "scara": null, "etaj": null,
      "apartament": null, "cod_postal": "400001",
      "adresa_text": "Cluj-Napoca, Str. Exemplu nr. 10, jud. Cluj",
      "lat": 46.7712, "lng": 23.6236, "geo_precision": "street"
    },
    "contact": { "telefon": "+40 264 000 000", "mobil": null, "fax": null, "email": "contact@exemplu.test", "website": "www.exemplu.test", "iban": null },
    "fiscal": {
      "status_tva": true, "status_tva_incasare": false, "status_split_tva": false,
      "status_inactiv": false, "status_e_factura": true, "data_inreg_e_factura": "2022-07-01",
      "forma_proprietate": "Proprietate privată", "forma_organizare": "Societate comercială",
      "organ_fiscal_competent": "Administrația Județeană a Finanțelor Publice Exemplu"
    },
    "capital_social": null,
    "categorie_marime": "mica",
    "obiect_activitate_text": null,
    "administrator": "Ion Exemplu",

    "financiar_highlight": { "an": 2024, "cifra_afaceri_neta": 4250000.0, "profit_net": 510000.0, "pierdere_neta": null, "numar_mediu_salariati": 18 },
    "administratori": [
      { "persoana_id": "00000000-0000-0000-0000-000000000001", "nume": "Ion Exemplu", "tip": "PF", "cui_pj": null,
        "calitate": "administrator", "cota_procent": null, "aport": null, "data_inceput": "2015-03-12", "data_sfarsit": null, "is_curent": true }
    ],
    "caen_secundare": [ { "cod_caen": "6202", "versiune_caen": 2, "denumire": "Activități de consultanță în tehnologia informației" } ]
  },
  "meta": { "versiune_api": "v1", "generat_la": "..." }
}
```
When `include` also requests `bilanturi`/`tva`/`sucursale`/`modificari`, those arrive as `bilanturi`, `perioade_tva`, `sucursale_externe`, `modificari` (same shapes as the dedicated sub-resources below).

**404** if the CUI doesn't exist.

> Note: `capital_social`, and `cota_procent`/`aport` on administrators, are **not populated** (Romania doesn't publish them in free bulk sources) — expect `null`. See §13.

### 6.2 `GET /v1/firme/{cui}/validate` — fast pre-invoice check ⭐
The single most useful endpoint for accounting/invoicing software. Tells you, instantly, whether a fiscal code is valid, whether the company exists, and whether it's safe to invoice.

**Returns 200 even when the company doesn't exist** (`exista: false`) — so you don't have to treat "not found" as an error.

```bash
curl "https://api.arcadi.nexus/v1/firme/12345678/validate" -H "X-API-Key: $NEXUS_API_KEY"
```
```json
{
  "data": {
    "cui": 12345678,
    "format_valid": true,
    "exista": true,
    "denumire": "ACME PROD S.R.L.",
    "is_radiata": false,
    "status_inactiv": false,
    "status_tva": true,
    "status_e_factura": true,
    "apta_facturare": true
  },
  "meta": { "versiune_api": "v1" }
}
```
- `format_valid` — passed the CUI checksum.
- `exista` — found in the database.
- `apta_facturare` — convenience boolean = `not is_radiata and not status_inactiv` (a quick "OK to invoice this partner" flag).
- If the checksum fails: `{ "cui": ..., "format_valid": false, "exista": false, "denumire": null, "apta_facturare": false }`.
- If valid format but not found: `{ "format_valid": true, "exista": false, "denumire": null, "apta_facturare": false }`.

### 6.3 `POST /v1/firme/batch` — resolve many CUIs in one call ⭐
Validate/resolve up to **100** CUIs in a single request (no N+1). Each entry comes back with a `status`.

**Body:**
```json
{ "cui": [12345678, 87654321, 1], "include": ["financiar_highlight"] }
```
- `cui` — array of integers, 1..100. Over 100 → 422.
- `include` — optional; only `financiar_highlight` is supported here (attaches latest revenue/profit/employees per found firm).

```bash
curl -X POST "https://api.arcadi.nexus/v1/firme/batch" \
  -H "X-API-Key: $NEXUS_API_KEY" -H "Content-Type: application/json" \
  -d '{"cui":[12345678,87654321,1],"include":["financiar_highlight"]}'
```
```json
{
  "data": [
    { "cui": 12345678, "status": "gasit", "firma": {
        "cui": 12345678, "denumire": "ACME PROD S.R.L.", "cod_inmatriculare": "J12/100/2015",
        "forma_juridica": "SRL", "judet": "Cluj", "localitate": "Cluj-Napoca",
        "cod_caen_principal": "6201", "is_radiata": false, "status_inactiv": false,
        "status_tva": true, "status_e_factura": true,
        "financiar_highlight": { "an": 2024, "cifra_afaceri_neta": 4250000.0, "profit_net": 510000.0, "numar_mediu_salariati": 18 } } },
    { "cui": 87654321, "status": "negasit", "firma": null },
    { "cui": 1, "status": "format_invalid", "firma": null }
  ],
  "meta": { "solicitate": 3, "gasite": 1, "versiune_api": "v1" }
}
```
`status` ∈ `gasit` | `negasit` | `format_invalid`. (A `GET /v1/firme/batch` returns a 405 hint pointing you to POST.)

### 6.4 `GET /v1/firme` — advanced search (filter by ANY principal field) ⭐
Search and filter the whole company base. Combine any of the filters below.

**Query params:**
| Param | Type | Notes |
|---|---|---|
| `q` | string (min 2) | Name search — Romanian full-text + fuzzy trigram. |
| `administrator` | string (min 3) | Administrator name (fuzzy). |
| `cui` | integer | Exact CUI. |
| `cui_in` | csv of integers | Up to 100 CUIs (`12345678,87654321`). |
| `cod_inmatriculare` | string | Exact registration code (`J12/100/2015`). |
| `judet` | string | County (e.g. `Cluj`). |
| `localitate` | string | Locality — **requires `judet`** too (else 422). |
| `cod_caen` | string | CAEN code (e.g. `6201`). |
| `caen_mod` | `principal` \| `orice` | Match principal CAEN only (default) or also secondary. |
| `forma_juridica` | string | Legal form code (`SRL`, `SA`, `PFA`, …). |
| `status_tva` | boolean | VAT payer. |
| `status_inactiv` | boolean | Fiscally inactive. |
| `status_e_factura` | boolean | Registered for e-Factura. |
| `is_radiata` | boolean | Struck off. |
| `inmatriculare_de_la` / `inmatriculare_pana_la` | date | Registration date range (`YYYY-MM-DD`). |
| `cifra_min` / `cifra_max` | number | Revenue range — **requires `an_financiar`**. |
| `profit_min` / `profit_max` | number | Net profit range — **requires `an_financiar`**. |
| `salariati_min` / `salariati_max` | integer | Employees range — **requires `an_financiar`**. |
| `an_financiar` | integer (2009–2024) | **Mandatory** whenever you filter or sort by a financial metric (the financials are year-partitioned). |
| `sort` | enum | `relevanta` \| `denumire` \| `data_inmatriculare` \| `cui` \| `cifra` \| `profit` \| `salariati`. Default `relevanta` if `q` present, else `denumire`. |
| `order` | `asc` \| `desc` | Default `asc`. |
| `limit` | integer (1–100) | Default 20. |
| `offset` | integer (0–10000) | Default 0. |
| `include` | string | Pass `financiar_highlight` to attach latest financials to each result. |

**Validation rules to respect (else 422):**
- Any financial filter (`cifra_*`/`profit_*`/`salariati_*`) **or** sort in `{cifra,profit,salariati}` ⟹ `an_financiar` is required.
- `sort=relevanta` requires `q`.
- `localitate` requires `judet`.
- `cui_in` ≤ 100 values, numeric.

```bash
# Software companies in Cluj, sorted by 2024 revenue, top 20
curl -G "https://api.arcadi.nexus/v1/firme" -H "X-API-Key: $NEXUS_API_KEY" \
  --data-urlencode "judet=Cluj" \
  --data-urlencode "cod_caen=6201" \
  --data-urlencode "an_financiar=2024" \
  --data-urlencode "sort=cifra" --data-urlencode "order=desc" \
  --data-urlencode "limit=20"
```
```json
{
  "data": [
    { "cui": 12345678, "denumire": "ACME PROD S.R.L.", "cod_inmatriculare": "J12/100/2015",
      "forma_juridica": "SRL", "judet": "Cluj", "localitate": "Cluj-Napoca",
      "cod_caen_principal": "6201", "is_radiata": false, "status_tva": true, "status_inactiv": false, "status_e_factura": true }
  ],
  "paginare": { "limit": 20, "offset": 0, "are_pagina_urmatoare": true, "cursor_urmator": null, "total": null },
  "meta": { "sort": "cifra", "order": "desc", "versiune_api": "v1" }
}
```

### 6.5 Sub-resources of a firm
All take `cui` in the path; all return `{ "data": ..., "meta": ... }`.

**`GET /v1/firme/{cui}/administratori`** — current + historical people.
Query: `doar_curenti` (boolean) to restrict to current.
```json
{ "data": [ { "persoana_id": "00000000-0000-0000-0000-000000000001", "nume": "Ion Exemplu", "tip": "PF",
  "cui_pj": null, "calitate": "administrator", "cota_procent": null, "aport": null,
  "data_inceput": "2015-03-12", "data_sfarsit": null, "is_curent": true } ], "meta": {"versiune_api":"v1"} }
```
`tip`: `PF` (natural person) or `PJ` (a company acting as e.g. liquidator) — when `PJ`, `cui_pj` points to that company's CUI.

**`GET /v1/firme/{cui}/bilanturi`** — multi-year financials.
Query: `de_la` / `pana_la` (year bounds). Ordered newest-first.
```json
{ "data": [ { "an": 2024, "cifra_afaceri_neta": 4250000.0, "venituri_totale": 4300000.0,
  "cheltuieli_totale": 3700000.0, "profit_brut": 600000.0, "pierdere_bruta": null,
  "profit_net": 510000.0, "pierdere_neta": null, "numar_mediu_salariati": 18,
  "active_imobilizate": 800000.0, "active_circulante": 1200000.0, "stocuri": 150000.0,
  "creante": 900000.0, "casa_si_conturi_banci": 300000.0, "datorii_totale": 900000.0,
  "capitaluri_total": 1500000.0, "capital_subscris_varsat": 200.0,
  "cod_caen_an": "6201", "versiune_caen_an": 2 } ], "meta": {"versiune_api":"v1"} }
```

**`GET /v1/firme/{cui}/caen`** — principal + secondary activity codes.
```json
{ "data": { "principal": { "cod": "6201", "versiune": 2, "denumire": "Activități de realizare a soft-ului la comandă" },
  "secundare": [ { "cod_caen": "6202", "versiune_caen": 2, "denumire": "Activități de consultanță în tehnologia informației" } ] },
  "meta": {"versiune_api":"v1"} }
```
(404 if the firm doesn't exist.)

**`GET /v1/firme/{cui}/tva`** — VAT registration history.
```json
{ "data": [ { "data_inceput": "2016-01-01", "data_sfarsit": null, "data_anulare": null, "motiv_anulare": null, "is_curent": true } ], "meta": {"versiune_api":"v1"} }
```

**`GET /v1/firme/{cui}/sucursale`** — branches in other EU member states.
```json
{ "data": [ { "tara_membra": "Germania", "denumire_locala": "ACME GmbH", "identificator": "DE000000000" } ], "meta": {"versiune_api":"v1"} }
```

**`GET /v1/firme/{cui}/modificari`** — change history (audit trail).
Query: `limit` (1–200, default 50). Changes detected by diffing successive ONRC/ANAF snapshots.
```json
{ "data": [ { "field_name": "telefon", "old_value": "+40 264 000 000", "new_value": "+40 264 111 111",
  "changed_at": "2026-04-10T08:00:00+00:00", "data_source": "anaf/v9" } ], "meta": {"versiune_api":"v1"} }
```

---

## 7. Endpoint reference — PERSOANE (people)

People = administrators / representatives / shareholders. A person can run many firms; this is how you build "who controls what" graphs.

### `GET /v1/persoane?nume=…` — fuzzy name search
**Query:** `nume` (string, **min 3**, required), `tip` (`PF`|`PJ`), `judet_curent` (string), `limit` (1–100), `offset` (0–10000).
```bash
curl -G "https://api.arcadi.nexus/v1/persoane" -H "X-API-Key: $NEXUS_API_KEY" \
  --data-urlencode "nume=exemplu" --data-urlencode "tip=PF" --data-urlencode "limit=20"
```
```json
{ "data": [ { "id": "00000000-0000-0000-0000-000000000001", "nume": "Ion Exemplu", "tip": "PF",
  "data_nasterii": null, "judet_curent": "Cluj", "localitate_curenta": "Cluj-Napoca", "cui_pj": null } ],
  "paginare": { "limit": 20, "offset": 0, "are_pagina_urmatoare": false, "total": null }, "meta": {"versiune_api":"v1"} }
```

### `GET /v1/persoane/{id}` — person detail + firms they run
`id` is a UUID (from search results or from a firm's `administratori[].persoana_id`).
**Query:** `limit_firme` (1–200, default 50).
```json
{ "data": {
    "id": "00000000-0000-0000-0000-000000000001", "nume": "Ion Exemplu", "tip": "PF",
    "data_nasterii": null, "localitate_nasterii": null, "judet_nasterii": null,
    "localitate_curenta": "Cluj-Napoca", "judet_curent": "Cluj", "cui_pj": null,
    "rezumat": { "firme_total": 3, "firme_curente": 2 },
    "firme": [ { "cui": 12345678, "denumire": "ACME PROD S.R.L.", "judet": "Cluj", "is_radiata": false,
      "cod_caen_principal": "6201", "calitate": "administrator", "cota_procent": null, "aport": null,
      "data_inceput": "2015-03-12", "data_sfarsit": null, "is_curent": true } ]
  },
  "meta": { "firme_pagina_urmatoare": false, "versiune_api": "v1" } }
```
If the person is itself a company (`tip: "PJ"`), a `firma_asociata: { cui, link }` is added pointing to its company profile.

### `GET /v1/persoane/{id}/firme` — paginated list of a person's firms
**Query:** `limit` (1–200), `offset` (0–10000). Returns a collection with `paginare`.

---

## 8. Endpoint reference — ONG (NGOs)

NGOs have their own table and their own (different) financial statements.

- **`GET /v1/ong?q=…`** — search. Params: `q` (min 2), `judet`, `localitate` (requires `judet`), `status_tva`, `is_radiata`, `limit`, `offset`.
- **`GET /v1/ong/{cui}`** — detail (identity, address, fiscal, two CAEN codes: `cod_caen_economic` + `cod_caen_fara_scop`, each with a `*_den` label). If the same CUI also exists as a company, a `si_in_firme: { cui, link }` is added.
- **`GET /v1/ong/{cui}/bilanturi`** — NGO financial statements (an, cifra_afaceri_neta, venituri_totale, cheltuieli_totale, profit_net, pierdere_neta, numar_mediu_salariati, cod_caen_an).

```bash
curl -G "https://api.arcadi.nexus/v1/ong" -H "X-API-Key: $NEXUS_API_KEY" \
  --data-urlencode "q=exemplu" --data-urlencode "judet=Iași"
```
```json
{ "data": [ { "cui": 12345678, "denumire": "ASOCIAȚIA EXEMPLU", "cod_inmatriculare": "12/A/2018",
  "forma_juridica": "AS", "judet": "Iași", "localitate": "Iași", "cod_caen_economic": "9499",
  "is_radiata": false, "status_tva": false, "status_inactiv": false } ],
  "paginare": { "limit": 20, "offset": 0, "are_pagina_urmatoare": false, "total": null }, "meta": {"versiune_api":"v1"} }
```

---

## 9. Endpoint reference — UNITĂȚI DE ÎNVĂȚĂMÂNT (schools)

- **`GET /v1/unitati-invatamant?q=…`** — search. Params: `q` (min 2), `judet`, `localitate` (requires `judet`), `tip` (e.g. `Liceu`), `is_publica` (boolean), `limit`, `offset`.
- **`GET /v1/unitati-invatamant/{cod_siiir}`** — detail by SIIIR code. If `cod_fiscal_pj` is set, a `firma_asociata: { cui, link }` links to the legal-person company.

```json
{ "data": { "cod_siiir": "0000000001", "denumire_lunga": "LICEUL TEORETIC EXEMPLU", "tip": "Liceu",
  "tip_simplificat": "Liceu", "judet": "Brașov", "localitate": "Brașov", "is_publica": true,
  "is_pj": true, "cod_fiscal_pj": 12345678, "an_scolar": "2024-2025", "lat": 45.66, "lng": 25.61,
  "firma_asociata": { "cui": 12345678, "link": "/v1/firme/12345678" } }, "meta": {"versiune_api":"v1"} }
```

---

## 10. Endpoint reference — NOMENCLATOARE (reference data)

Small, slow-moving lookup tables. **Cacheable 24h** (`Cache-Control: public, max-age=86400`). Cache them in your app and resolve codes locally.

| Endpoint | Returns |
|---|---|
| `GET /v1/nomenclatoare/caen?versiune=&q=&limit=` | CAEN codes. `versiune` (2 or 3), `q` (search code/name, min 2), `limit` (1–500). Items: `{cod, versiune, denumire, cod_echivalent_alta_ver}`. |
| `GET /v1/nomenclatoare/caen/{cod}?versiune=` | One CAEN code (latest revision if `versiune` omitted). 404 if unknown. |
| `GET /v1/nomenclatoare/judete` | Counties: `{cod_auto, cod_statistic, denumire, regiune}` (e.g. `CJ`, `12`, `Cluj`, `Nord-Vest`). |
| `GET /v1/nomenclatoare/forme-juridice` | Legal forms: `{cod, denumire_completa, tip_persoana}` (e.g. `SRL`). |
| `GET /v1/nomenclatoare/stari` | Firm states: `{cod, denumire}` (e.g. `8` → `FUNCTIONARE`). |
| `GET /v1/nomenclatoare/calitati` | Person roles: `{cod, denumire, categorie}`. |

> **CAEN Rev. 2 vs Rev. 3:** Romania has two coexisting CAEN nomenclatures. Older firms may carry Rev. 2 codes, newer ones Rev. 3. The API returns `versiune` (2 or 3) alongside each code, and `cod_echivalent_alta_ver` maps to the equivalent in the other revision. Don't assume a single revision.

---

## 11. Endpoint reference — STATISTICI (aggregations)

Materialized-view-backed, so they're fast. Lightly cached server-side.

- **`GET /v1/stats`** — global counters.
  ```json
  { "data": { "firme_total": 3973230, "firme_active": 1988862, "firme_radiate": 1996311,
    "platitori_tva": 480114, "inactivi": 454635, "e_factura": 264735, "persoane": 2165643,
    "ong": 95635, "unitati_invatamant": 18022, "bilanturi": 11716925 }, "meta": {"versiune_api":"v1"} }
  ```
- **`GET /v1/stats/judete`** — per-county breakdown: `[{judet, total, active, radiate, plat_tva, inactivi, e_factura}]`.
- **`GET /v1/stats/caen/{cod}`** — per-CAEN counters + 2024 financial summary: `{cod_caen, denumire, total, active, plat_tva, firme_count, cifra_total, cifra_medie, profit_mediu, salariati_mediu, firme_profitabile, firme_in_pierdere}`. 404 if no summary.
- **`GET /v1/top-firme?an=&cod_caen=&judet=&sort=&limit=`** — national top ranking (≤5000/year precomputed). `an` (2009–2024, default 2024), `sort` (`cifra`|`profit`|`salariati`, default `cifra`), `limit` (1–100). Items: `{rank_global, cui, denumire, judet, cod_caen_principal, cifra_afaceri_neta, profit_net, numar_mediu_salariati}`.

---

## 12. Endpoint reference — WEBHOOKS (change notifications) ⭐

Subscribe to a target URL and NEXUS will POST you a **signed** notification whenever a watched company changes (name, address, fiscal status, administrators, etc., as detected by diffing ONRC/ANAF snapshots). This beats polling.

### 12.1 Manage subscriptions (scoped to your API key)

**`POST /v1/webhooks`** — create a subscription. The `secret` is returned **once**; store it to verify signatures.
```bash
curl -X POST "https://api.arcadi.nexus/v1/webhooks" \
  -H "X-API-Key: $NEXUS_API_KEY" -H "Content-Type: application/json" \
  -d '{"target_url":"https://app-ul-tau.example/webhooks/nexus","events":["company.changed"],"filter_cuis":[12345678],"filter_fields":["status_tva","denumire"]}'
```
Body fields:
- `target_url` (required) — must start with `http://` or `https://` (use HTTPS).
- `events` — default `["company.changed"]`.
- `filter_cuis` — only these CUIs (omit/null = all).
- `filter_fields` — only when these fields change (omit/null = any field).

Response includes `secret: "whsec_…"` (shown once) + the subscription record.

- **`GET /v1/webhooks`** — list your subscriptions.
- **`GET /v1/webhooks/{id}`** — one subscription (404 if not yours/unknown).
- **`GET /v1/webhooks/{id}/livrari?limit=`** — delivery history: `[{id, event, status, attempts, response_code, created_at, last_attempt_at, next_attempt_at}]`. `status` ∈ `pending|delivered|failed|dead`.
- **`DELETE /v1/webhooks/{id}`** — deactivate. Returns `{id, dezactivat: true}` (404 if unknown/already off).

### 12.2 Receiving & verifying a delivery (CRITICAL)

NEXUS POSTs JSON to your `target_url` with these headers:
- `X-Nexus-Signature: sha256=<hex>` — HMAC-SHA256 over `"{timestamp}.{raw_body}"` using your webhook `secret`.
- `X-Nexus-Timestamp` — the timestamp used in the signed string.
- `X-Nexus-Delivery` — a unique delivery id (use it for **idempotency** / dedupe; deliveries are at-least-once).

**Always verify the signature over the EXACT raw request body bytes (before any JSON parsing), then reject timestamps older than ~5 minutes (replay protection), then parse defensively.**

The JSON body represents the change event. Treat it defensively — at minimum it carries the event name and the change detail (the CUI, which field changed, old→new value, when, and the source). Representative shape:
```json
{
  "event": "company.changed",
  "cui": 12345678,
  "modificari": [
    { "field_name": "status_tva", "old_value": "false", "new_value": "true",
      "changed_at": "2026-05-26T09:55:00+00:00", "data_source": "anaf/v9" }
  ]
}
```
> The header contract (signature/timestamp/delivery) is firm. The body's exact key names should be parsed defensively — read what's present, key your logic off `cui` + the change records, and verify the HMAC over the raw bytes regardless of body shape.

**Retry policy:** on a non-2xx (or no) response, NEXUS retries with exponential backoff (~1m, 5m, 15m, 1h, 3h, 6h, 12h) and marks the delivery **`dead`** after 8 failed attempts. Respond `2xx` quickly (do heavy work async) to avoid retries.

**Verification — Python (FastAPI/Flask)**
```python
import hmac, hashlib, time

def verify_nexus(secret: str, timestamp: str, raw_body: bytes, signature_header: str) -> bool:
    if abs(time.time() - int(timestamp)) > 300:        # reject > 5 min old (replay)
        return False
    expected = hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(f"sha256={expected}", signature_header)
# headers: X-Nexus-Timestamp, X-Nexus-Signature, X-Nexus-Delivery (dedupe on the last)
```

**Verification — Node (Express)**
```js
import crypto from "node:crypto";
// IMPORTANT: capture the RAW body, e.g. express.raw({ type: "application/json" })
function verifyNexus(secret, timestamp, rawBody, signatureHeader) {
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const h = crypto.createHmac("sha256", secret);
  h.update(`${timestamp}.`); h.update(rawBody);            // rawBody is a Buffer
  const expected = `sha256=${h.digest("hex")}`;
  const a = Buffer.from(expected), b = Buffer.from(signatureHeader || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

**Verification — PHP**
```php
function verify_nexus($secret, $timestamp, $rawBody, $signatureHeader): bool {
  if (abs(time() - (int)$timestamp) > 300) return false;
  $expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $rawBody, $secret);
  return hash_equals($expected, (string)$signatureHeader);
}
```

---

## 13. Data coverage & known gaps (so you never promise data that isn't there)

| Available ✅ | Not populated ❌ (don't expect it) |
|---|---|
| Identity: denumire, CUI, cod_inmatriculare (J/F), euid, data_inmatriculare, forma_juridica | **Capital social** (`capital_social` is `null`) — not in free bulk sources |
| Structured + free-text address, geocoded lat/lng | **Asociați cote / aport** (`cota_procent`, `aport` are `null`) |
| Contact: telefon, fax, email, website, IBAN (when declared to ANAF) | **Beneficiari reali (UBO)** — not exposed |
| Fiscal: status_tva (+incasare/split), status_inactiv, status_e_factura, organ fiscal | **Bilanțuri pre-2009** — free data starts 2009 |
| CAEN principal + all secondary (Rev. 2 & 3) | Detailed mandate dates / censors / auditors |
| Administrators/representatives (current + historical), per-role | Internal branches (puncte de lucru) — only EU branches are listed |
| Bilanțuri 2009–2024 (revenue, profit, employees, balance sheet) | |
| VAT periods history; change history (`modificari`) | |
| ONG + NGO financials; schools (MEN) | |

Other truths to internalize:
- **Two CAEN revisions coexist** (see §10). Always read `versiune`.
- Struck-off companies are **kept** (`is_radiata: true`), never deleted — historical lookups work.
- Fiscal booleans can be `null` (= not yet enriched / unknown), distinct from `false`.
- `total` in pagination is intentionally `null`; rely on `are_pagina_urmatoare`.

---

## 14. "What do I need?" → endpoint map + recipes

### 14.1 Need → endpoint
| You want… | Call |
|---|---|
| Is this CUI real & safe to invoice? | `GET /v1/firme/{cui}/validate` |
| Check a list of partners at once | `POST /v1/firme/batch` |
| Full company profile | `GET /v1/firme/{cui}?include=all` (or pick sections) |
| Find companies by name | `GET /v1/firme?q=…` |
| Find companies by county/CAEN/status/revenue… | `GET /v1/firme?…filters…` |
| A company's financial history (for charts) | `GET /v1/firme/{cui}/bilanturi` |
| Who runs this company | `GET /v1/firme/{cui}/administratori` |
| All companies a person runs | `GET /v1/persoane/{id}/firme` (find the id via `GET /v1/persoane?nume=…`) |
| VAT history | `GET /v1/firme/{cui}/tva` |
| What changed recently on a company | `GET /v1/firme/{cui}/modificari` |
| Get notified when a company changes | `POST /v1/webhooks` |
| Resolve a CAEN/county/legal-form code to a label | `GET /v1/nomenclatoare/…` (cache 24h) |
| Country/county/sector aggregates, top firms | `GET /v1/stats`, `/v1/stats/judete`, `/v1/stats/caen/{cod}`, `/v1/top-firme` |
| NGO data | `GET /v1/ong…` |
| School data | `GET /v1/unitati-invatamant…` |

### 14.2 Recipe — validate a partner before invoicing (accounting)
```python
import os, httpx
BASE, KEY = "https://api.arcadi.nexus", os.environ["NEXUS_API_KEY"]

def can_invoice(cui: int) -> dict:
    r = httpx.get(f"{BASE}/v1/firme/{cui}/validate", headers={"X-API-Key": KEY}, timeout=10)
    r.raise_for_status()
    d = r.json()["data"]
    return {
        "ok": d["apta_facturare"],          # not struck off, not inactive
        "exists": d["exista"],
        "name": d.get("denumire"),
        "vat_payer": d.get("status_tva"),    # decide whether to add VAT
        "e_factura": d.get("status_e_factura"),
    }
```

### 14.3 Recipe — bulk-validate a customer list
```python
import httpx, os
BASE, KEY = "https://api.arcadi.nexus", os.environ["NEXUS_API_KEY"]

def validate_many(cuis: list[int]) -> list[dict]:
    out = []
    for i in range(0, len(cuis), 100):                       # 100 per call
        chunk = cuis[i:i+100]
        r = httpx.post(f"{BASE}/v1/firme/batch", headers={"X-API-Key": KEY},
                       json={"cui": chunk}, timeout=30)
        r.raise_for_status()
        out.extend(r.json()["data"])
    return out
```

### 14.4 Recipe — paginate a full search result set
```python
def search_all(params: dict):
    params = {**params, "limit": 100, "offset": 0}
    while True:
        r = httpx.get(f"{BASE}/v1/firme", headers={"X-API-Key": KEY}, params=params, timeout=20)
        r.raise_for_status()
        body = r.json()
        yield from body["data"]
        if not body["paginare"]["are_pagina_urmatoare"]:
            break
        params["offset"] += params["limit"]
        if params["offset"] > 10000:                          # API hard cap → narrow filters
            break
```

### 14.5 Recipe — server-side proxy (keeps the key off the client)
Your frontend must never see the key. Expose a tiny endpoint on YOUR backend that injects it.

**Cloudflare Worker / Pages Function**
```js
export async function onRequestGet({ request, env }) {
  const cui = new URL(request.url).searchParams.get("cui");
  const r = await fetch(`https://api.arcadi.nexus/v1/firme/${cui}/validate`, {
    headers: { "X-API-Key": env.NEXUS_API_KEY },   // secret binding, server-only
  });
  return new Response(await r.text(), { status: r.status, headers: { "content-type": "application/json" } });
}
```

**Node / Express**
```js
app.get("/api/firma/:cui", async (req, res) => {
  const r = await fetch(`https://api.arcadi.nexus/v1/firme/${req.params.cui}`, {
    headers: { "X-API-Key": process.env.NEXUS_API_KEY },
  });
  res.status(r.status).type("application/json").send(await r.text());
});
```

### 14.6 Recipe — handle rate limits gracefully
```python
import time, httpx
def call(path, **kw):
    for attempt in range(5):
        r = httpx.get(f"{BASE}{path}", headers={"X-API-Key": KEY}, timeout=15, **kw)
        if r.status_code == 429:
            time.sleep(int(r.headers.get("Retry-After", "2")))   # honor server hint
            continue
        if r.status_code in (500, 503):
            time.sleep(2 ** attempt)                              # exp backoff
            continue
        r.raise_for_status()
        return r.json()
    raise RuntimeError("NEXUS unavailable after retries")
```

---

## 15. Glossary (Romanian field → meaning)

| Field | Meaning |
|---|---|
| `cui` | Fiscal code (CUI/CIF). Primary key for firms/NGOs. Integer. |
| `denumire` | Company / entity name. |
| `cod_inmatriculare` | Trade-registry number (e.g. `J12/100/2015`; `F…`/`X…` for some forms). |
| `euid` | European Unique Identifier. |
| `data_inmatriculare` | Registration date. |
| `forma_juridica` | Legal form code (`SRL`, `SA`, `PFA`, …). |
| `is_radiata` | Struck off / dissolved (true/false). |
| `stare_curenta` | Current state (code + label, e.g. `FUNCTIONARE`). |
| `cod_caen_principal` / `caen_principal` | Main activity code (+ `versiune` 2/3 + label). |
| `caen_secundare` | Secondary activity codes. |
| `status_tva` | VAT payer. `status_tva_incasare` = VAT-on-collection; `status_split_tva` = split VAT. |
| `status_inactiv` | Declared fiscally inactive. |
| `status_e_factura` | Registered in the e-Factura system (`data_inreg_e_factura` = since). |
| `organ_fiscal_competent` | Competent tax office. |
| `capital_social` | Share capital (NOT populated → `null`). |
| `categorie_marime` | Size class (micro/mică/mijlocie/mare). |
| `administrator` | Denormalized current administrator name (convenience). |
| `persoane` / `administratori` | People linked to the firm; `calitate` = role; `is_curent` = currently in role. |
| `cota_procent` / `aport` | Ownership % / contribution (NOT populated → `null`). |
| `bilanturi` | Annual financial statements. |
| `cifra_afaceri_neta` | Net turnover / revenue. |
| `profit_net` / `pierdere_neta` | Net profit / net loss. |
| `numar_mediu_salariati` | Average number of employees. |
| `perioade_tva` | VAT registration periods. |
| `modificari` | Detected changes over time (audit). |
| `judet` / `localitate` / `sector` | County / locality / Bucharest sector. |
| `sucursale_externe` | Branches in other EU states. |
| `cod_siiir` | School identifier (MEN). |
| `paginare.are_pagina_urmatoare` | Whether more results exist. |

---

## 16. Out of scope for integrators (FYI only)
- **`POST /v1/admin/keys`** and other `/v1/admin/*` endpoints exist but are **hidden from the public OpenAPI** and gated by a separate `X-Admin-Token` (held only by the official key-gen portal). Integrators do **not** mint keys programmatically — the user generates a key in the portal and pastes it to you (see §2).
- Internal ops endpoints (job triggers, control) are on a different service that is **not** exposed publicly. You will never reach them via `api.arcadi.nexus`.

---

## 17. Final integration checklist
- [ ] Got the `nxs_live_…` key from the user; stored it as a **server-side** secret (env var), never in client code.
- [ ] Grepped the client bundle for `nxs_live_` → zero matches.
- [ ] All calls go to `https://api.arcadi.nexus/v1/…` with header `X-API-Key`.
- [ ] Reading data from `data`; switching error handling on `eroare.cod` (not message text).
- [ ] Honoring `429` `Retry-After` + exponential backoff on `5xx`.
- [ ] Pre-validating CUIs with the local checksum (§4) before calling.
- [ ] For financial filters/sorts, always sending `an_financiar`.
- [ ] Paginating via `are_pagina_urmatoare`; not exceeding `offset` 10000.
- [ ] If using webhooks: verifying `X-Nexus-Signature` over the **raw body** + rejecting >5-min-old timestamps + deduping on `X-Nexus-Delivery`.
- [ ] Caching `nomenclatoare` responses for 24h.
- [ ] Not promising data that's `null` by design (capital social, UBO, cote — §13).

---

## 18. Verification (prove the integration works)

```bash
# 1. Service is up (no key needed)
curl -fsS "https://api.arcadi.nexus/healthz"
# → {"status":"ok","db":"ok","versiune_api":"v1"}

# 2. Key works + see your limits
curl -fsS "https://api.arcadi.nexus/v1" -H "X-API-Key: $NEXUS_API_KEY"
# → { "data": { "api": "NEXUS Public API", "cheie": { "prefix": "nxs_live_…", "rate_limit_per_min": … } }, ... }

# 3. Validate a CUI (replace with a real one)
curl -fsS "https://api.arcadi.nexus/v1/firme/12345678/validate" -H "X-API-Key: $NEXUS_API_KEY"

# 4. Missing key → 401 (expected)
curl -fsS -o /dev/null -w "%{http_code}\n" "https://api.arcadi.nexus/v1/firme/12345678/validate"
# → 401

# 5. Search
curl -fsS -G "https://api.arcadi.nexus/v1/firme" -H "X-API-Key: $NEXUS_API_KEY" \
  --data-urlencode "q=exemplu" --data-urlencode "limit=5"
```
If #1 returns `ok`, #2 echoes your key prefix, and #4 returns `401`, the integration is correctly wired.

---

## 19. Endpoint cheat-sheet (everything, at a glance)

```
META
  GET    /healthz                              (no auth) liveness + db
  GET    /v1                                   key info + limits

FIRME
  GET    /v1/firme                             advanced search (q, administrator, judet, localitate,
                                               cod_caen, caen_mod, forma_juridica, status_*, is_radiata,
                                               inmatriculare_de_la/_pana_la, cifra/profit/salariati _min/_max
                                               (+an_financiar), sort, order, limit, offset, include)
  GET    /v1/firme/{cui}                        full profile (include=…, an_bilant)
  GET    /v1/firme/{cui}/validate               fast invoice check (200 even if not found)
  POST   /v1/firme/batch                         up to 100 CUIs ({cui:[…], include:[…]})
  GET    /v1/firme/{cui}/administratori          (doar_curenti)
  GET    /v1/firme/{cui}/bilanturi               (de_la, pana_la)
  GET    /v1/firme/{cui}/caen
  GET    /v1/firme/{cui}/tva
  GET    /v1/firme/{cui}/sucursale
  GET    /v1/firme/{cui}/modificari              (limit)

PERSOANE
  GET    /v1/persoane                            search (nume[min3], tip, judet_curent, limit, offset)
  GET    /v1/persoane/{id}                       detail + firms (limit_firme)
  GET    /v1/persoane/{id}/firme                 (limit, offset)

ONG
  GET    /v1/ong                                 search (q, judet, localitate, status_tva, is_radiata)
  GET    /v1/ong/{cui}
  GET    /v1/ong/{cui}/bilanturi

UNITĂȚI DE ÎNVĂȚĂMÂNT
  GET    /v1/unitati-invatamant                  search (q, judet, localitate, tip, is_publica)
  GET    /v1/unitati-invatamant/{cod_siiir}

NOMENCLATOARE  (cache 24h)
  GET    /v1/nomenclatoare/caen                  (versiune, q, limit)
  GET    /v1/nomenclatoare/caen/{cod}            (versiune)
  GET    /v1/nomenclatoare/judete
  GET    /v1/nomenclatoare/forme-juridice
  GET    /v1/nomenclatoare/stari
  GET    /v1/nomenclatoare/calitati

STATISTICI
  GET    /v1/stats
  GET    /v1/stats/judete
  GET    /v1/stats/caen/{cod}
  GET    /v1/top-firme                            (an, cod_caen, judet, sort, limit)

WEBHOOKS
  POST   /v1/webhooks                             ({target_url, events, filter_cuis, filter_fields})
  GET    /v1/webhooks
  GET    /v1/webhooks/{id}
  GET    /v1/webhooks/{id}/livrari                (limit)
  DELETE /v1/webhooks/{id}
```

Base URL: `https://api.arcadi.nexus` · Auth: `X-API-Key: nxs_live_…` · Interactive: `/docs`, `/redoc`, `/openapi.json`

---

*All names, CUIs, codes, and addresses in this manual are fictional examples for illustration only. Live API responses contain real public data on Romanian legal entities (lawfully published via ONRC/ANAF/MFP). When you generate documentation or examples, keep them fictional too.*
