# DataNexusAI API

Public docs (same content as this file):

- HTML: `/api/` or `/api/docs`
- Markdown: `/api/docs.md`
- Catalog-driven docs: `/docs` (and `/docs/<category>/`)
- OpenAPI: `/openapi.json`
- Agent brief: `/llms.txt`
- x402 discovery: `/.well-known/x402`

Local: `http://127.0.0.1:8000/api/`
Production: `https://api.datanexusai.org/api/`

Start the server from `services/web/config`:

```bash
python manage.py runserver
```

This file is the list of implemented HTTP calls. Add a section here whenever a new endpoint is added.

---

## Data sources & licensing

Economic series ingested from third parties (including FRED) are subject to source
Terms of Use. **FRED API access does not imply rights to commercially redistribute
series.**

DataNexusAI provides **derived analytics** calculated from multiple public data
sources, including selected FRED API data used at **request time**. Raw FRED series
are **not** exposed as DataNexusAI endpoints and are **not** archived to MySQL for
redistribution.

Paid FRED-backed analytics require `license_status=APPROVED` per series (Django admin).
See project docs: `docs/fred-licensing.md`, `docs/data-sources.md`.

> This product uses the FRED® API but is not endorsed or certified by the
> Federal Reserve Bank of St. Louis.

FRED content is used as a **runtime economic data source** for calculations, not as
an AI/ML training corpus and not as a FRED data mirror.

---

## Payments (x402 / HTTP 402)

Data endpoints require [x402](https://docs.x402.org) micropayments (**USDC**). Buyers (wallets, Coinbase Wallet, AI agents) can pay on any accepted network. Documentation stays free.

| Path / who | Access |
| --- | --- |
| `/api/`, `/api/docs`, `/api/docs.md` | Free (everyone) |
| `/docs`, `/openapi.json`, `/llms.txt`, `/.well-known/x402`, `/robots.txt`, `/sitemap.xml` | Free (everyone) |
| Direct `localhost` / `127.0.0.1` runserver | Free (`X402_SKIP_LOCAL`) |
| IPs in `X402_FREE_IPS` | Free (owner allowlist) |
| Everyone else on paid `/api/...` routes | Paid |

**Accepted networks (default):** Base (`eip155:8453`), Polygon (`eip155:137`), Arbitrum One (`eip155:42161`). Configure via `X402_NETWORKS=base,polygon,arbitrum`. Same EVM `X402_PAY_TO` address receives USDC on each chain — switch network in MetaMask/Coinbase Wallet to see balances.

### Flow

1. Client calls a paid endpoint without payment → **`402 Payment Required`** with a `PAYMENT-REQUIRED` header (price, network, pay-to address).
2. Client signs a USDC payment and retries with a `PAYMENT-SIGNATURE` header.
3. Server verifies + settles via the facilitator, then returns **`200`** with data and a `PAYMENT-RESPONSE` header.

```bash
# Docs — free (production)
curl -i "https://api.datanexusai.org/api/"

# Data — unpaid → 402 (from a non-allowlisted IP)
curl -i "https://api.datanexusai.org/api/v1/datasets"
```

### Config (project `.env`)

| Variable | Example | Notes |
| --- | --- | --- |
| `X402_ENABLED` | `True` | Turn on gating |
| `X402_PAY_TO` | `0xYourAddress` | EVM wallet that receives **USDC** (same address on Base/Polygon/Arbitrum) |
| `X402_NETWORKS` | `base,polygon,arbitrum` | Comma-separated networks (aliases or CAIP-2 ids) |
| `X402_PRICE` | `0.01` | Per request (becomes `$0.01`; avoid `$` in `.env`) |
| `X402_FACILITATOR_URL` | `https://facilitator.payai.network` | Multi-network facilitator. Coinbase CDP: `https://api.cdp.coinbase.com/platform/v2/x402` |
| `X402_FREE_IPS` | `1.2.3.4` | Optional owner allowlist |

Install: `pip install "x402[evm,httpx]==2.24.0"`

Cash-out path: USDC on Base → Coinbase → EUR → Revolut.

Compatible clients: any x402 buyer (AI agents, `@x402/fetch`, Python `x402` client). See https://docs.x402.org

---

## SEBRA

Data source: `egovbg_sebra_days` (days with `status = HAS_PAYMENTS`). Amounts are sums, not zero-filled empty days.

### `GET /api/sebra/getPeriodsWithMostPayments`

**Sample:** [https://api.datanexusai.org/api/sebra/getPeriodsWithMostPayments?period=days&from=01-10-2020&to=01-11-2020](https://api.datanexusai.org/api/sebra/getPeriodsWithMostPayments?period=days&from=01-10-2020&to=01-11-2020)

Which calendar slots have the **largest payment sums** (and how many payments).

**Period (required, one of):**

| Value | Meaning |
| --- | --- |
| `days` | Day of month `1–31` |
| `months` | Calendar month + year |
| `weekdays` | Weekday (Monday=0 … Sunday=6) |

Pass as `?period=days` or as a flag: `?days` / `?months` / `?weekdays`.

**Optional query params:**

| Param | Example | Description |
| --- | --- | --- |
| `from` / `to` | see below | Inclusive date window. If `from` is set and `to` is omitted, `to` is today. |
| `currency` | `EUR` | `BGN` or `EUR`. If omitted, rows are grouped by currency. |
| `year` | `2024` | Restrict to one calendar year. |
| `limit` | `10` | Max rows after sorting by amount descending. |

**Date window (`from` / `to`):**

| `period` | Format | Example | Meaning |
| --- | --- | --- | --- |
| `days`, `weekdays` | `DD-MM-YYYY` | `from=01-10-2020&to=01-11-2020` | 1 Oct 2020 through 1 Nov 2020 |
| `months` | `MM-YYYY` | `from=01-2020&to=05-2020` | January 2020 through May 2020 (whole months) |

`months` also accepts `DD-MM-YYYY` if you need an exact calendar cut. `days` / `weekdays` also accept `MM-YYYY` as the first/last day of that month.

If `from` is given without `to`, the window is **from that date through today**. Only `to` (no `from`) still means everything up to that date. With neither bound, the full history is used.

**Examples:**

```http
GET /api/sebra/getPeriodsWithMostPayments?period=days
GET /api/sebra/getPeriodsWithMostPayments?period=days&from=01-10-2020&to=01-11-2020
GET /api/sebra/getPeriodsWithMostPayments?period=days&from=01-10-2020
GET /api/sebra/getPeriodsWithMostPayments?period=months&from=01-2020&to=05-2020&currency=EUR
GET /api/sebra/getPeriodsWithMostPayments?period=weekdays&year=2025&limit=5
GET /api/sebra/getPeriodsWithMostPayments?days&currency=EUR
```

```bash
curl "https://api.datanexusai.org/api/sebra/getPeriodsWithMostPayments?period=days&from=01-10-2020&to=01-11-2020"
curl "https://api.datanexusai.org/api/sebra/getPeriodsWithMostPayments?period=months&from=01-2020&to=05-2020&currency=EUR"
```

**200 response (shape):**

```json
{
  "endpoint": "getPeriodsWithMostPayments",
  "period": "days",
  "period_key": "day_of_month",
  "description": "Days of the month (1–31) with the largest payment sums",
  "ordered_by": "total_amount",
  "filters": { "currency": "EUR", "year": null, "from": "2020-10-01", "to": "2020-11-01", "limit": null },
  "count": 31,
  "items": [
    {
      "day_of_month": 28,
      "label": "28",
      "currency": "EUR",
      "total_amount": "1234567.89",
      "payment_count": 42,
      "observed_days": 12,
      "rank": 1
    }
  ]
}
```

For `months`, items use `month`, then `year`, plus a Bulgarian month label (e.g. `януари`). Each calendar month is a separate row (August 2020 ≠ August 2024).  
For `weekdays`, items use `weekday` plus a Bulgarian weekday label (e.g. `понеделник`).

**400:** missing/invalid `period`, invalid `currency`, invalid `from`/`to`, `from` after `to`, or `limit < 1`.

---

### `GET /api/sebra/getTaxonomyForYear/{year}`

**Sample:** [https://api.datanexusai.org/api/sebra/getTaxonomyForYear/2019](https://api.datanexusai.org/api/sebra/getTaxonomyForYear/2019)

Where the money went in that calendar year: SEBRA economic accounts grouped as **Salaries** (`Заплати`), **Maintenance** (`Издръжка`), **Capital expenditure** (`Капиталови разходи`) (plus social payments, transfers, taxes, other), ordered by amount descending.

Also: `GET /api/sebra/getTaxonomyForYear?year=2019`

**Params:**

| Param | Example | Description |
| --- | --- | --- |
| `year` | `2019` | Required unless given in the path. |
| `currency` | `BGN` | `BGN` or `EUR`. If omitted, groups are split by currency. |

**Examples:**

```http
GET /api/sebra/getTaxonomyForYear/2019
GET /api/sebra/getTaxonomyForYear?year=2019
GET /api/sebra/getTaxonomyForYear/2019?currency=BGN
```

```bash
curl "https://api.datanexusai.org/api/sebra/getTaxonomyForYear/2019"
```

**200 response (shape):**

```json
{
  "endpoint": "getTaxonomyForYear",
  "year": 2019,
  "ordered_by": "total_amount",
  "filters": { "year": 2019, "currency": null },
  "year_totals": [{ "currency": "BGN", "total_amount": "1000000.00" }],
  "count": 3,
  "items": [
    {
      "category": "Заплати",
      "category_key": "salaries",
      "currency": "BGN",
      "total_amount": "500000.00",
      "share_percent": "50.00",
      "payment_count": 120,
      "account_count": 2,
      "rank": 1,
      "accounts": [
        {
          "code": "01 xxxx",
          "description": "Заплати, възнаграждения и други плащания за персонала - нетна сума за изплащане",
          "total_amount": "490000.00",
          "payment_count": 100,
          "rank": 1
        }
      ]
    }
  ]
}
```

**Salaries** (`Заплати`), **Maintenance** (`Издръжка`) and **Capital expenditure** (`Капиталови разходи`) are always present (amount `0` if that year had none). Other buckets appear only when there is spend.

**400:** missing/invalid `year`, or invalid `currency`.

---

### `GET /api/sebra/getTaxonomyForPeriod`

**Sample:** [https://api.datanexusai.org/api/sebra/getTaxonomyForPeriod?from=01-10-2020&to=01-11-2020](https://api.datanexusai.org/api/sebra/getTaxonomyForPeriod?from=01-10-2020&to=01-11-2020)

Same account buckets as `getTaxonomyForYear` (**Salaries**, **Maintenance**, **Capital expenditure**, …), but for a date window.

**Params:**

| Param | Example | Description |
| --- | --- | --- |
| `from` | `01-10-2020` or `01-2020` | Required. `DD-MM-YYYY` or `MM-YYYY` (month starts on day 1). |
| `to` | `01-11-2020` or `05-2020` | Inclusive end. If omitted, **today**. `MM-YYYY` means the last day of that month. |
| `currency` | `BGN` | `BGN` or `EUR`. If omitted, groups are split by currency. |

**Examples:**

```http
GET /api/sebra/getTaxonomyForPeriod?from=01-10-2020&to=01-11-2020
GET /api/sebra/getTaxonomyForPeriod?from=01-10-2020
GET /api/sebra/getTaxonomyForPeriod?from=01-2020&to=05-2020
GET /api/sebra/getTaxonomyForPeriod?from=01-2020&currency=BGN
```

```bash
curl "https://api.datanexusai.org/api/sebra/getTaxonomyForPeriod?from=01-10-2020&to=01-11-2020"
curl "https://api.datanexusai.org/api/sebra/getTaxonomyForPeriod?from=01-2020"
```

**200 response (shape):** same `items` as `getTaxonomyForYear`. Totals are in `period_totals`. `filters.from` / `filters.to` are ISO dates.

**400:** missing/invalid `from`/`to`, `from` after `to`, or invalid `currency`.

---

### `GET /api/sebra/getMostSponsoredProgramme`

**Sample:** [https://api.datanexusai.org/api/sebra/getMostSponsoredProgramme/2019](https://api.datanexusai.org/api/sebra/getMostSponsoredProgramme/2019)

Programmes ranked by **total amount** (then payment count): programme → payment count → total amount.

Filter with a **year** or a **from/to** window (not both). Without either, the full history is used.

**Params:**

| Param | Example | Description |
| --- | --- | --- |
| `year` | `2019` | Calendar year. Also: `/getMostSponsoredProgramme/2019`. |
| `from` / `to` | `01-10-2020`, `01-2020` | Same date window as `getTaxonomyForPeriod`. If `from` is set and `to` is omitted, `to` is today. |
| `currency` | `EUR` | `BGN` or `EUR`. If omitted, rows are grouped by currency. |
| `limit` | `10` | Max programmes after ranking. |

**Examples:**

```http
GET /api/sebra/getMostSponsoredProgramme/2019
GET /api/sebra/getMostSponsoredProgramme?year=2019
GET /api/sebra/getMostSponsoredProgramme?from=01-10-2020&to=01-11-2020
GET /api/sebra/getMostSponsoredProgramme?from=01-2020&to=05-2020&limit=10
GET /api/sebra/getMostSponsoredProgramme?from=01-2020&currency=BGN
```

```bash
curl "https://api.datanexusai.org/api/sebra/getMostSponsoredProgramme/2019"
curl "https://api.datanexusai.org/api/sebra/getMostSponsoredProgramme?from=01-10-2020&to=01-11-2020&limit=10"
```

**200 response (shape):**

```json
{
  "endpoint": "getMostSponsoredProgramme",
  "ordered_by": "total_amount",
  "filters": { "year": 2019, "from": null, "to": null, "currency": null, "limit": null },
  "count": 1,
  "items": [
    {
      "programme": "Оперативна програма …",
      "payment_count": 42,
      "total_amount": "1234567.89",
      "currency": "BGN",
      "rank": 1
    }
  ]
}
```

**400:** invalid `year`, `year` together with `from`/`to`, invalid `from`/`to`, invalid `currency`, or `limit < 1`.

---

### `GET /api/sebra/getUnusualPayments`

**Sample:** [https://api.datanexusai.org/api/sebra/getUnusualPayments/2019](https://api.datanexusai.org/api/sebra/getUnusualPayments/2019)

Four unusual-activity lists for the **same** year or `from`/`to` window. `limit` (default **5**) is applied to **each** list.

1. **`unusual_days`** — days whose total amount is unusually high (z-score ≥ 2 vs other days in the window).
2. **`unusual_programmes`** — days when a programme paid unusually much vs that programme’s own days in the window (needs ≥ 5 days for the programme).
3. **`expense_spikes`** — expense type (Salaries, Maintenance, Capital expenditure, …) whose monthly total is at least **2×** the previous month in the window.
4. **`unusual_payment_counts`** — days with an unusually high **number** of payments (z-score ≥ 2).

**Params:** same as `getMostSponsoredProgramme` (`year` or `from`/`to`, `currency`, `limit`).

**Examples:**

```http
GET /api/sebra/getUnusualPayments/2019
GET /api/sebra/getUnusualPayments?year=2019&limit=5
GET /api/sebra/getUnusualPayments?from=01-10-2020&to=01-11-2020
GET /api/sebra/getUnusualPayments?from=01-2020&to=05-2020&limit=5
```

```bash
curl "https://api.datanexusai.org/api/sebra/getUnusualPayments/2019?limit=5"
```

**200 response (shape):**

```json
{
  "endpoint": "getUnusualPayments",
  "method": "zscore >= 2 vs the mean in the same window; expense spikes are month-over-month ratio >= 2",
  "filters": { "year": 2019, "from": null, "to": null, "currency": null, "limit": 5 },
  "unusual_days": [
    {
      "payment_date": "2019-12-20",
      "currency": "BGN",
      "total_amount": "9000000.00",
      "mean_amount": "1000000.00",
      "zscore": "3.20",
      "payment_count": 400,
      "rank": 1
    }
  ],
  "unusual_programmes": [
    {
      "programme": "Оперативна програма …",
      "payment_date": "2019-06-15",
      "currency": "BGN",
      "total_amount": "2000000.00",
      "mean_amount": "200000.00",
      "zscore": "4.10",
      "observed_days": 40,
      "rank": 1
    }
  ],
  "expense_spikes": [
    {
      "category": "Капиталови разходи",
      "category_key": "capital",
      "currency": "BGN",
      "month": "2019-11",
      "total_amount": "800000.00",
      "previous_amount": "200000.00",
      "increase_ratio": "4.00",
      "rank": 1
    }
  ],
  "unusual_payment_counts": [
    {
      "payment_date": "2019-03-25",
      "currency": "BGN",
      "payment_count": 2000,
      "mean_count": "400.00",
      "zscore": "2.80",
      "total_amount": "1500000.00",
      "rank": 1
    }
  ]
}
```

A list can be empty if nothing in the window crosses the threshold.

**400:** invalid `year`, `year` together with `from`/`to`, invalid `from`/`to`, invalid `currency`, or `limit < 1`.

---

## Hearthstone

Card snapshots from `hearthstone_cards_en` and `hearthstone_cards_bg` (same schema; BG holds translated text fields). Class filters use the English enum in `hearthstone_cards_en.card_class` (e.g. `SHAMAN`).

### `GET /api/hearthstone/getTranslatedCardInBG`

**Sample:** [https://api.datanexusai.org/api/hearthstone/getTranslatedCardInBG?name=Fireball](https://api.datanexusai.org/api/hearthstone/getTranslatedCardInBG?name=Fireball)

Look up an **English** card name in `hearthstone_cards_en`, resolve `dbf_id` / `id`, return matching rows from `hearthstone_cards_bg`.

| Param | Example | Description |
| --- | --- | --- |
| `name` | `Fireball` | Required unless given in the path. Exact match, case-insensitive. |

**Examples:**

```http
GET /api/hearthstone/getTranslatedCardInBG?name=Fireball
GET /api/hearthstone/getTranslatedCardInBG/Fireball
```

**200 response (shape):** `query`, `matched_en` (id/dbf_id/name), `count`, `items` (BG card objects). Multiple EN variants with the same name return multiple BG items when present.

**404:** name not found in English table. **400:** missing `name`.

### `GET /api/hearthstone/getCardsByClass`

**Sample:** [https://api.datanexusai.org/api/hearthstone/getCardsByClass?class=Shaman](https://api.datanexusai.org/api/hearthstone/getCardsByClass?class=Shaman)

All cards with `hearthstone_cards_en.card_class = SHAMAN` (accepts `Shaman`, `SHAMAN`, `шаман`, …). Each item is `{ "en": {...}, "bg": {...} }` (`bg` may be `null`).

| Param | Example | Description |
| --- | --- | --- |
| `class` | `Shaman` | Required unless given in the path. |
| `collectible` | `true` | Optional. `true` / `false` to filter collectible cards. |
| `limit` | `200` | Max rows (default 200, max 2000). |

**Examples:**

```http
GET /api/hearthstone/getCardsByClass?class=Shaman
GET /api/hearthstone/getCardsByClass/Mage?collectible=true&limit=50
```

**400:** missing/unknown class, or invalid `limit`.

### `GET /api/hearthstone/getCard`

**Sample:** [https://api.datanexusai.org/api/hearthstone/getCard?name=Fireball](https://api.datanexusai.org/api/hearthstone/getCard?name=Fireball)

Bilingual lookup from both tables. Provide `name` and/or `dbf_id`.

| Param | Example | Description |
| --- | --- | --- |
| `name` | `Fireball` | English name, case-insensitive. |
| `dbf_id` | `315` | Numeric Hearthstone dbf id. |

**Examples:**

```http
GET /api/hearthstone/getCard?name=Fireball
GET /api/hearthstone/getCard?dbf_id=315
GET /api/hearthstone/getCard?name=Fireball&dbf_id=315
```

**200:** `items` as `{ "en": {...}, "bg": {...} }`. **404:** no EN match. **400:** neither param given.

---

## PTP report

Road accidents from `egovbg_ptp_reports` (Bulgarian MoI / МВР snapshot). `location` is usually `ГР.ВАРНА`, `ГР.СОФИЯ`, etc.

### `GET /api/ptp/getPtpPerCity`

**Sample:** [https://api.datanexusai.org/api/ptp/getPtpPerCity?city=Sofia&year=2025](https://api.datanexusai.org/api/ptp/getPtpPerCity?city=Sofia&year=2025)

All crashes for a city. Works with Latin or Cyrillic names (`Sofia`, `София`, `Varna`, `Пловдив`, …).

**Params:**

| Param | Example | Description |
| --- | --- | --- |
| `city` | `Sofia` | Required unless given in the path. |
| `year` | `2025` | Calendar year. |
| `from` / `to` | `01-01-2025`, `01-2025` | Same window as SEBRA. If `from` is set and `to` is omitted, `to` is today. |
| `limit` | `100` | Optional max rows (newest first). Omit to return all matches. |

Use **either** `year` **or** `from`/`to`, not both.

**Examples:**

```http
GET /api/ptp/getPtpPerCity?city=Sofia
GET /api/ptp/getPtpPerCity/Sofia?year=2025
GET /api/ptp/getPtpPerCity?city=Варна&from=01-01-2025&to=31-12-2025
GET /api/ptp/getPtpPerCity?city=Plovdiv&from=01-2025
```

```bash
curl "https://api.datanexusai.org/api/ptp/getPtpPerCity?city=Sofia"
curl "https://api.datanexusai.org/api/ptp/getPtpPerCity/Sofia?year=2025"
```

**200 response (shape):**

```json
{
  "endpoint": "getPtpPerCity",
  "filters": { "city": "Sofia", "matched_as": ["СОФИЯ", "СТОЛИЧНА"], "year": 2025, "from": null, "to": null, "limit": null },
  "count": 2,
  "items": [
    {
      "crash_datetime": "2025-12-20T13:10:00",
      "crash_type": "сблъскване между МПС странично",
      "ptp_place": "в населено място",
      "region": "СОФИЯ",
      "municipality": "СТОЛИЧНА",
      "location": "ГР.СОФИЯ",
      "latitude": "42.69770000",
      "longitude": "23.32190000",
      "died_count": 0,
      "injured_count": 1,
      "participant_count": 2,
      "is_major": false
    }
  ]
}
```

**400:** missing `city`, invalid `year`, `year` together with `from`/`to`, invalid `from`/`to`, or `limit < 1`.

---

### `GET /api/ptp/getPtpPerRegion`

**Sample:** [https://api.datanexusai.org/api/ptp/getPtpPerRegion?region=Sofia&year=2025](https://api.datanexusai.org/api/ptp/getPtpPerRegion?region=Sofia&year=2025)

All crashes in a **district/oblast** (`region` column). `Sofia` maps to **СОФИЯ (СТОЛИЦА)** (Sofia city), not Sofia district. For the district use `region=Софийска`.

Same time params as `getPtpPerCity`: `year` or `from`/`to`, optional `limit`.

**Examples:**

```http
GET /api/ptp/getPtpPerRegion?region=Sofia
GET /api/ptp/getPtpPerRegion/Sofia?year=2025
GET /api/ptp/getPtpPerRegion?region=Варна&from=01-2025
GET /api/ptp/getPtpPerRegion?region=Софийска
```

```bash
curl "https://api.datanexusai.org/api/ptp/getPtpPerRegion?region=Sofia"
```

Response shape matches `getPtpPerCity` (`endpoint` is `getPtpPerRegion`, filter key is `region`).

**400:** missing `region`, invalid `year`, `year` together with `from`/`to`, invalid `from`/`to`, or `limit < 1`.

---

### `GET /api/ptp/getMostDeadlyLocations`

**Sample:** [https://api.datanexusai.org/api/ptp/getMostDeadlyLocations?year=2020](https://api.datanexusai.org/api/ptp/getMostDeadlyLocations?year=2020)

Deadliest **2 km** crash clusters for a year. Centroids come from the monthly table `egovbg_ptp_dedly_locations`. Counts (`crash_count`, `died_count`, `injured_count`) are summed only from linked rows in `egovbg_ptp_reports` for that year — crash details are not duplicated.

Locations with **0 deaths** in the window are omitted. Sorted by deaths, then crash count.

**Params:**

| Param | Example | Description |
| --- | --- | --- |
| `year` | `2020` | Required unless given in the path, or use `from`/`to` instead. |
| `from` / `to` | `01-01-2020`, `01-2020` | Same window as other PTP calls. If `from` is set and `to` is omitted, `to` is today. |
| `limit` | `20` | Max clusters. Default `20`, max `200`. |

Use **either** `year` **or** `from`/`to`, not both.

**Examples:**

```http
GET /api/ptp/getMostDeadlyLocations?year=2020
GET /api/ptp/getMostDeadlyLocations/2020
GET /api/ptp/getMostDeadlyLocations?year=2020&limit=10
GET /api/ptp/getMostDeadlyLocations?from=01-01-2020&to=31-12-2020
```

```bash
curl "https://api.datanexusai.org/api/ptp/getMostDeadlyLocations?year=2020"
curl "https://api.datanexusai.org/api/ptp/getMostDeadlyLocations/2020?limit=10"
```

**200 response (shape):**

```json
{
  "endpoint": "getMostDeadlyLocations",
  "filters": { "year": 2020, "from": null, "to": null, "limit": 20 },
  "count": 2,
  "items": [
    {
      "location_id": 12,
      "latitude": "42.69770000",
      "longitude": "23.32190000",
      "radius_m": 2000,
      "region": "СОФИЯ (СТОЛИЦА)",
      "location": "ГР.СОФИЯ",
      "crash_count": 18,
      "died_count": 7,
      "injured_count": 21
    }
  ]
}
```

**400:** missing `year` (and no `from`/`to`), invalid `year`, `year` together with `from`/`to`, invalid `from`/`to`, or invalid `limit`.

---

## Agri-food

JSON API over the loaded tables (`agrifood_import`, `agrifood_member_state`,
`agrifood_cereals_*`, `agrifood_oilseeds_*`, `agrifood_pigmeat_*`). One interface
across the three price datasets. Facts are taken from the latest `agrifood_import`
row per `dataset`.

Pagination on every list: `limit` (default 100, max 500) and `offset`.

Commodity ids are built from real keys:

- cereals: `cereals_prices:{product_name}`
- oilseeds: `oilseeds_prices:{product_name}|{product_type}`
- pigmeat: `pigmeat_prices:{pig_class}`

### `GET /api/v1/datasets`

**Sample:** [https://api.datanexusai.org/api/v1/datasets](https://api.datanexusai.org/api/v1/datasets)

Latest import per dataset (`dataset`, `source`, `source_file`, `source_url`, `file_hash`, `import_date`, `row_count`).

### `GET /api/v1/datasets/{id}`

**Sample:** [https://api.datanexusai.org/api/v1/datasets/cereals_prices](https://api.datanexusai.org/api/v1/datasets/cereals_prices)

`id` is `agrifood_import.dataset` (`cereals_prices`, `oilseeds_prices`, `pigmeat_prices`). `results` are import versions.

### `GET /api/v1/commodities`

**Sample:** [https://api.datanexusai.org/api/v1/commodities?dataset=cereals_prices&limit=20](https://api.datanexusai.org/api/v1/commodities?dataset=cereals_prices&limit=20)

Catalog from `agrifood_cereals_product`, `agrifood_oilseeds_product`, `agrifood_pigmeat_class`.

| Param | Column |
| --- | --- |
| `dataset` | tagged dataset id |
| `q` / `commodity` / `product` | `product_name` or `pig_class` |

### `GET /api/v1/countries`

**Sample:** [https://api.datanexusai.org/api/v1/countries](https://api.datanexusai.org/api/v1/countries)

From `agrifood_member_state` (`code`, `name`). Optional `dataset` keeps countries that appear in that fact table.

### `GET /api/v1/prices`

**Sample:** [https://api.datanexusai.org/api/v1/prices?dataset=cereals_prices&country=BG&date_from=2024-01-01&limit=20](https://api.datanexusai.org/api/v1/prices?dataset=cereals_prices&country=BG&date_from=2024-01-01&limit=20)

Union of `agrifood_cereals_price`, `agrifood_oilseeds_price`, `agrifood_pigmeat_price`.

| Param | Maps to |
| --- | --- |
| `dataset` | `dataset` |
| `commodity` / `product` | `product_name` / `pig_class` |
| `country` | `member_state_code` or `member_state_name` |
| `market` | `market_name` |
| `date` | `begin_date` |
| `date_from` / `date_to` | `begin_date` range |
| `period` | `week_number` or `marketing_year` |
| `unit` | `unit` |
| `stage` | `stage_name` / `market_stage` |
| `product_type` | `product_type` |
| `pig_class` | `pig_class` |

### `GET /api/v1/commodities/{id}/prices`

**Sample:** [https://api.datanexusai.org/api/v1/commodities/cereals_prices:Milling%20rye/prices?country=AT&limit=20](https://api.datanexusai.org/api/v1/commodities/cereals_prices:Milling%20rye/prices?country=AT&limit=20)

Same filters, restricted to one commodity id. Ordered by `begin_date` descending.

### `GET /api/v1/commodities/{id}/history`

**Sample:** [https://api.datanexusai.org/api/v1/commodities/cereals_prices:Milling%20rye/history?country=AT&limit=20](https://api.datanexusai.org/api/v1/commodities/cereals_prices:Milling%20rye/history?country=AT&limit=20)

Time series for that commodity (`begin_date` ascending), same filters.

### `GET /api/v1/changes`

**Sample:** [https://api.datanexusai.org/api/v1/changes?country=BG&commodity=1001&date_from=2020&date_to=2025&metric=imports](https://api.datanexusai.org/api/v1/changes?country=BG&commodity=1001&date_from=2020&date_to=2025&metric=imports)

Period-to-period changes for `price`, `imports`, `exports`, `trade_balance`. Filters: `country`, `commodity`, `date_from`, `date_to`, `metric`. Each row has `previous_value`, `current_value`, `absolute_change`, `percentage_change`, `period`. Missing values are skipped (not filled with 0).

Weekly Agri-food price LAG remains available as [https://api.datanexusai.org/api/v1/prices/changes?dataset=pigmeat_prices&country=BG&limit=20](https://api.datanexusai.org/api/v1/prices/changes?dataset=pigmeat_prices&country=BG&limit=20) (also if `dataset=` is passed to `/api/v1/changes`).

### `GET /api/v1/anomalies`

**Sample:** [https://api.datanexusai.org/api/v1/anomalies?min_percent=40&limit=20](https://api.datanexusai.org/api/v1/anomalies?min_percent=40&limit=20)

Rows with `|percent_change| >= min_percent` (default 25) or `|zscore| >= z` (default 3) within the same series.

### `GET /api/v1/latest`

**Sample:** [https://api.datanexusai.org/api/v1/latest?dataset=oilseeds_prices&limit=20](https://api.datanexusai.org/api/v1/latest?dataset=oilseeds_prices&limit=20)

Newest `begin_date` per series.

```http
GET /api/v1/datasets
GET /api/v1/prices?dataset=cereals_prices&country=BG&date_from=2024-01-01&limit=20
GET /api/v1/commodities/cereals_prices:Milling%20rye/history?country=AT
GET /api/v1/changes?country=BG&commodity=1001&metric=imports
GET /api/v1/prices/changes?dataset=pigmeat_prices&country=BG
GET /api/v1/anomalies?min_percent=40
GET /api/v1/latest?dataset=oilseeds_prices
```

```bash
curl "https://api.datanexusai.org/api/v1/datasets"
curl "https://api.datanexusai.org/api/v1/prices?country=BG&limit=5"
```

---

## Trade and agricultural markets

JSON over `eurostat_comext_trade` plus Agri-food cereal prices. No production series is loaded, so `production` is omitted. Comext partner in the current extract is `WORLD` (world total). Flow `1`/`2` are CXT_EU_FLUX IMPORT/EXPORT. Product codes `1001`/`1003`/`1005` are wheat / barley / maize from the crawled DS-045409 labels.

Pagination: `limit` (default 100, max 500), `offset`.

### `GET /api/v1/trade`

**Sample:** [https://api.datanexusai.org/api/v1/trade?reporter=BG&product=1001&date_from=2020](https://api.datanexusai.org/api/v1/trade?reporter=BG&product=1001&date_from=2020)

Filters: `reporter`/`country`, `partner`, `product`/`commodity`, `flow` (`1`,`2`,`import`,`export`), `date_from`, `date_to`. Rows pivot `VALUE_IN_EUROS` → `value` and `QUANTITY_IN_100KG` → `quantity`.

### `GET /api/v1/trade/balance`

**Sample:** [https://api.datanexusai.org/api/v1/trade/balance?country=BG&commodity=1001](https://api.datanexusai.org/api/v1/trade/balance?country=BG&commodity=1001)

Requires `reporter` and `product`. `trade_balance = exports - imports` on `VALUE_IN_EUROS`.

### `GET /api/v1/trade/partners`

**Sample:** [https://api.datanexusai.org/api/v1/trade/partners?reporter=BG&product=1001&period=2025](https://api.datanexusai.org/api/v1/trade/partners?reporter=BG&product=1001&period=2025)

Requires `reporter` and `product`. Optional `flow` (`import`/`export`/`both`, default `both`), `period`, `date_from`, `date_to`, `limit` (default 20), `sort` (`value`/`quantity`/`share`). `WORLD` and other aggregate partner codes are excluded from the ranking; WORLD totals are used as share denominators and reported in `data_quality`.

### `GET /api/v1/markets/{country}/{commodity}`

**Sample:** [https://api.datanexusai.org/api/v1/markets/BG/1001](https://api.datanexusai.org/api/v1/markets/BG/1001)

Combines Agri-food average annual price with Comext trade (`imports`, `exports`, `trade_balance = exports - imports`). Missing values stay `null`. Optional: `year`, `date_from`, `date_to`, `partner` (default `WORLD`), `flow`.

### `GET /api/v1/markets/{country}/{commodity}/history`

**Sample:** [https://api.datanexusai.org/api/v1/markets/BG/1001/history?date_from=2020&date_to=2025](https://api.datanexusai.org/api/v1/markets/BG/1001/history?date_from=2020&date_to=2025)

Annual series from Agri-food prices + Comext trade: `period`, `price`, `imports`, `exports`, `trade_balance`. Optional `date_from`, `date_to`. Missing indicators are `null`.

### `GET /api/v1/markets/{country}/{commodity}/insights`

**Sample:** [https://api.datanexusai.org/api/v1/markets/BG/1001/insights](https://api.datanexusai.org/api/v1/markets/BG/1001/insights)

YoY percent changes and z-score anomalies (`z` default 3). Deterministic SQL/Python, no LLM.

### `GET /api/v1/markets/changes`

**Sample:** [https://api.datanexusai.org/api/v1/markets/changes?countries=BG&commodities=1001&min_percent=10](https://api.datanexusai.org/api/v1/markets/changes?countries=BG&commodities=1001&min_percent=10)

YoY moves with `|percent| >= min_percent` (default 10). Optional `countries`, `commodities`.

### `GET /api/v1/markets/anomalies`

**Sample:** [https://api.datanexusai.org/api/v1/markets/anomalies?countries=BG&commodities=1001](https://api.datanexusai.org/api/v1/markets/anomalies?countries=BG&commodities=1001)

`|yoy| >= min_percent` (default 40) or `|zscore| >= z` on price/imports.

### `GET /api/v1/markets/compare`

**Sample:** [https://api.datanexusai.org/api/v1/markets/compare?countries=BG,RO,GR,DE&commodity=1001](https://api.datanexusai.org/api/v1/markets/compare?countries=BG,RO,GR,DE&commodity=1001)

Compare 2+ countries for one commodity and one period. Filters: `countries`, `commodity`, `date_from`, `date_to`. Each country: `price`, `imports`, `exports`, `trade_balance`, `price_change`, `imports_change`, `exports_change`. Missing values are `null`. Without dates, uses the latest common period.

```http
GET /api/v1/trade?reporter=BG&product=1001&date_from=2020
GET /api/v1/trade/balance?country=BG&commodity=1001
GET /api/v1/markets/BG/1001
GET /api/v1/markets/BG/1001/history
GET /api/v1/markets/compare?countries=BG,RO,GR,DE&commodity=1001
```

---

## Macro & rates (derived — Eurostat + ECB + selected FRED runtime)

These endpoints expose **derived analytics**, not raw series dumps.

- Eurostat/ECB: read from ingested `economy_*` tables.
- FRED: **runtime API request → calculation → derived JSON only** (no observation mirror).
  Requires per-series `license_status=APPROVED`. Not raw FRED proxies.

### FRED-backed derived analytics (x402)

| Endpoint | Derived output |
| --- | --- |
| `GET /api/analytics/us/yield-curve` | `US_10Y_2Y_SPREAD` = DGS10 − DGS2 |
| `GET /api/analytics/us-eu/inflation` | US CPI YoY − EU HICP YoY |
| `GET /api/analytics/us/macro` | inflation/UE/rate changes + macro regime |
| `GET /api/analytics/market/volatility-regime` | VIX regime / percentile / z-score |

```http
GET /api/analytics/us/yield-curve
GET /api/analytics/us-eu/inflation
GET /api/analytics/us/macro
GET /api/analytics/market/volatility-regime?lookback_days=252
```

> This product uses the FRED® API but is not endorsed or certified by the
> Federal Reserve Bank of St. Louis.

### `GET /api/v1/macro/eu-inflation-divergence`

**Sample:** [https://api.datanexusai.org/api/v1/macro/eu-inflation-divergence](https://api.datanexusai.org/api/v1/macro/eu-inflation-divergence)

Member-state HICP YoY vs EA20/EU27 benchmark + MoM from HICP index. Ranked by absolute gap.

| Param | Notes |
| --- | --- |
| `period` | Optional (e.g. `2024-08`); default = latest |

### `GET /api/v1/rates/ecb-stance`

**Sample:** [https://api.datanexusai.org/api/v1/rates/ecb-stance](https://api.datanexusai.org/api/v1/rates/ecb-stance)

DFR / MRO / MLFR corridor + €STR−DFR spread + liquidity stance label.

### `GET /api/v1/rates/eu-curve-slope`

**Sample:** [https://api.datanexusai.org/api/v1/rates/eu-curve-slope](https://api.datanexusai.org/api/v1/rates/eu-curve-slope)

Euro-area AAA curve levels + `10Y−2Y` / `30Y−10Y` slopes, butterfly, steep/flat/inverted regime.

### `GET /api/v1/fx/eurusd-analytics`

**Sample:** [https://api.datanexusai.org/api/v1/fx/eurusd-analytics?lookback_days=252](https://api.datanexusai.org/api/v1/fx/eurusd-analytics?lookback_days=252)

EURUSD multi-horizon returns, annualized vol, percentile, z-score, max drawdown.

| Param | Notes |
| --- | --- |
| `lookback_days` | History window (default 252, max 2000) |

```http
GET /api/v1/macro/eu-inflation-divergence
GET /api/v1/rates/ecb-stance
GET /api/v1/rates/eu-curve-slope
GET /api/v1/fx/eurusd-analytics?lookback_days=252
```
