# InvesTax Partner API

**Доступ к API:** Для получения API-ключа обратитесь к администрации [Sber-Invest](https://sber-invest.kz/).

Документация партнёрского API для интеграции с InvesTax.

**Base URL:** `https://investax.taxexpress.kz`

## Минимальный workflow: JSON in → report out → declaration out

1. **JSON in:** `POST /api/loadjson` — загрузите Standard JSON для реального `broker` + стабильного `account_id`.
2. **Report out:** `POST /api/genreport` — получите `report_id` и `download_url` XLSX.
3. **Только если HTTP 202:** выполните `GET` по возвращённому `status_url` (`/api/jobs/<job_id>`) до `status=finished`.
4. **Declaration out:** `POST /api/declarations/250` или `POST /api/declarations/270` с готовым `report_id`; используйте `download_url` из ответа.

**Другие способы загрузки** (CSV/XLSX, PDF, TraderNet) остаются доступны ниже, но Standard JSON — наиболее прямой normalized integration path.

## Аутентификация

Передавайте API-ключ в каждом запросе:

```
X-API-Key: YOUR_API_KEY
```

## Standard JSON

Для системной интеграции используйте `POST /api/loadjson`. Один payload относится к одному `broker` + `account_id` + `report_end_date` и может содержать любую комбинацию поддерживаемых секций.

**Минимальный пример:**

```json
{
  "schema_version": 1,
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main",
  "report_end_date": "2025-12-31",
  "broker_info": {
    "name": "INTERACTIVE BROKERS LLC",
    "swift": "IBKRUS33",
    "iso_code": "US"
  },
  "trades": [
    {
      "date": "2025-01-10T10:00:00",
      "type": "bought",
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 10,
      "price": 185.25,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security"
    },
    {
      "date": "2025-06-20T10:00:00",
      "type": "sold",
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 4,
      "price": 210.0,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security"
    }
  ],
  "cash": [
    {
      "currency": "USD",
      "amount": 1234.56
    }
  ],
  "assets": [
    {
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 6,
      "price": 185.25,
      "currency": "USD",
      "asset_cat": "security"
    }
  ]
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/loadjson' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"schema_version":1,"user_id":"user@example.com","broker":"IB","account_id":"main","report_end_date":"2025-12-31","broker_info":{"name":"INTERACTIVE BROKERS LLC","swift":"IBKRUS33","iso_code":"US"},"trades":[{"date":"2025-01-10T10:00:00","type":"bought","ticker":"AAPL","isin":"US0378331005","amount":10,"price":185.25,"currency":"USD","place":"NASDAQ","asset_cat":"security"},{"date":"2025-06-20T10:00:00","type":"sold","ticker":"AAPL","isin":"US0378331005","amount":4,"price":210.0,"currency":"USD","place":"NASDAQ","asset_cat":"security"}],"cash":[{"currency":"USD","amount":1234.56}],"assets":[{"ticker":"AAPL","isin":"US0378331005","amount":6,"price":185.25,"currency":"USD","asset_cat":"security"}]}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/loadjson'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "schema_version": 1,
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main",
  "report_end_date": "2025-12-31",
  "broker_info": {
    "name": "INTERACTIVE BROKERS LLC",
    "swift": "IBKRUS33",
    "iso_code": "US"
  },
  "trades": [
    {
      "date": "2025-01-10T10:00:00",
      "type": "bought",
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 10,
      "price": 185.25,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security"
    },
    {
      "date": "2025-06-20T10:00:00",
      "type": "sold",
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 4,
      "price": 210.0,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security"
    }
  ],
  "cash": [
    {
      "currency": "USD",
      "amount": 1234.56
    }
  ],
  "assets": [
    {
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 6,
      "price": 185.25,
      "currency": "USD",
      "asset_cat": "security"
    }
  ]
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

### Верхний уровень

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `schema_version` | нет | `integer` | Версия контракта. Сейчас 1; если поле опущено, используется 1. |
| `user_id` | да | `string` | Стабильный идентификатор пользователя в системе партнёра. |
| `client_id` | нет | `string` | Налогоплательщик внутри user_id. Для external API по умолчанию "1". |
| `name_first / name_last` | нет | `string` | Используются при автоматическом создании default client; при отсутствии применяется NA. |
| `broker` | да | `string` | Реальный writable machine code брокера из справочника. all/all_ru здесь запрещены. |
| `account_id` | да | `string` | Стабильный идентификатор брокерского счёта. |
| `report_end_date` | да | `ISO date/datetime` | Дата среза отчёта, например 2025-12-31. Определяет tax_year. |
| `is_d_account` | нет | `boolean \| "d"` | Признак D-account. По умолчанию обычный счёт. |
| `broker_info` | нет | `object` | Опциональные реквизиты учреждения для конкретного broker + account_id: name, swift, iso_code. Не дублируются в cash. |
| `trades / dividends / interest / cash / assets / transfers / corporate_actions` | ≥ 1 секция | `array` | Передавайте только имеющиеся данные. Опущенные секции не изменяются. |

### `broker_info` — реквизиты банка/брокера

`broker_info` — опциональный top-level object для реквизитов конкретного `broker + account_id`. Эти значения не нужно повторять в каждой строке `cash`.

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `name` | нет | `string ≤ 500` | Официальное название банка/брокера для декларации. |
| `swift` | нет | `SWIFT/BIC, 8 или 11 символов` | SWIFT/BIC учреждения. Нормализуется в верхний регистр; пустое значение/Unknown считается отсутствующим. |
| `iso_code` | нет | `ISO-2 country code` | Двухбуквенный код страны, например US, TR, GB. Нормализуется в верхний регистр. |

- broker_info относится к конкретному broker + account_id. Если объект передан, в нём должен быть хотя бы один непустой реквизит.
- Частичное обновление не стирает остальные сохранённые реквизиты. При формировании декларации используется приоритет: account-specific override → настроенный справочник брокеров → Unknown.
- Не повторяйте name/swift/iso_code в каждой строке cash: денежные остатки остаются массивом currency + amount.
- SWIFT является метаданными учреждения, но конкретное поле и тип в итоговом ФНО зависят от версии формы. В ФНО 270 и legacy ФНО 250 схема допускает строковый SWIFT; для новых версий ФНО 250 Investax следует типам, которые принимает кабинет.

### Секции Standard JSON

#### `trades` — Сделки

Покупки/продажи ценных бумаг и ПФИ. Для новых интеграций используйте canonical type=bought|sold.

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `date` | да | `ISO datetime/date` | Дата операции без timezone offset. |
| `type` | да | `bought \| sold` | buy/sell принимаются как aliases, но canonical значения предпочтительны. |
| `isin` | да | `string ≤ 16` | Начинается с двух латинских букв. Не придумывайте отсутствующий ISIN. |
| `ticker` | нет | `string` | Если не передан, используется isin. |
| `amount` | да | `number > 0` | Количество. |
| `price` | да | `number` | Цена из исходного отчёта; при multiplier Investax использует price × multiplier. |
| `currency` | да | `currency code` | Например USD, KZT, EUR. |
| `place` | нет | `string` | Биржа/рынок; default NotFound. |
| `fee_broker / fee_exchange` | нет | `number` | Комиссии; default 0. |
| `fee_broker_currency` | нет | `currency code` | По умолчанию currency сделки. |
| `shorts` | нет | `boolean` | Признак short; default false. |
| `asset_cat` | нет | `enum` | Категория инструмента; default security, но производные нужно классифицировать явно. |
| `multiplier` | нет | `number > 0` | Контрактный multiplier. Рабочая цена нормализуется как price × multiplier. |
| `underlying` | нет | `string` | Underlying ticker/code для опциона/дериватива. |
| `settlement_type` | нет | `string` | Например open, close, exercise, assignment, delivery, expiration. |
| `option_type` | нет | `C/call \| P/put` | Тип опциона. |
| `option_strike` | нет | `number` | Strike. |
| `option_expiry` | нет | `ISO date` | Дата expiry. |
| `date_calc` | нет | `ISO datetime/date` | Дополнительная расчётная дата, если она есть в source data. |
| `original_code / note / cost` | нет | `mixed` | Дополнительные исходные поля. |

#### `dividends` — Дивиденды и купоны

Доходы по ценным бумагам. Canonical dividend_type: d для dividend, c для coupon.

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `date` | да | `ISO date/datetime` | Дата дохода. |
| `isin` | да | `string ≤ 16` | ISIN/instrument id. |
| `ticker` | нет | `string` | Ticker; fallback к isin. |
| `income` | да | `number` | Сумма дохода в currency. |
| `currency` | да | `currency code` | Валюта дохода. |
| `tax_brok` | нет | `number` | Удержанный налог; сохраняется как абсолютное значение. |
| `tax_brok_currency` | нет | `currency code` | По умолчанию currency. |
| `dividend_type` | нет | `d \| c` | Рекомендуется d для dividend и c для coupon. |
| `note / note_tax / place` | нет | `string` | Дополнительные исходные поля. |

#### `interest` — Проценты по счёту

Процентный доход, который не является dividend/coupon security event.

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `date` | да | `ISO date/datetime` | Дата дохода. |
| `currency` | да | `currency code` | Валюта. |
| `income` | да | `number` | Сумма. Alias amount также принимается. |
| `description` | нет | `string` | Описание; alias comment также принимается. |
| `interest_type` | нет | `string` | Например cash_interest; default other_interest. |

#### `cash` — Денежные остатки

Cash snapshot на report_end_date.

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `currency` | да | `currency code` | Валюта остатка. |
| `amount` | да | `number` | Сумма остатка. |

#### `assets` — Остатки ценных бумаг / инструментов

Remaining-assets snapshot на report_end_date; используется в проверке FIFO/деклараций.

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `isin` | да | `string ≤ 16` | ISIN/instrument id. |
| `ticker` | нет | `string` | Ticker; fallback к isin. |
| `amount` | да | `number` | Количество на report_end_date. |
| `currency` | да | `currency code` | Валюта инструмента. |
| `asset_cat` | нет | `enum` | Категория; default security. |
| `price` | нет | `number` | Snapshot/basis price, если известна. |

#### `transfers` — Переводы ценных бумаг

Входящие/исходящие transfers. Для однозначности рекомендуется explicit transfer_dir и стабильный id.

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `id` | рекомендуется | `string` | Стабильный source transaction id. |
| `date` | да | `ISO date/datetime` | Операционная дата transfer. |
| `isin` | да | `string ≤ 16` | ISIN/instrument id. |
| `ticker` | нет | `string` | Ticker; fallback к isin. |
| `amount` | да | `number ≠ 0` | Рекомендуется положительное количество вместе с transfer_dir. |
| `transfer_dir` | рекомендуется | `in \| out` | Направление. Alias direction также принимается. |
| `currency` | нет | `currency code` | Валюта инструмента/учёта, если известна. |
| `note` | нет | `string` | Комментарий. |
| `reverted` | нет | `boolean` | Признак отменённого transfer; default false. |

#### `corporate_actions` — Корпоративные действия

Сейчас Standard JSON поддерживает stock split/reverse split.

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `type` | да | `split` | В настоящее время поддерживается только split. |
| `date` | да | `ISO date/datetime` | Дата split. Alias action_date также принимается. |
| `isin` | да | `string ≤ 16` | ISIN/instrument id. |
| `ticker` | нет | `string` | Ticker; fallback к isin. |
| `ratio_num` | да | `positive integer` | Числитель NEW/OLD. |
| `ratio_den` | да | `positive integer` | Знаменатель NEW/OLD. |
| `ratio_text` | нет | `string` | Только display/audit text; при отсутствии строится как ratio_num:ratio_den. |
| `comment` | нет | `string` | Комментарий. |

## asset_cat: коротко

| asset_cat | Налоговая роль | Примеры |
|---|---|---|
| `security` | Сальдируемые сделки с ценными бумагами. | акции, ETF, облигации и другие обычные securities |
| `security_option` | Standalone — ПФИ/несальдируемая; physical exercise/assignment может перейти в basis underlying security. | опцион на акцию/ETF |
| `future` | Несальдируемый ПФИ. | биржевой futures contract |
| `nonsecurity_option` | Несальдируемый ПФИ. | index/commodity option, не являющийся security option |
| `nonsecurity_derivative` | Несальдируемый ПФИ. | CFD/forward/иной derivative, не являющийся security |

### asset_cat: подробнее

- **`security`** — Обычная категория ценной бумаги. Реализованные сделки участвуют в стандартном FIFO и секции сальдируемых операций.
- **`security_option`** — При обычном close/expiration остаётся отдельным ПФИ. При подтверждённой физической поставке Investax связывает option leg с underlying security и переносит option premium в basis underlying вместо отдельной PFI строки.
- **`future`** — FIFO рассчитывается отдельно, но результат относится к несальдируемым сделкам с ПФИ.
- **`nonsecurity_option`** — Не сальдируется с обычными security trades; учитывается как самостоятельный ПФИ.
- **`nonsecurity_derivative`** — Используется для производных инструментов, которые не относятся к security_option или future.

### Physical security option delivery

Standalone `security_option` остаётся ПФИ. Для физического exercise/assignment передавайте canonical option metadata (`underlying`, `option_type`, `option_strike`, `option_expiry`, `multiplier`, `settlement_type`) и фактическую underlying security trade. Investax сам выводит delivery link; поля `underlying_delivered` и `linked_underlying_trade_id` передавать запрещено.

Для автоматического matching option event и underlying trade должны быть согласованы по timestamp, underlying ticker, currency, strike и delivered quantity (`contracts × multiplier`). После успешного matching option premium переносится в basis underlying security.

**Пример long call exercise:**

```json
{
  "trades": [
    {
      "date": "2025-11-01T15:00:00",
      "type": "bought",
      "ticker": "OPTABC",
      "isin": "USOPTABC000001",
      "amount": 1,
      "price": 2,
      "currency": "USD",
      "place": "CBOE",
      "asset_cat": "security_option",
      "multiplier": 100,
      "underlying": "ABC",
      "option_type": "C",
      "option_strike": 50,
      "option_expiry": "2025-11-21",
      "settlement_type": "open"
    },
    {
      "date": "2025-11-20T15:30:00",
      "type": "sold",
      "ticker": "OPTABC",
      "isin": "USOPTABC000001",
      "amount": 1,
      "price": 0,
      "currency": "USD",
      "place": "CBOE",
      "asset_cat": "security_option",
      "multiplier": 100,
      "underlying": "ABC",
      "option_type": "C",
      "option_strike": 50,
      "option_expiry": "2025-11-21",
      "settlement_type": "exercise"
    },
    {
      "date": "2025-11-20T15:30:00",
      "type": "bought",
      "ticker": "ABC",
      "isin": "USABC000000001",
      "amount": 100,
      "price": 50,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security",
      "settlement_type": "exercise"
    }
  ]
}
```

В этом примере premium 2 × 100 = 200 включается в acquisition basis 100 underlying shares: basis на акцию становится 52 вместо strike 50.

### Stock split / reverse split

`ratio_num / ratio_den = NEW shares per OLD share`. Используйте только `ratio_num` и `ratio_den`, не `ratio_from`/`ratio_to`.

**Обычный split 2-for-1** — ratio_num / ratio_den = 2 / 1: каждая 1 старая акция становится 2 новыми.

```json
{
  "corporate_actions": [
    {
      "type": "split",
      "date": "2025-02-01",
      "ticker": "ABC",
      "isin": "US0000000001",
      "ratio_num": 2,
      "ratio_den": 1
    }
  ]
}
```

**Reverse split 1-for-10** — ratio_num / ratio_den = 1 / 10: каждые 10 старых акций становятся 1 новой.

```json
{
  "corporate_actions": [
    {
      "type": "split",
      "date": "2025-09-01",
      "ticker": "XYZ",
      "isin": "US0000000002",
      "ratio_num": 1,
      "ratio_den": 10
    }
  ]
}
```

## Данные для ФНО 250 / 270

Объект `person` используется обеими декларациями. Поля со звёздочкой (`да*`) API оставляет backward-compatible, но их следует передавать для готовой к загрузке в кабинет декларации.

**Пример:**

```json
{
  "person": {
    "fio1": "SARSENBAYEV",
    "fio2": "AYAN",
    "fio3": "",
    "iin": "XXXXXXXXXXXX",
    "declaration_type": "dt_regular",
    "sra_code": "6001",
    "phone": "+77000000000",
    "email": "ayan@example.com"
  }
}
```

| Поле | Формы | Обязательно | Тип | Описание |
|---|---|---|---|---|
| `fio1` | 250 / 270 | да* | `string` | Фамилия. На странице декларации поле обязательное. |
| `fio2` | 250 / 270 | да* | `string` | Имя. На странице декларации поле обязательное. |
| `fio3` | 250 / 270 | нет | `string` | Отчество, если имеется. |
| `iin` | 250 / 270 | да* | `string` | ИИН налогоплательщика. Для готовой к загрузке декларации передавайте 12 цифр. |
| `declaration_type` | 250 / 270 | нет | `enum` | dt_main \| dt_regular \| dt_additional \| dt_notice. Default: dt_regular. |
| `agreement` | 250 / 270 | нет | `boolean-like` | Согласие/раскрытие информации. Default: false. |
| `sra_code` | 250 / 270 | да* | `string` | Код ОГД по месту жительства. Записывается в ogdCodeByResidence. |
| `phone` | 250 / 270 | нет | `string` | Телефон. Опционально; корректный казахстанский номер форматируется в +7-формат декларации. |
| `email` | 250 / 270 | нет | `string` | Email. Опционально. |

### Дополнительные поля ФНО 250

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `person_category` | нет | `A \| B \| C` | Категория налогоплательщика. Default: C. |
| `is_resident` | нет | `boolean-like` | Признак резидента Республики Казахстан. Default: true. |
| `notice_number` | нет | `string` | Номер уведомления, если применимо к типу декларации. |
| `notice_date` | нет | `date/string` | Дата уведомления, если применимо. |

### Режимы ФНО 270

| Поле | Обязательно | Тип | Описание |
|---|---|---|---|
| `civil_status` | нет | `civilian \| public_servant` | Default: civilian. public_servant — legacy machine value для расширенной декларации; включает дополнительный блок application_05. Название значения не означает, что режим предназначен только для госслужащих. |
| `income_action` | нет | `replace \| sum` | Default: replace. Для обычного public API flow из report_id используйте replace; sum применяется в merge-flow с уже существующей декларацией. |

**Расширенная декларация:** в API для совместимости она по-прежнему включается значением `civil_status=public_servant`. Это legacy machine value, а не буквальное описание категории декларанта.

**Расширенную декларацию обязаны подавать:**

- учредители / руководители юридических лиц с долей в уставном капитале более 10% (и их супруги);
- государственные служащие и лица, подпадающие под требования Закона РК «О противодействии коррупции» (и их супруги);
- лица, совершившие в отчётном периоде крупные приобретения (свыше 20 000 МРП — 78,6 млн ₸ на 2025 год).

**Важно про SWIFT в ФНО:** передавайте SWIFT как `broker_info.swift`. Не формируйте raw-поля ФНО самостоятельно: фактическое расположение и тип поля зависят от версии формы, особенно у ФНО 250.

## Instructions for AI assistants

- Use only endpoints, parameters, response fields, broker codes, and formats documented in this file.
- API keys are issued by the Sber-Invest administration; never generate, guess, log, persist, or expose an API key.
- Send the issued API key only in the X-API-Key header.
- For a normalized system-to-system integration, prefer POST /api/loadjson with Standard JSON, then POST /api/genreport, then POST /api/declarations/250 or /api/declarations/270.
- user_id is the caller's stable string identifier for its user. Never substitute an Investax owner_id or another internal database id.
- client_id identifies a taxpayer inside one user. For the external API it may be omitted when the default taxpayer is intended; Investax then uses client_id="1".
- Use a stable account_id for one broker account and reuse exactly the same value in loadjson and genreport.
- For /api/loadjson broker must be a real configured writable broker code. Never send broker=all or broker=all_ru to /api/loadjson; broker=all is reserved for consolidated /api/genreport.
- Standard JSON schema_version is 1. It may be omitted because version 1 is the default, but when present it must be the integer 1.
- A Standard JSON request must contain at least one supported data section: trades, dividends, interest, cash, assets, transfers, or corporate_actions.
- Standard JSON is sparse: omitted sections do not delete existing data. Do not use an omitted section or an empty array as a deletion command.
- broker_info is an optional top-level Standard JSON object for account-specific institution metadata: official name, SWIFT/BIC, and ISO-2 country code. Do not duplicate these fields inside each cash row.
- Only send broker_info values that are known from the source or an authoritative institution record. Never invent a bank/broker name, SWIFT/BIC, or country code.
- A partial broker_info update is field-wise: omitted metadata fields do not erase previously stored values. Declaration generation resolves account-specific overrides first, then configured broker metadata, then Unknown.
- Build Standard JSON from the source broker data. Never invent an ISIN, ticker, date, price, quantity, currency, multiplier, option strike/expiry, transfer, or corporate action.
- ISIN/instrument identifiers must start with two Latin letters and must not exceed 16 characters. Never guess a missing ISIN; use the documented ISIN correction flow when needed.
- Use timezone-naive ISO dates/datetimes, for example 2025-12-31 or 2025-03-12T14:30:00. Do not add Z or timezone offsets.
- For new Standard JSON, prefer canonical trade type values bought and sold. Aliases such as buy/sell are accepted but should not be generated when canonical values are known.
- For dividends, prefer dividend_type="d" for ordinary dividends and dividend_type="c" for coupons.
- Choose asset_cat from the documented categories. Do not label a derivative as security merely because security is the default.
- For multiplier-based contracts, send the broker/raw price in price and the positive contract multiplier in multiplier. Investax normalizes the working trade price as price × multiplier.
- For options, provide underlying, option_type, option_strike, option_expiry, multiplier, and settlement_type whenever the source data contains them.
- Never send underlying_delivered or linked_underlying_trade_id in Standard JSON. They are internal derived fields and the API rejects caller-supplied values.
- For a physically exercised/assigned security_option, send the option event and resulting underlying security trade with canonical metadata. Investax derives the delivery link and, when matched, folds the option premium into the underlying security acquisition/disposal basis.
- For stock splits, use corporate_actions with type="split", ratio_num and ratio_den. ratio_num / ratio_den means NEW shares per OLD share: 2/1 is a 2-for-1 split; 1/10 is a 1-for-10 reverse split. Do not generate ratio_from or ratio_to.
- For transfers, prefer a stable id, a positive amount, and explicit transfer_dir="in" or "out". date is the operational transfer date.
- When producing an initial complete year import, include all available relevant sections, especially year-end cash/assets and corporate actions; later calls may update only the sections being corrected.
- Treat endpoint names as identifiers, not as file-format restrictions. In particular, /api/loadcsv is a historical name and may accept XLSX for brokers documented as Excel-capable.
- Never rename multipart fields. For /api/loadcsv use csv_trans and csv_divs exactly as documented even when the uploaded file is XLSX.
- When /api/tradernet/check_sms returns need_account_selection=true, display the returned accounts and pass the selected accounts[n].account_id unchanged as account_id to /api/tradernet/select_account. Do not derive or parse another identifier.
- Before uploading broker files, check the broker table and endpoint notes. Do not infer a supported format from the filename, endpoint name, or broker display name.
- For HTTP 202 responses, follow the returned status_url until status=finished or failed. Do not repeatedly resubmit the original operation while its job is running.
- When the API returns download_url or status_url, use that URL instead of reconstructing it manually.
- For declaration person data, sra_code is the OGD code by residence and is written to ogdCodeByResidence. Provide it for a ready-to-submit declaration; phone and email are optional and must not be invented.
- For Form 270, civil_status=public_servant is a legacy machine value that enables the expanded declaration block (application_05). Do not interpret the value literally as meaning that only public servants use this mode; use civil_status=civilian for the ordinary flow.
- Do not infer raw FNO field types from broker_info. Investax serializes SWIFT/bank metadata according to the target FNO schema; Form 250 field layout/type differs by FNO year.
- On HTTP 422 with error=missing_isin, use the documented ISIN correction flow. Never invent or guess an ISIN.
- For a consolidated report use broker=all and omit account_id as documented. Do not build a substitute consolidated tax calculation client-side.
- Do not infer the meaning of report datasets from df_* names alone. Use the Report JSON schema section in this document.
- df_aggr contains FIFO-matched realized transactions used in the tax calculation; it is not the original list of broker trades.
- df_b_yr contains purchases for the report period and df_s_yr contains sales for the report period.
- Fields named income_usd or ending in _usd are legacy names in several report datasets. They may represent income in the transaction/original currency, not necessarily USD. Follow each field description.
- Fields ending in _kzt are KZT-denominated unless the field description explicitly says otherwise.
- Date/time values are returned as JSON strings, but source formatting varies by dataset. Follow each field description and parse explicitly instead of assuming one universal date format.
- Treat the fields marked core in the Report JSON schema as the integration contract. Additional fields may appear; ignore unknown fields unless they are explicitly needed.
- In df_country, df_countrydiv, and df_countrycoup, income_usd is set to 0 when a country group contains mixed currencies. Use income_kzt for cross-currency aggregation.
- df_mbought contains remaining FIFO acquisition lots, not live market valuation and not a substitute for broker portfolio data.
- For transfers, dt is the operational transfer date. date_orig is the historical acquisition date when known. Do not substitute date_orig for dt in FIFO flow.
- A non-zero leftover_cnt means FIFO could not fully match disposal quantity to acquisition lots. Surface this condition; do not silently treat the report as complete.
- df_countrycoup currently represents the coupon/reward country section and also includes normalized interest income in that section.
- Keep df_aggr_Dacc logically separate from ordinary df_aggr unless the documented tax/report flow explicitly combines summary values.
- Preserve documented request field names and enum/code values exactly. Do not translate machine values such as broker codes, transfer_dir, asset_cat, settlement_type, or account_id.

## Идентификаторы

### `user_id`

Строковый стабильный идентификатор пользователя. Не используйте внутренний числовой id. Рекомендуемый вариант: уникальный email, например `user@example.com`.

### `client_id`

Строковый идентификатор конкретного налогоплательщика внутри пользователя. Например, один пользователь может иметь `client_id=self` и `client_id=spouse`. Если отдельный `client_id` не нужен, API использует default `client_id="1"` в поддерживающих это flows.

## Коды брокеров

В поле `broker` передавайте код из первой колонки.

| Код API | Брокер | Standard JSON | Файл через /api/loadcsv | PDF | SMS | Ручной ввод |
|---|---|:---:|:---:|:---:|:---:|:---:|
| `Jusan` | Alatau City Invest | ✓ | ✓ | — | — | ✓ |
| `bcc` | BCC Bank | ✓ | — | ✓ | — | ✓ |
| `bcc_invest` | BCC Invest | ✓ | ✓ | ✓ | — | ✓ |
| `Binance` | Binance | ✓ | — | — | — | — |
| `Binance_KZ` | Binance KZ | ✓ | — | — | — | — |
| `Schwab` | Charles Schwab | ✓ | ✓ | — | — | ✓ |
| `Exante` | Exante | ✓ | ✓ | — | — | ✓ |
| `FreedGlobal` | FF Global PLC | ✓ | ✓ | ✓ | ✓ | ✓ |
| `Freed_Bank` | FF-Bank (Фридом Валюта) | ✓ | — | ✓ | — | ✓ |
| `FINN` | FFIN «Белиз» | ✓ | ✓ | ✓ | ✓ | ✓ |
| `FINN2` | FFIN «Белиз» v2 | ✓ | — | ✓ | — | — |
| `FFIN3` | FFIN «Белиз» v3 | ✓ | — | ✓ | — | — |
| `FFIN4` | FFIN «Белиз» v4 | ✓ | — | ✓ | — | — |
| `finam` | finam | ✓ | ✓ | — | — | ✓ |
| `halyk` | Halyk Bank | ✓ | ✓ | — | — | ✓ |
| `halyk_fin` | Halyk Finance | ✓ | ✓ | ✓ | — | ✓ |
| `IB2` | IB_v2 | ✓ | — | ✓ | — | — |
| `IB` | IBKR | ✓ | ✓ | ✓ | — | ✓ |
| `IB_old` | IBKR_old | ✓ | — | — | — | — |
| `investlink` | InvestLink | ✓ | — | ✓ | — | ✓ |
| `Just2Trade` | Just2Trade | ✓ | ✓ | — | — | ✓ |
| `n1` | N1 Broker | ✓ | ✓ | — | — | ✓ |
| `ninjatrader` | NINJA TRADER | ✓ | ✓ | — | — | ✓ |
| `paidax` | paidax | ✓ | ✓ | — | — | ✓ |
| `tabys_old` | Tabys (old) | ✓ | — | ✓ | — | ✓ |
| `Ameritrade` | TDA (старые отчеты) | ✓ | ✓ | — | — | ✓ |
| `Turlov` | TFOS | ✓ | ✓ | ✓ | ✓ | ✓ |
| `FreedFinNew2` | АО "Фридом Финанс v3" | ✓ | — | ✓ | — | — |
| `FreedFinNew` | АО "Фридом Финанс" | ✓ | ✓ | ✓ | ✓ | ✓ |
| `FreedFin` | АО "Фридом Финанс" v2 | ✓ | — | — | — | — |
| `tabys_pro` | Табыс PRO | ✓ | — | ✓ | — | ✓ |
| `standard_kz` | Универсальный KZ | ✓ | ✓ | — | — | ✓ |
| `standard_us` | Универсальный US | ✓ | ✓ | — | — | ✓ |

### Важное замечание о формате файлов

Название `/api/loadcsv` историческое и не означает «только CSV». Для Freedom (`FreedFinNew`, `FreedGlobal`, `Turlov`) тот же endpoint принимает XLSX; multipart-поля при этом по-прежнему называются `csv_trans` и `csv_divs`.

---

## Структура JSON налогового отчёта

При синхронном `POST /api/genreport` (`mode=inline`) объект `data` содержит перечисленные ниже datasets. Табличные datasets сериализуются как массивы JSON objects.

**Правило совместимости:** поля со статусом `core` считаются документированным integration contract. Поля со статусом `current` присутствуют в текущем payload, но интеграция не должна зависеть от них без необходимости. Новые дополнительные поля могут появляться без изменения существующих core fields.

### `data.df_data_aggr` — Сводные налоговые итоги

Итоговые суммы по сделкам, дивидендам, купонам/вознаграждениям и строка Всего. Набор колонок имеет фиксированный набор документированных колонок.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `kind` | `string` | `core` | Категория итога: Сделки, Дивиденды, Купоны и вознаграждения или Всего. |
| `income_usd` | `number` | `core` | Legacy name: фактический доход в отображаемой/исходной валюте; не обязательно USD. |
| `income_kzt` | `number` | `core` | Фактический доход в KZT. |
| `income_taxable_usd` | `number` | `core` | Legacy name: доход для целей налогообложения в исходной валюте; не обязательно USD. |
| `income_taxable_kzt` | `number` | `core` | Доход для целей налогообложения в KZT. |
| `tax` | `number` | `core` | Расчётный налог в KZT. |

### `data.df_country` — Доходы по странам: сделки

Агрегация налогооблагаемого дохода от реализованных сделок по коду страны.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `iso_code` | `string` | `core` | Код страны. |
| `income_kzt` | `number` | `core` | Доход в KZT. |
| `income_usd` | `number` | `core` | Legacy name: доход в исходной валюте; не обязательно USD. Если внутри страны несколько валют, значение равно 0; для итогов используйте income_kzt. |

### `data.df_countrydiv` — Доходы по странам: дивиденды

Агрегация дивидендов по коду страны.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `iso_code` | `string` | `core` | Код страны. |
| `income_kzt` | `number` | `core` | Доход в KZT. |
| `income_usd` | `number` | `core` | Legacy name: доход в валюте дивиденда; не обязательно USD. При нескольких валютах внутри страны значение равно 0. |

### `data.df_countrycoup` — Доходы по странам: купоны и проценты

Агрегация секции купонов/вознаграждений по коду страны; текущая логика включает сюда также процентный доход для сводного country block.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `iso_code` | `string` | `core` | Код страны. |
| `income_kzt` | `number` | `core` | Доход в KZT. |
| `income_usd` | `number` | `core` | Legacy name: доход в исходной валюте; не обязательно USD. Если внутри страны несколько валют, значение равно 0; для итогов используйте income_kzt. |

### `data.df_aggr` — Реализованные сделки / FIFO

Основная расчётная таблица. Каждая строка представляет часть продажи, сопоставленную с покупкой по FIFO, после расчёта дохода, курсов и налога. Это не исходный список брокерских сделок.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `broker` | `string` | `core` | Отображаемое имя брокера/счёта, добавленное для отчёта. |
| `broker_nm` | `string` | `core` | Machine code брокера, например IB или FreedFinNew. |
| `account_id` | `string` | `core` | Идентификатор брокерского счёта. |
| `account_type` | `string` | `core` | Тип счёта в отчёте: r для обычного счёта, d для D-account. |
| `ticker` | `string` | `core` | Тикер инструмента. |
| `dt_b` | `string (datetime)` | `core` | Дата и время FIFO-покупки, сопоставленной с продажей. |
| `dt_s` | `string (datetime)` | `core` | Дата и время продажи/закрывающей операции. |
| `isin` | `string\|null` | `core` | ISIN инструмента, использованный при формировании отчёта. |
| `iso_code` | `string` | `core` | Код страны, обычно полученный из ISIN. |
| `amount` | `number` | `core` | Количество инструмента в конкретном FIFO-match. |
| `price_b` | `number` | `core` | Цена покупки для сопоставленного FIFO-лота. |
| `price_s` | `number` | `core` | Цена продажи. |
| `currency_b` | `string` | `core` | Валюта покупки. |
| `currency_s` | `string` | `core` | Валюта продажи. |
| `fee_broker` | `number` | `core` | Брокерская комиссия, нормализованная для FIFO-расчёта. |
| `fee_broker_currency` | `string` | `core` | Валюта брокерской комиссии. |
| `rate_b` | `number\|null` | `current` | Курс валюты покупки к KZT, используемый расчётом. |
| `rate_s` | `number\|null` | `core` | Курс валюты продажи к KZT, используемый расчётом. |
| `rate_fee` | `number\|null` | `core` | Курс валюты комиссии к KZT. |
| `income` | `number` | `core` | Фактический доход/убыток в валюте сделки, когда валюты сопоставимы. Не считать автоматически USD. |
| `income_kzt` | `number` | `core` | Фактический доход/убыток в KZT. |
| `income_ofsh_usd` | `number` | `core` | Legacy name: доход для целей налогообложения в валюте сделки; не обязательно USD. |
| `income_ofsh_kzt` | `number` | `core` | Доход для целей налогообложения в KZT. |
| `tax` | `number` | `core` | Расчётный налог в KZT для строки. |
| `place` | `string` | `core` | Нормализованная биржа/площадка продажи с fallback на площадку покупки. |
| `asset_cat` | `string` | `core` | Категория актива, используемая в правилах сальдирования, например security или security_option. |
| `shorts` | `boolean` | `current` | Признак short-flow/FIFO fallback, если присутствует в текущем payload. |

### `data.df_aggr_Dacc` — Реализованные сделки D-account

FIFO-расчёт для D-account. Семантика полей совпадает с df_aggr.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `broker` | `string` | `core` | Отображаемое имя брокера/счёта, добавленное для отчёта. |
| `broker_nm` | `string` | `core` | Machine code брокера, например IB или FreedFinNew. |
| `account_id` | `string` | `core` | Идентификатор брокерского счёта. |
| `account_type` | `string` | `core` | Тип счёта в отчёте: r для обычного счёта, d для D-account. |
| `ticker` | `string` | `core` | Тикер инструмента. |
| `dt_b` | `string (datetime)` | `core` | Дата и время FIFO-покупки, сопоставленной с продажей. |
| `dt_s` | `string (datetime)` | `core` | Дата и время продажи/закрывающей операции. |
| `isin` | `string\|null` | `core` | ISIN инструмента, использованный при формировании отчёта. |
| `iso_code` | `string` | `core` | Код страны, обычно полученный из ISIN. |
| `amount` | `number` | `core` | Количество инструмента в конкретном FIFO-match. |
| `price_b` | `number` | `core` | Цена покупки для сопоставленного FIFO-лота. |
| `price_s` | `number` | `core` | Цена продажи. |
| `currency_b` | `string` | `core` | Валюта покупки. |
| `currency_s` | `string` | `core` | Валюта продажи. |
| `fee_broker` | `number` | `core` | Брокерская комиссия, нормализованная для FIFO-расчёта. |
| `fee_broker_currency` | `string` | `core` | Валюта брокерской комиссии. |
| `rate_b` | `number\|null` | `current` | Курс валюты покупки к KZT, используемый расчётом. |
| `rate_s` | `number\|null` | `core` | Курс валюты продажи к KZT, используемый расчётом. |
| `rate_fee` | `number\|null` | `core` | Курс валюты комиссии к KZT. |
| `income` | `number` | `core` | Фактический доход/убыток в валюте сделки, когда валюты сопоставимы. Не считать автоматически USD. |
| `income_kzt` | `number` | `core` | Фактический доход/убыток в KZT. |
| `income_ofsh_usd` | `number` | `core` | Legacy name: доход для целей налогообложения в валюте сделки; не обязательно USD. |
| `income_ofsh_kzt` | `number` | `core` | Доход для целей налогообложения в KZT. |
| `tax` | `number` | `core` | Расчётный налог в KZT для строки. |
| `place` | `string` | `core` | Нормализованная биржа/площадка продажи с fallback на площадку покупки. |
| `asset_cat` | `string` | `core` | Категория актива, используемая в правилах сальдирования, например security или security_option. |
| `shorts` | `boolean` | `current` | Признак short-flow/FIFO fallback, если присутствует в текущем payload. |

### `data.df_div` — Дивиденды

Полученные дивиденды за отчётный период после нормализации и расчёта KZT/налога.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `broker` | `string` | `core` | Отображаемое имя брокера/счёта. |
| `ticker` | `string` | `core` | Тикер инструмента. |
| `date` | `string (date)` | `core` | Дата дохода в формате YYYY-MM-DD. |
| `isin` | `string\|null` | `core` | ISIN инструмента. |
| `iso_code` | `string` | `core` | Код страны, обычно полученный из ISIN. |
| `income_usd` | `number` | `core` | Legacy name: сумма дивиденда/купона в валюте операции; не обязательно USD. |
| `tax_brok` | `number` | `core` | Налог, удержанный брокером, в исходных нормализованных данных. |
| `rate` | `number\|null` | `core` | Курс валюты дохода к KZT. |
| `income_kzt` | `number` | `core` | Доход в KZT. |
| `tax` | `number` | `core` | Расчётный налог в KZT. |
| `currency` | `string` | `core` | Валюта дохода. |

### `data.df_coup` — Купоны

Полученные купоны за отчётный период. Структура полей совпадает с df_div.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `broker` | `string` | `core` | Отображаемое имя брокера/счёта. |
| `ticker` | `string` | `core` | Тикер инструмента. |
| `date` | `string (date)` | `core` | Дата дохода в формате YYYY-MM-DD. |
| `isin` | `string\|null` | `core` | ISIN инструмента. |
| `iso_code` | `string` | `core` | Код страны, обычно полученный из ISIN. |
| `income_usd` | `number` | `core` | Legacy name: сумма дивиденда/купона в валюте операции; не обязательно USD. |
| `tax_brok` | `number` | `core` | Налог, удержанный брокером, в исходных нормализованных данных. |
| `rate` | `number\|null` | `core` | Курс валюты дохода к KZT. |
| `income_kzt` | `number` | `core` | Доход в KZT. |
| `tax` | `number` | `core` | Расчётный налог в KZT. |
| `currency` | `string` | `core` | Валюта дохода. |

### `data.df_interest` — Процентный доход

Проценты по брокерскому счёту после нормализации, пересчёта в KZT и расчёта налога.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `broker` | `string` | `core` | Отображаемое имя брокера/счёта. |
| `id` | `integer` | `core` | ID исходной записи процентного дохода. |
| `ticker` | `string` | `core` | Техническое значение INTEREST для совместимости отчётных таблиц. |
| `dt` | `string (datetime)` | `core` | Дата процентной операции. |
| `date` | `string (date)` | `core` | Та же дата в формате YYYY-MM-DD. |
| `isin` | `string` | `core` | Обычно пустая строка: процентный доход не является ценной бумагой. |
| `name` | `string` | `core` | Название/описание, формируется из description. |
| `currency` | `string` | `core` | Валюта процентного дохода. |
| `income_usd` | `number` | `core` | Legacy name: сумма процентного дохода в исходной валюте; не обязательно USD. |
| `rate` | `number\|null` | `core` | Курс к KZT. |
| `income_kzt` | `number` | `core` | Доход в KZT. |
| `tax` | `number` | `core` | Расчётный налог в KZT. |
| `iso_code` | `string` | `core` | Код страны брокера; fallback XX. |
| `description` | `string` | `core` | Описание операции из брокерских данных. |
| `interest_type` | `string` | `core` | Нормализованный тип процентного дохода. |

### `data.df_mbought` — Активы на конец периода

Оставшиеся после FIFO покупки/позиции на конец отчётного периода.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `ticker` | `string` | `core` | Тикер. |
| `isin` | `string\|null` | `core` | ISIN. |
| `name` | `string\|null` | `core` | Название инструмента. |
| `amount` | `number` | `core` | Оставшееся количество. |
| `price` | `number` | `core` | Цена соответствующего оставшегося лота. |
| `currency` | `string` | `core` | Валюта цены. |
| `dt` | `string (datetime)` | `core` | Дата приобретения лота. |
| `place` | `string` | `core` | Биржа/площадка. |
| `asset_cat` | `string` | `core` | Категория актива. |

### `data.df_b_yr` — Покупки за отчётный период

Нормализованные покупки за отчётный период. Не путать с df_mbought: здесь перечисляются покупки периода, а не только остаток.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `ticker` | `string` | `core` | Тикер инструмента. |
| `dt` | `string (date/time)` | `core` | Дата/время покупки или продажи за отчётный период. |
| `isin` | `string\|null` | `core` | ISIN инструмента. |
| `iso_code` | `string` | `core` | Код страны инструмента. |
| `amount` | `number` | `core` | Количество. |
| `price` | `number` | `core` | Цена сделки. |
| `currency` | `string` | `core` | Валюта сделки. |
| `rate` | `number\|null` | `core` | Курс валюты к KZT. |
| `cost_kzt` | `number\|null` | `core` | Стоимость сделки в KZT. |
| `asset_cat` | `string` | `core` | Категория актива. |
| `broker` | `string` | `core` | Отображаемое имя брокера/счёта. |
| `broker_nm` | `string` | `core` | Machine code брокера. |
| `account_id` | `string` | `core` | Идентификатор брокерского счёта. |
| `account_type` | `string` | `core` | r для обычного счёта, d для D-account. |

### `data.df_s_yr` — Продажи за отчётный период

Нормализованные продажи за отчётный период.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `ticker` | `string` | `core` | Тикер инструмента. |
| `dt` | `string (date/time)` | `core` | Дата/время покупки или продажи за отчётный период. |
| `isin` | `string\|null` | `core` | ISIN инструмента. |
| `iso_code` | `string` | `core` | Код страны инструмента. |
| `amount` | `number` | `core` | Количество. |
| `price` | `number` | `core` | Цена сделки. |
| `currency` | `string` | `core` | Валюта сделки. |
| `rate` | `number\|null` | `core` | Курс валюты к KZT. |
| `cost_kzt` | `number\|null` | `core` | Стоимость сделки в KZT. |
| `asset_cat` | `string` | `core` | Категория актива. |
| `broker` | `string` | `core` | Отображаемое имя брокера/счёта. |
| `broker_nm` | `string` | `core` | Machine code брокера. |
| `account_id` | `string` | `core` | Идентификатор брокерского счёта. |
| `account_type` | `string` | `core` | r для обычного счёта, d для D-account. |

### `data.df_assetcur` — Валюта/денежные остатки

Остатки денежных средств/валюты на брокерском счёте для отчётного периода.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `id` | `integer` | `core` | ID записи остатка. |
| `currency` | `string` | `core` | Код валюты. |
| `amount` | `number` | `core` | Остаток. |
| `cutoff_date` | `string` | `core` | Дата состояния остатка; текущий формат ответа DD-MM-YYYY. |
| `broker_str` | `string` | `core` | Отображаемое имя брокера. |
| `broker` | `string` | `core` | Machine code брокера. |
| `account_id` | `string` | `core` | Идентификатор брокерского счёта. |

### `data.df_transfers` — Трансферы ценных бумаг

Трансферы ценных бумаг. Для входящих трансферов доступны исторические цена/валюта/date_orig; для исходящих эти acquisition fields намеренно могут быть пустыми.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `transfer_id` | `integer` | `core` | ID трансфера. |
| `broker_nm` | `string` | `core` | Machine code брокера. |
| `account_id` | `string` | `core` | Брокерский счёт. |
| `ticker` | `string` | `core` | Тикер. |
| `orig_id` | `string\|integer\|null` | `core` | Исходный идентификатор записи, если известен. |
| `dt` | `string (datetime)` | `core` | Дата трансфера. |
| `amount` | `number` | `core` | Количество. |
| `price` | `number\|null` | `core` | Историческая цена приобретения для transfer-in; для transfer-out может быть null. |
| `currency` | `string` | `core` | Валюта исторической цены; для transfer-out может быть пустой. |
| `date_orig` | `datetime\|null` | `core` | Историческая дата приобретения; для transfer-out может быть null. |
| `note` | `string\|null` | `core` | Комментарий. |
| `transfer_dir` | `string` | `core` | Направление: in или out. |
| `broker_str` | `string\|null` | `core` | Отображаемое имя брокера. |
| `date_orig_f` | `string` | `current` | Display-only дата исходного приобретения; может отсутствовать в consolidated payload. |

### `data.df_transfers_info` — Информация о трансферах

Заголовочная/служебная информация о transfer events. Используется для описания transfer-in/out и связывания исторических acquisition lots.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `transfer_id` | `integer` | `core` | ID трансфера. |
| `orig_id` | `string\|integer\|null` | `core` | Исходный идентификатор, если известен. |
| `broker_nm` | `string` | `core` | Machine code брокера. |
| `account_id` | `string` | `core` | Брокерский счёт. |
| `ticker` | `string` | `core` | Тикер. |
| `dt` | `string (datetime)` | `core` | Дата трансфера. |
| `amount` | `number` | `core` | Количество. |
| `currency` | `string\|null` | `core` | Валюта, если применима. |
| `note` | `string\|null` | `core` | Комментарий. |
| `transfer_dir` | `string` | `core` | Направление: in или out. |
| `broker_str` | `string` | `current` | Отображаемое имя брокера; присутствует в consolidated flow. |

### `data.df_leftover` — Продажи без найденной покупки

Продажи/количества, для которых FIFO не смог полностью найти acquisition lot. Ненулевой результат требует проверки данных или осознанного allow_short_fallback.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `ticker` | `string` | `core` | Тикер. |
| `dt_s` | `string (datetime)` | `core` | Дата продажи/закрытия. |
| `count` | `number` | `core` | Непокрытое FIFO количество. |
| `broker` | `string` | `current` | Брокер/счёт; добавляется в consolidated report. |

### `data.leftover_cnt` — Количество FIFO leftovers

Не массив, а объект совместимости. Значение сериализуется строкой.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `leftover_cnt` | `string` | `core` | Суммарное непокрытое количество, сериализованное как строка для текущего API contract. |

### `data.report_info` — Метаданные сформированного отчёта

Идентификаторы и готовая ссылка на XLSX.

| Поле | Тип | Статус | Описание |
|---|---|---|---|
| `report_id` | `integer` | `core` | ID отчёта. |
| `filename` | `string\|null` | `core` | Имя XLSX-файла. |
| `download_url` | `string` | `core` | Готовый URL для скачивания отчёта; использовать вместо ручной сборки URL. |

---

## Начало работы

### GET `/api/testreq`

**Проверка API-ключа**

Проверяет API key и возвращает контекст интеграции.

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/testreq' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/testreq'
headers = {'X-API-Key': 'YOUR_API_KEY'}
r = requests.get(url, headers=headers, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "api_client_code": "partner_code",
  "scopes": [
    "clients:read",
    "data:read",
    "reports:read"
  ]
}
```

---

## Налогоплательщики

Один user_id может содержать одного или нескольких налогоплательщиков. Используйте отдельный client_id только когда нужно разделить их данные.

### GET `/api/clients`

**Список налогоплательщиков**

Возвращает налогоплательщиков, доступных выбранному пользователю.

**Scope:** `clients:read`

**Примечания:**

- Используйте user_id как стабильный идентификатор пользователя в вашей системе.
- Если client_id не указан, используется стабильный default client_id "1" там, где endpoint поддерживает автоматическое создание.

**Query parameters:**

- `user_id`: `user@example.com`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/clients?user_id=user%40example.com' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/clients'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "count": 1,
  "user_id": "user@example.com",
  "clients": [
    {
      "id": "1",
      "name_first": "TEST",
      "name_last": "USER"
    }
  ]
}
```

---

### POST `/api/clients`

**Создать налогоплательщика**

Создаёт отдельного налогоплательщика внутри одного пользователя.

**Scope:** `clients:write`

**Примечания:**

- Если client_id не передан, API создаёт или возвращает default client с id "1".
- Явный client_id удобен, когда один пользователь ведёт данные нескольких налогоплательщиков, например свои и супруги.

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "client_id": "spouse",
  "name_first": "TEST",
  "name_last": "USER",
  "birth_date": "1988-05-12",
  "note": "Spouse"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/clients' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","client_id":"spouse","name_first":"TEST","name_last":"USER","birth_date":"1988-05-12","note":"Spouse"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/clients'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "client_id": "spouse",
  "name_first": "TEST",
  "name_last": "USER",
  "birth_date": "1988-05-12",
  "note": "Spouse"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "created": true,
  "default_client": false,
  "user_id": "user@example.com",
  "client": {
    "id": "spouse",
    "name_first": "TEST",
    "name_last": "USER"
  }
}
```

---

### POST `/api/client-invites`

**Создать приглашение консультанту**

Позволяет предоставить консультанту Sber-Invest доступ к выбранному налогоплательщику, например для помощи с проверкой данных, исправлением ошибок или формированием отчётности.

**Scope:** `clients:write`

**Примечания:**

- Налогоплательщик с указанным client_id должен уже существовать.
- После создания приглашения передайте значение code консультанту Sber-Invest. Консультант активирует код в Investax и получит доступ только к выбранному налогоплательщику.
- role=view даёт доступ только для просмотра; role=edit позволяет также вносить изменения.
- ttl_days задаёт срок действия приглашения и предоставленного через него доступа: от 1 до 3650 дней.
- Передавайте код только тому консультанту, которому вы хотите предоставить доступ.

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "client_id": "spouse",
  "role": "edit",
  "ttl_days": 30
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/client-invites' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","client_id":"spouse","role":"edit","ttl_days":30}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/client-invites'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "client_id": "spouse",
  "role": "edit",
  "ttl_days": 30
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "code": "INVITE_TOKEN.INVITE_ID",
  "role": "edit",
  "client_id": "spouse"
}
```

---

## Загрузка брокерских данных

Для системной интеграции предпочтителен POST /api/loadjson со Standard JSON. Также доступны исторические CSV/XLSX, PDF и TraderNet flows. Используйте реальный код broker и стабильный account_id конкретного брокерского счёта.

### POST `/api/loadjson`

**Загрузить Standard JSON**

Основной normalized JSON endpoint: валидирует и sparsely merge-ит сделки, доходы, остатки, transfers и corporate actions для выбранного broker/account.

**Scope:** `data:write`

**Примечания:**

- Рекомендуемый endpoint для систем, которые могут преобразовать broker data в документированный Standard JSON.
- Payload sparse: опущенные секции не удаляют уже загруженные данные. Standard JSON не является командой полного replace account history.
- broker должен быть реальным configured broker code; aggregate pseudo-codes all/all_ru здесь запрещены.
- client_id можно опустить: external API использует default client_id="1".
- Формат всех секций, asset_cat, derivatives и split описан выше в разделе Standard JSON.

**JSON body:**

```json
{
  "schema_version": 1,
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main",
  "report_end_date": "2025-12-31",
  "broker_info": {
    "name": "INTERACTIVE BROKERS LLC",
    "swift": "IBKRUS33",
    "iso_code": "US"
  },
  "trades": [
    {
      "date": "2025-01-10T10:00:00",
      "type": "bought",
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 10,
      "price": 185.25,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security"
    },
    {
      "date": "2025-06-20T10:00:00",
      "type": "sold",
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 4,
      "price": 210.0,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security"
    }
  ],
  "cash": [
    {
      "currency": "USD",
      "amount": 1234.56
    }
  ],
  "assets": [
    {
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 6,
      "price": 185.25,
      "currency": "USD",
      "asset_cat": "security"
    }
  ]
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/loadjson' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"schema_version":1,"user_id":"user@example.com","broker":"IB","account_id":"main","report_end_date":"2025-12-31","broker_info":{"name":"INTERACTIVE BROKERS LLC","swift":"IBKRUS33","iso_code":"US"},"trades":[{"date":"2025-01-10T10:00:00","type":"bought","ticker":"AAPL","isin":"US0378331005","amount":10,"price":185.25,"currency":"USD","place":"NASDAQ","asset_cat":"security"},{"date":"2025-06-20T10:00:00","type":"sold","ticker":"AAPL","isin":"US0378331005","amount":4,"price":210.0,"currency":"USD","place":"NASDAQ","asset_cat":"security"}],"cash":[{"currency":"USD","amount":1234.56}],"assets":[{"ticker":"AAPL","isin":"US0378331005","amount":6,"price":185.25,"currency":"USD","asset_cat":"security"}]}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/loadjson'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "schema_version": 1,
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main",
  "report_end_date": "2025-12-31",
  "broker_info": {
    "name": "INTERACTIVE BROKERS LLC",
    "swift": "IBKRUS33",
    "iso_code": "US"
  },
  "trades": [
    {
      "date": "2025-01-10T10:00:00",
      "type": "bought",
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 10,
      "price": 185.25,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security"
    },
    {
      "date": "2025-06-20T10:00:00",
      "type": "sold",
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 4,
      "price": 210.0,
      "currency": "USD",
      "place": "NASDAQ",
      "asset_cat": "security"
    }
  ],
  "cash": [
    {
      "currency": "USD",
      "amount": 1234.56
    }
  ],
  "assets": [
    {
      "ticker": "AAPL",
      "isin": "US0378331005",
      "amount": 6,
      "price": 185.25,
      "currency": "USD",
      "asset_cat": "security"
    }
  ]
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "user_id": "user@example.com",
  "client_id": "1",
  "default_client": true,
  "broker": "IB",
  "broker_name": "Interactive Brokers",
  "account_id": "main",
  "is_d_account": false,
  "result": {
    "schema_version": 1,
    "broker": "IB",
    "account_id": "main",
    "report_end_date": "2025-12-31T23:59:59.999999",
    "is_d_account": false,
    "tax_year": 2025,
    "provided_sections": [
      "assets",
      "cash",
      "trades"
    ],
    "imported": {
      "trades": 2,
      "dividends": 0,
      "interest": 0,
      "cash": 1,
      "assets": 1,
      "transfers": {
        "received": 0,
        "inserted": 0,
        "updated": 0,
        "deleted": 0,
        "skipped": 0
      },
      "corporate_actions": 0
    },
    "warnings": []
  }
}
```

---

### POST `/api/loadcsv`

**Загрузить CSV / Excel отчёт брокера**

Загружает брокерский файл через исторически названный endpoint /api/loadcsv. В зависимости от брокера поддерживается CSV и/или Excel; большие файлы могут обрабатываться в фоне.

**Scope:** `data:write`

**Примечания:**

- csv_divs опционален для форматов, где дивиденды находятся в отдельном файле.
- Имена multipart-полей остаются csv_trans/csv_divs даже если фактический файл имеет расширение .xlsx.
- Для Freedom (FreedFinNew, FreedGlobal, Turlov) Excel XLSX загружается через этот же /api/loadcsv endpoint.
- Если client_id не указан, используется default client "1".

**Multipart/form-data:**

- `user_id`: `user@example.com`
- `broker`: `IB`
- `account_id`: `main`
- `csv_trans`: file `trades.csv`
- `csv_divs`: file `dividends.csv`

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/loadcsv' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -F 'user_id=user@example.com' \
  -F 'broker=IB' \
  -F 'account_id=main' \
  -F 'csv_trans=@trades.csv' \
  -F 'csv_divs=@dividends.csv'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/loadcsv'
headers = {'X-API-Key': 'YOUR_API_KEY'}
data = {
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main"
}
files = {
    'csv_trans': open('trades.csv', 'rb'),
    'csv_divs': open('dividends.csv', 'rb'),
}
r = requests.post(url, headers=headers, data=data, files=files, timeout=180)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "mode": "rq",
  "job_id": "JOB_ID",
  "client_id": "1",
  "default_client": true
}
```

---

### POST `/api/loadpdf`

**Загрузить брокерский PDF**

Загружает основной PDF отчёт брокера и, при необходимости, отдельный PDF с дивидендами.

**Scope:** `data:write`

**Примечания:**

- pdf_dividends является опциональным.

**Multipart/form-data:**

- `user_id`: `user@example.com`
- `broker`: `FreedGlobal`
- `account_id`: `main`
- `pdf_report`: file `broker_report.pdf`
- `pdf_dividends`: file `dividends.pdf`

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/loadpdf' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -F 'user_id=user@example.com' \
  -F 'broker=FreedGlobal' \
  -F 'account_id=main' \
  -F 'pdf_report=@broker_report.pdf' \
  -F 'pdf_dividends=@dividends.pdf'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/loadpdf'
headers = {'X-API-Key': 'YOUR_API_KEY'}
data = {
  "user_id": "user@example.com",
  "broker": "FreedGlobal",
  "account_id": "main"
}
files = {
    'pdf_report': open('broker_report.pdf', 'rb'),
    'pdf_dividends': open('dividends.pdf', 'rb'),
}
r = requests.post(url, headers=headers, data=data, files=files, timeout=180)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "mode": "rq",
  "job_id": "JOB_ID",
  "client_id": "1"
}
```

---

### POST `/api/tradernet/send_sms`

**Tradernet: отправить SMS**

Шаг 1. Запрашивает SMS-код для Tradernet / Freedom account.

**Scope:** `data:write`

**Примечания:**

- URL Tradernet выбирается сервером. Переданный клиентом base_url не используется.

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "FreedFinNew",
  "phone": "+77000000000"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/tradernet/send_sms' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"FreedFinNew","phone":"+77000000000"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/tradernet/send_sms'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "FreedFinNew",
  "phone": "+77000000000"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "auth_code_id": "AUTH_CODE_ID",
  "client_id": "1",
  "default_client": true,
  "broker": "FreedFinNew",
  "user_id": "user@example.com"
}
```

---

### POST `/api/tradernet/check_sms`

**Tradernet: проверить SMS**

Шаг 2. Проверяет SMS. Если найдено несколько счетов, возвращает список для выбора.

**Scope:** `data:write`

**Примечания:**

- SMS-код, SID и другие authentication secrets не сохраняются в raw broker logs.
- Если need_account_selection=true, отправьте выбранный accounts[].account_id на следующий endpoint.

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "FreedFinNew",
  "auth_code_id": "AUTH_CODE_ID",
  "sms_code": "123456",
  "phone": "+77000000000"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/tradernet/check_sms' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"FreedFinNew","auth_code_id":"AUTH_CODE_ID","sms_code":"123456","phone":"+77000000000"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/tradernet/check_sms'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "FreedFinNew",
  "auth_code_id": "AUTH_CODE_ID",
  "sms_code": "123456",
  "phone": "+77000000000"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "need_account_selection": true,
  "accounts": [
    {
      "account_id": "ACCOUNT_ID_FROM_API",
      "trader_systems_id": "TRADER_SYSTEM_ID_FROM_API",
      "account_type": "brokerage",
      "reception_code": "FFG"
    }
  ]
}
```

---

### POST `/api/tradernet/select_account`

**Tradernet: выбрать счёт**

Шаг 3. Выбирает счёт и загружает broker data в Investax.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "FreedFinNew",
  "auth_code_id": "AUTH_CODE_ID",
  "sms_code": "123456",
  "account_id": "ACCOUNT_ID_FROM_API",
  "phone": "+77000000000"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/tradernet/select_account' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"FreedFinNew","auth_code_id":"AUTH_CODE_ID","sms_code":"123456","account_id":"ACCOUNT_ID_FROM_API","phone":"+77000000000"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/tradernet/select_account'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "FreedFinNew",
  "auth_code_id": "AUTH_CODE_ID",
  "sms_code": "123456",
  "account_id": "ACCOUNT_ID_FROM_API",
  "phone": "+77000000000"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "message": "Tradernet data loaded",
  "broker": "FreedGlobal",
  "client_id": "1",
  "data": {
    "account_num": "TRADER_SYSTEM_ID_FROM_API",
    "trades_processed": true,
    "divs_processed": true,
    "assets_processed": true
  }
}
```

---

## Сделки и доходы

Эти endpoints позволяют просматривать загруженные данные и при необходимости добавлять или корректировать отдельные записи вручную.

### GET `/api/trades`

**Список сделок**

Возвращает сделки выбранного пользователя и налогоплательщика.

**Scope:** `data:read`

**Примечания:**

- Дополнительные фильтры: account_id, ticker, currency, date_from, date_to, type, asset_cat.
- page_size ограничен 200.

**Query parameters:**

- `user_id`: `user@example.com`
- `broker`: `IB`
- `year`: `2025`
- `page`: `1`
- `page_size`: `100`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/trades?user_id=user%40example.com&broker=IB&year=2025&page=1&page_size=100' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/trades'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "broker": "IB",
  "year": "2025",
  "page": "1",
  "page_size": "100"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "client_id": "1",
  "page": 1,
  "page_size": 100,
  "total": 1,
  "trades": [
    {
      "id": 101,
      "ticker": "AAPL",
      "type": "sold",
      "amount": 2,
      "price": 210.25,
      "currency": "USD"
    }
  ]
}
```

---

### POST `/api/trades`

**Добавить сделку**

Добавляет ручную сделку.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB",
  "ticker": "AAPL",
  "date": "2025-05-20",
  "type": "bought",
  "amount": 2,
  "price": 195.1,
  "currency": "USD",
  "account_id": "main",
  "fee_broker": 1.0,
  "asset_cat": "security"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/trades' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB","ticker":"AAPL","date":"2025-05-20","type":"bought","amount":2,"price":195.1,"currency":"USD","account_id":"main","fee_broker":1.0,"asset_cat":"security"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/trades'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB",
  "ticker": "AAPL",
  "date": "2025-05-20",
  "type": "bought",
  "amount": 2,
  "price": 195.1,
  "currency": "USD",
  "account_id": "main",
  "fee_broker": 1.0,
  "asset_cat": "security"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "created": true,
  "client_id": "1",
  "trade": {
    "id": 101,
    "ticker": "AAPL",
    "type": "bought"
  }
}
```

---

### PATCH `/api/trades/<int:record_id>`

**Изменить сделку**

Изменяет только указанные поля существующей сделки.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB",
  "price": 196.25
}
```

**cURL:**

```bash
curl -X PATCH 'https://investax.taxexpress.kz/api/trades/101' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB","price":196.25}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/trades/101'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB",
  "price": 196.25
}
r = requests.patch(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "updated": true,
  "trade": {
    "id": 101,
    "price": 196.25
  }
}
```

---

### DELETE `/api/trades/<int:record_id>`

**Удалить сделку**

Удаляет сделку только внутри текущего user/client scope.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB"
}
```

**cURL:**

```bash
curl -X DELETE 'https://investax.taxexpress.kz/api/trades/101' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/trades/101'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB"
}
r = requests.delete(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "deleted": true,
  "record_id": 101
}
```

---

### GET `/api/dividends`

**Список дивидендов**

Возвращает дивиденды и купоны.

**Scope:** `data:read`

**Примечания:**

- Фильтры: broker, account_id, year, ticker, currency, date_from, date_to, dividend_type.

**Query parameters:**

- `user_id`: `user@example.com`
- `broker`: `IB`
- `year`: `2025`
- `page`: `1`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/dividends?user_id=user%40example.com&broker=IB&year=2025&page=1' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/dividends'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "broker": "IB",
  "year": "2025",
  "page": "1"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "client_id": "1",
  "dividends": [
    {
      "id": 51,
      "ticker": "AAPL",
      "income": 15.5,
      "tax_brok": 2.32,
      "currency": "USD",
      "dividend_type": "ord_div"
    }
  ]
}
```

---

### POST `/api/dividends`

**Добавить дивиденд**

Добавляет ручную запись дивиденда или купона.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB",
  "ticker": "AAPL",
  "date": "2025-05-15",
  "income": 15.5,
  "tax_brok": 2.32,
  "dividend_type": "ord_div",
  "currency": "USD",
  "account_id": "main"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/dividends' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB","ticker":"AAPL","date":"2025-05-15","income":15.5,"tax_brok":2.32,"dividend_type":"ord_div","currency":"USD","account_id":"main"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/dividends'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB",
  "ticker": "AAPL",
  "date": "2025-05-15",
  "income": 15.5,
  "tax_brok": 2.32,
  "dividend_type": "ord_div",
  "currency": "USD",
  "account_id": "main"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "created": true,
  "dividend": {
    "id": 51,
    "ticker": "AAPL"
  }
}
```

---

### PATCH `/api/dividends/<int:record_id>`

**Изменить дивиденд**

Изменяет существующий дивиденд.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB",
  "income": 16.0
}
```

**cURL:**

```bash
curl -X PATCH 'https://investax.taxexpress.kz/api/dividends/51' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB","income":16.0}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/dividends/51'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB",
  "income": 16.0
}
r = requests.patch(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "updated": true,
  "dividend": {
    "id": 51,
    "income": 16.0
  }
}
```

---

### DELETE `/api/dividends/<int:record_id>`

**Удалить дивиденд**

Удаляет дивиденд в текущем user/client scope.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB"
}
```

**cURL:**

```bash
curl -X DELETE 'https://investax.taxexpress.kz/api/dividends/51' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/dividends/51'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB"
}
r = requests.delete(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "deleted": true,
  "record_id": 51
}
```

---

## Трансферы и активы

Используйте этот раздел для переноса ценных бумаг между счетами и проверки данных по активам выбранного налогоплательщика.

### GET `/api/accounts`

**Список брокерских счетов**

Возвращает broker/account pairs, обнаруженные в загруженных данных.

**Scope:** `data:read`

**Примечания:**

- Можно фильтровать по broker и account_id.

**Query parameters:**

- `user_id`: `user@example.com`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/accounts?user_id=user%40example.com' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/accounts'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "client_id": "1",
  "accounts": [
    {
      "broker": "FreedGlobal",
      "account_id": "TRADER_SYSTEM_ID_FROM_API",
      "account_type": "r",
      "sources": [
        "account_info",
        "trades"
      ]
    }
  ]
}
```

---

### GET `/api/assets`

**Список активов**

Возвращает currency assets и remaining security positions одной лентой.

**Scope:** `data:read`

**Примечания:**

- Фильтры: broker, account_id, year, kind=currency|security, symbol/ticker.

**Query parameters:**

- `user_id`: `user@example.com`
- `year`: `2025`
- `page`: `1`
- `page_size`: `100`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/assets?user_id=user%40example.com&year=2025&page=1&page_size=100' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/assets'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "year": "2025",
  "page": "1",
  "page_size": "100"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "client_id": "1",
  "assets": [
    {
      "kind": "currency",
      "symbol": "USD",
      "amount": 1000.0
    },
    {
      "kind": "security",
      "symbol": "AAPL",
      "amount": 2.0
    }
  ]
}
```

---

### GET `/api/transfers`

**Список трансферов**

Возвращает transfer headers и acquisition lots для входящих переводов.

**Scope:** `data:read`

**Query parameters:**

- `user_id`: `user@example.com`
- `year`: `2025`
- `page`: `1`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/transfers?user_id=user%40example.com&year=2025&page=1' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/transfers'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "year": "2025",
  "page": "1"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "client_id": "1",
  "transfers": [
    {
      "transfer_id": 41,
      "broker": "IB",
      "account_id": "main",
      "ticker": "AAPL",
      "direction": "in",
      "amount": 2,
      "lots": [
        {
          "amount": 2,
          "price": 180,
          "currency": "USD"
        }
      ]
    }
  ]
}
```

---

### POST `/api/transfers`

**Добавить трансфер**

Создаёт ручной transfer. Для transfer in можно передать исторические acquisition lots.

**Scope:** `data:write`

**Примечания:**

- Операционная дата transfer-in задаётся transfer_date. Историческая дата приобретения может храниться в lot.date_orig.

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main",
  "ticker": "AAPL",
  "amount": 2,
  "transfer_date": "2025-01-10",
  "direction": "in",
  "currency": "USD",
  "lots": [
    {
      "amount": 2,
      "price": 180.0,
      "currency": "USD",
      "date_orig": "2024-06-15"
    }
  ],
  "note": "Transferred from another broker"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/transfers' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB","account_id":"main","ticker":"AAPL","amount":2,"transfer_date":"2025-01-10","direction":"in","currency":"USD","lots":[{"amount":2,"price":180.0,"currency":"USD","date_orig":"2024-06-15"}],"note":"Transferred from another broker"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/transfers'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main",
  "ticker": "AAPL",
  "amount": 2,
  "transfer_date": "2025-01-10",
  "direction": "in",
  "currency": "USD",
  "lots": [
    {
      "amount": 2,
      "price": 180.0,
      "currency": "USD",
      "date_orig": "2024-06-15"
    }
  ],
  "note": "Transferred from another broker"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "created": true,
  "transfer_id": 41
}
```

---

### PATCH `/api/transfers/<int:record_id>`

**Изменить трансфер**

Изменяет существующий transfer.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB",
  "note": "Updated note"
}
```

**cURL:**

```bash
curl -X PATCH 'https://investax.taxexpress.kz/api/transfers/41' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB","note":"Updated note"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/transfers/41'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB",
  "note": "Updated note"
}
r = requests.patch(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "updated": true,
  "transfer_id": 41
}
```

---

### DELETE `/api/transfers/<int:record_id>`

**Удалить трансфер**

Удаляет transfer только внутри текущего scope.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB"
}
```

**cURL:**

```bash
curl -X DELETE 'https://investax.taxexpress.kz/api/transfers/41' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/transfers/41'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB"
}
r = requests.delete(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "deleted": true,
  "transfer_id": 41
}
```

---

### POST `/api/transfers/from-trades`

**Создать трансфер из сделок**

Формирует paired OUT/IN transfer на основе уже существующих purchase trades.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "source_broker": "IB",
  "destination_broker": "FreedGlobal",
  "destination_account": "TRADER_SYSTEM_ID_FROM_API",
  "transfer_date": "2025-03-01",
  "rows": [
    {
      "trade_id": 101,
      "amount": 2
    }
  ],
  "note": "Broker transfer"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/transfers/from-trades' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","source_broker":"IB","destination_broker":"FreedGlobal","destination_account":"TRADER_SYSTEM_ID_FROM_API","transfer_date":"2025-03-01","rows":[{"trade_id":101,"amount":2}],"note":"Broker transfer"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/transfers/from-trades'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "source_broker": "IB",
  "destination_broker": "FreedGlobal",
  "destination_account": "TRADER_SYSTEM_ID_FROM_API",
  "transfer_date": "2025-03-01",
  "rows": [
    {
      "trade_id": 101,
      "amount": 2
    }
  ],
  "note": "Broker transfer"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "created": true
}
```

---

## Корректировки ISIN

Корректировка ISIN нужна, когда у инструмента отсутствует корректный ISIN. Обычно необходимость такой корректировки видна при формировании отчёта.

### GET `/api/isin-overrides`

**Список корректировок ISIN**

Возвращает пользовательские корректировки ticker-to-ISIN.

**Scope:** `data:read`

**Query parameters:**

- `user_id`: `user@example.com`
- `broker`: `IB`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/isin-overrides?user_id=user%40example.com&broker=IB' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/isin-overrides'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "broker": "IB"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "client_id": "1",
  "overrides": [
    {
      "id": 7,
      "broker": "IB",
      "ticker": "ABC",
      "isin": "US0000000002"
    }
  ]
}
```

---

### POST `/api/isin-overrides`

**Добавить или изменить ISIN**

Добавляет пользовательскую корректировку ISIN для выбранного пользователя.

**Scope:** `data:write`

**Примечания:**

- Корректировка применяется только к данным выбранного пользователя и налогоплательщика.
- Корректировка используется только когда ISIN не был определён автоматически.

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB",
  "ticker": "ABC",
  "isin": "US0000000002"
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/isin-overrides' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB","ticker":"ABC","isin":"US0000000002"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/isin-overrides'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB",
  "ticker": "ABC",
  "isin": "US0000000002"
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "created": true,
  "override": {
    "ticker": "ABC",
    "isin": "US0000000002"
  }
}
```

---

### DELETE `/api/isin-overrides/<int:override_id>`

**Удалить корректировку ISIN**

Удаляет пользовательскую корректировку ISIN по её id.

**Scope:** `data:write`

**JSON body:**

```json
{
  "user_id": "user@example.com"
}
```

**cURL:**

```bash
curl -X DELETE 'https://investax.taxexpress.kz/api/isin-overrides/7' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com"}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/isin-overrides/7'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com"
}
r = requests.delete(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "deleted": true,
  "override_id": 7
}
```

---

## Отчёты

Сформируйте отчёт для одного broker/account или используйте broker="all" для консолидированного отчёта. Для скачивания используйте download_url из ответа API.

### GET, POST `/api/genreport`

**Сформировать налоговый отчёт**

Формирует Excel tax report для одного broker/account или консолидированный отчёт по всем брокерам пользователя.

**Scope:** `reports:write`

**Примечания:**

- Для больших наборов данных endpoint может вернуть HTTP 202 и job_id.
- Если отсутствует ISIN, возможен HTTP 422 с error=missing_isin.
- При mode=inline поле data содержит расчётные datasets. Их контракт описан в разделе Структура JSON налогового отчёта.

**JSON body:**

```json
{
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main",
  "year": 2025,
  "allow_short_fallback": false
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/genreport' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"user@example.com","broker":"IB","account_id":"main","year":2025,"allow_short_fallback":false}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/genreport'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "user@example.com",
  "broker": "IB",
  "account_id": "main",
  "year": 2025,
  "allow_short_fallback": false
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "mode": "inline",
  "report_id": 105,
  "client_id": "1",
  "download_url": "https://investax.taxexpress.kz/api/report/download?report_id=105&client_id=1&user_id=user%40example.com",
  "data": {
    "df_data_aggr": [
      {
        "kind": "Сделки",
        "income_usd": 1200.0,
        "income_kzt": 610000.0,
        "income_taxable_usd": 1100.0,
        "income_taxable_kzt": 560000.0,
        "tax": 56000.0
      }
    ],
    "df_aggr": [],
    "df_div": [],
    "df_coup": [],
    "df_interest": [],
    "df_b_yr": [],
    "df_s_yr": [],
    "df_assetcur": [],
    "df_transfers": [],
    "df_leftover": [],
    "leftover_cnt": {
      "leftover_cnt": "0"
    },
    "report_info": {
      "report_id": 105,
      "filename": "report.xlsx",
      "download_url": "https://investax.taxexpress.kz/api/report/download?report_id=105&client_id=1&user_id=user%40example.com"
    }
  }
}
```

---

### GET `/api/reports`

**Список отчётов**

Возвращает активные сформированные Excel reports.

**Scope:** `reports:read`

**Query parameters:**

- `user_id`: `user@example.com`
- `year`: `2025`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/reports?user_id=user%40example.com&year=2025' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/reports'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "year": "2025"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "client_id": "1",
  "reports": [
    {
      "report_id": 105,
      "broker": "IB",
      "year": 2025,
      "download_url": "https://investax.taxexpress.kz/api/report/download?report_id=105&client_id=1&user_id=user%40example.com"
    }
  ]
}
```

---

### GET `/api/report/download`

**Скачать отчёт**

Скачивает XLSX-отчёт выбранного пользователя и налогоплательщика.

**Scope:** `reports:read`

**Примечания:**

- Передавайте тот же client_id, для которого был сформирован отчёт. Предпочтительно использовать download_url из ответа API.

**Query parameters:**

- `user_id`: `user@example.com`
- `client_id`: `1`
- `report_id`: `105`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/report/download?user_id=user%40example.com&client_id=1&report_id=105' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/report/download'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "client_id": "1",
  "report_id": "105"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
content = r.content
```

**Ответ:**

```
Binary XLSX response
```

---

## Декларации

ФНО 250 и 270 формируются на основе уже созданного налогового отчёта. Сначала получите report_id, затем передайте его при создании декларации.

### POST `/api/declarations/250`

**Сформировать ФНО 250**

Генерирует JSON ФНО 250 из существующего source report.

**Scope:** `declarations:write`

**Примечания:**

- Текущая версия может сохранить декларацию is_active=false при blocking validation warning. Сам файл при этом создаётся и direct download остаётся доступен.
- В person передавайте sra_code — код ОГД по месту жительства (ogdCodeByResidence). phone, email, fio3, notice_number и notice_date опциональны.
- person_category по умолчанию C, is_resident по умолчанию true.
- Реквизиты банка/брокера для денежных остатков берутся из broker_info конкретного broker/account или из настроенного fallback. Не передавайте их повторно в cash.

**JSON body:**

```json
{
  "user_id": "ayan@example.com",
  "report_id": 105,
  "person": {
    "fio1": "SARSENBAYEV",
    "fio2": "AYAN",
    "fio3": "",
    "iin": "XXXXXXXXXXXX",
    "declaration_type": "dt_regular",
    "sra_code": "6001",
    "phone": "+77000000000",
    "email": "ayan@example.com",
    "person_category": "C",
    "is_resident": "true",
    "notice_number": "",
    "notice_date": null
  }
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/declarations/250' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"ayan@example.com","report_id":105,"person":{"fio1":"SARSENBAYEV","fio2":"AYAN","fio3":"","iin":"XXXXXXXXXXXX","declaration_type":"dt_regular","sra_code":"6001","phone":"+77000000000","email":"ayan@example.com","person_category":"C","is_resident":"true","notice_number":"","notice_date":null}}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/declarations/250'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "ayan@example.com",
  "report_id": 105,
  "person": {
    "fio1": "SARSENBAYEV",
    "fio2": "AYAN",
    "fio3": "",
    "iin": "XXXXXXXXXXXX",
    "declaration_type": "dt_regular",
    "sra_code": "6001",
    "phone": "+77000000000",
    "email": "ayan@example.com",
    "person_category": "C",
    "is_resident": "true",
    "notice_number": "",
    "notice_date": null
  }
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "created": true,
  "declaration_id": 201,
  "form": "250",
  "report_id": 105,
  "is_active": true,
  "validation": {
    "is_active": true,
    "blocking_reasons": []
  }
}
```

---

### POST `/api/declarations/270`

**Сформировать ФНО 270**

Генерирует JSON ФНО 270 из существующего source report.

**Scope:** `declarations:write`

**Примечания:**

- В person передавайте sra_code — код ОГД по месту жительства (ogdCodeByResidence). phone, email и fio3 опциональны.
- Для обычной генерации из report_id используйте income_action=replace. Режим sum предназначен для merge-flow с существующей декларацией.
- Реквизиты банка/брокера и SWIFT для денежных остатков берутся из broker_info конкретного broker/account или настроенного fallback.

> **Расширенная декларация**
>
> civil_status=civilian — обычная ФНО 270. civil_status=public_servant — legacy machine value для расширенной декларации; этот режим добавляет application_05. Название public_servant сохранено для совместимости и не означает, что режим предназначен только для госслужащих.
>
> **Расширенную декларацию обязаны подавать:**
> - учредители / руководители юридических лиц с долей в уставном капитале более 10% (и их супруги);
> - государственные служащие и лица, подпадающие под требования Закона РК «О противодействии коррупции» (и их супруги);
> - лица, совершившие в отчётном периоде крупные приобретения (свыше 20 000 МРП — 78,6 млн ₸ на 2025 год).

**JSON body:**

```json
{
  "user_id": "ayan@example.com",
  "report_id": 105,
  "civil_status": "civilian",
  "income_action": "replace",
  "person": {
    "fio1": "SARSENBAYEV",
    "fio2": "AYAN",
    "fio3": "",
    "iin": "XXXXXXXXXXXX",
    "declaration_type": "dt_regular",
    "agreement": "false",
    "sra_code": "6001",
    "phone": "+77000000000",
    "email": "ayan@example.com"
  }
}
```

**cURL:**

```bash
curl -X POST 'https://investax.taxexpress.kz/api/declarations/270' \
  -H 'X-API-Key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  --data-raw '{"user_id":"ayan@example.com","report_id":105,"civil_status":"civilian","income_action":"replace","person":{"fio1":"SARSENBAYEV","fio2":"AYAN","fio3":"","iin":"XXXXXXXXXXXX","declaration_type":"dt_regular","agreement":"false","sra_code":"6001","phone":"+77000000000","email":"ayan@example.com"}}'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/declarations/270'
headers = {'X-API-Key': 'YOUR_API_KEY'}
payload = {
  "user_id": "ayan@example.com",
  "report_id": 105,
  "civil_status": "civilian",
  "income_action": "replace",
  "person": {
    "fio1": "SARSENBAYEV",
    "fio2": "AYAN",
    "fio3": "",
    "iin": "XXXXXXXXXXXX",
    "declaration_type": "dt_regular",
    "agreement": "false",
    "sra_code": "6001",
    "phone": "+77000000000",
    "email": "ayan@example.com"
  }
}
r = requests.post(url, headers=headers, json=payload, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "created": true,
  "declaration_id": 202,
  "form": "270",
  "report_id": 105,
  "is_active": true
}
```

---

### GET `/api/declarations`

**Список деклараций**

Возвращает сформированные JSON declarations.

**Scope:** `declarations:read`

**Примечания:**

- По умолчанию возвращаются только is_active=true. Для диагностики используйте include_inactive=true.
- Фильтры: form=250|270, year, broker, account_id.

**Query parameters:**

- `user_id`: `user@example.com`
- `year`: `2025`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/declarations?user_id=user%40example.com&year=2025' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/declarations'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "year": "2025"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "ok": true,
  "client_id": "1",
  "include_inactive": false,
  "declarations": [
    {
      "declaration_id": 201,
      "form": "250",
      "report_id": 105,
      "is_active": true
    }
  ]
}
```

---

### GET `/api/declarations/<int:declaration_id>/download`

**Скачать декларацию**

Скачивает JSON declaration в user/client scope.

**Scope:** `declarations:read`

**Query parameters:**

- `user_id`: `user@example.com`
- `client_id`: `1`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/declarations/201/download?user_id=user%40example.com&client_id=1' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/declarations/201/download'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "client_id": "1"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
content = r.content
```

**Ответ:**

```
JSON file response
```

---

## Фоновые задачи

Если операция возвращает HTTP 202 и job_id, периодически запрашивайте статус до завершения задачи и используйте результат, возвращённый API.

### GET `/api/jobs/<job_id>`

**Статус фоновой задачи**

Возвращает статус фоновой задачи для выбранного пользователя и налогоплательщика.

**Примечания:**

- Недоступный для текущего API-ключа job_id возвращается как 404.

**Query parameters:**

- `user_id`: `user@example.com`
- `client_id`: `1`

**cURL:**

```bash
curl -X GET 'https://investax.taxexpress.kz/api/jobs/JOB_ID?user_id=user%40example.com&client_id=1' \
  -H 'X-API-Key: YOUR_API_KEY'
```

**Python:**

```python
import requests

url = 'https://investax.taxexpress.kz/api/jobs/JOB_ID'
headers = {'X-API-Key': 'YOUR_API_KEY'}
params = {
  "user_id": "user@example.com",
  "client_id": "1"
}
r = requests.get(url, headers=headers, params=params, timeout=120)
r.raise_for_status()
print(r.json())
```

**Ответ:**

```json
{
  "job_id": "JOB_ID",
  "status": "finished",
  "job_kind": "report_generation",
  "client_id": "1",
  "user_id": "user@example.com",
  "result": {
    "ok": true
  }
}
```

---
