> For the complete documentation index, see [llms.txt](https://wiki.indeepa.com/2.0/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.indeepa.com/2.0/vzaimodeistvie-s-indeepa-cherez-api/kak-skachivat-dannye-iz-otchyotov-indeepa-po-api.md).

# Как скачивать данные из отчётов INDEEPA по API

Эта инструкция универсальна. Неважно, для какого маркетплейса вы используете репрайсер — Ozon, Wildberries или Яндекс Маркет. Общая структура API-запроса одинакова для разных отчётов, но набор дополнительных параметров и состав возвращаемых данных могут отличаться.

**API** — это «окно» в систему INDEEPA, через которое можно автоматически получать данные отчётов без ручного экспорта. Его можно использовать в своих программах (Python, PowerShell, cURL и др.) для автоматической выгрузки данных, интеграции с дашбордами или анализа.

### Шаг 1. Возьмите свою ссылку из INDEEPA

1. Перейдите в раздел **«Отчёты»** и выберите нужный отчёт.

<figure><img src="/files/txpLmF1AJjrFW29QWtL9" alt=""><figcaption></figcaption></figure>

2. Для примера используется «Отчет по маржинальности Ozon». В параметрах отчёта укажите период, за который нужны данные, и нажмите кнопку **«Применить»**, чтобы отчёт обновился.

<figure><img src="/files/7O5wbYpditaJbdnrcNJZ" alt=""><figcaption></figcaption></figure>

Если отчёт использует параметры периода, даты можно позже изменить прямо в API-ссылке — об этом в Шаге 2.

3. После того как отчёт сформировался, нажмите в верхней части страницы кнопку **«Ссылка на API отчёта»**.

<figure><img src="/files/KI6TdpBBf3LNvVw0L2Zb" alt=""><figcaption></figcaption></figure>

4. В браузере автоматически откроется новая вкладка, и в ней начнёт загружаться информация. Рабочая ссылка для автоматизации — это **полный URL** из адресной строки той новой вкладки, которая открылась после нажатия кнопки. Именно её вы будете использовать в скриптах, Excel или других инструментах.

<figure><img src="/files/I3evMIpOZmDRe0L3DGlh" alt=""><figcaption></figcaption></figure>

#### Как получить свои userid, reportid и token

Эти значения нельзя сгенерировать или придумать самостоятельно через API. Все значения уже содержатся в API-ссылке, открытой после нажатия кнопки «Ссылка на API отчёта». Отдельно искать или создавать их не нужно.

* **userid** – ваш идентификатор пользователя. Обычно отображается в URL после входа в систему или в настройках профиля.
* **reportid** – идентификатор отчёта. Откройте нужный отчёт в INDEEPA; его ID часто виден в адресной строке браузера.
* **token** – ключ авторизации. Он автоматически генерируется в ссылке при запросе скачивания именно этого отчёта.

{% hint style="danger" %}
Не публикуйте API-ссылку и не передавайте её третьим лицам: `token` и `userid` могут предоставить доступ к данным отчёта. Храните ссылку в защищённом месте.\
Полную ссылку можно отправить сотруднику поддержки только по его прямому запросу через официальный канал INDEEPA.
{% endhint %}

### Шаг 2. Разбираемся с параметрами ссылки

**Пример того, как выглядит ссылка:**

<https://api.indeepa.com/prod/analytics2/getreportdatabyid?**reportid**=66ddd377-9319-4639-b016-3b8ffd1e9992&**userid**=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&**token**=5ae7f258-хххх-хххх-хххххх-e35хххххххd9b&**skip**=0&**take**=100&**parameters.varDateFrom**=2026-06-01&**parameters.varDateTo**=2026-06-07>

В примере выше параметры выделены только для вашей визуализации — чтобы вы сразу видели, о какой части ссылки пойдёт речь ниже.&#x20;

Все настройки выгрузки передаются в конце ссылки, после знака **«?»**. Каждый параметр отделён символом **«&»**.

<table data-header-hidden data-search="false"><thead><tr><th valign="top"></th><th valign="top"></th><th valign="top"></th><th valign="top"></th></tr></thead><tbody><tr><td valign="top"><strong>ПАРАМЕТР</strong></td><td valign="top"><strong>ЧТО ОЗНАЧАЕТ</strong></td><td valign="top"><strong>КАК МЕНЯТЬ</strong></td><td valign="top"><strong>ПРИМЕР</strong></td></tr><tr><td valign="top"><strong>reportid</strong></td><td valign="top">Указывает, какой именно отчёт выгружать</td><td valign="top">Не меняйте — берите из своей ссылки</td><td valign="top">reportid=a1b2c3d4-...</td></tr><tr><td valign="top"><strong>userid</strong></td><td valign="top">Ваш идентификатор в системе INDEEPA</td><td valign="top">Не меняйте — берите из своей ссылки</td><td valign="top">userid=e5f6g7h8-...</td></tr><tr><td valign="top"><strong>token</strong></td><td valign="top">Ключ доступа к отчёту</td><td valign="top">Не меняйте, <strong>храните в секрете</strong></td><td valign="top">token=x9y8z7w6-...</td></tr><tr><td valign="top"><strong>skip</strong></td><td valign="top">Сколько записей пропустить (для постраничной выгрузки)</td><td valign="top">Следующий <code>skip</code> равен предыдущему <code>skip</code> плюс <code>take</code>. Например, при <code>take=1000</code>: <code>0</code>, <code>1000</code>, <code>2000</code>.</td><td valign="top">skip=0</td></tr><tr><td valign="top"><strong>take</strong></td><td valign="top">Сколько записей получить за один раз</td><td valign="top">Максимальное значение — <code>1000</code></td><td valign="top">take=1000</td></tr></tbody></table>

#### **Дополнительные параметры конкретного отчёта**

Набор дополнительных параметров зависит от выбранного отчёта. Такие параметры не являются обязательными и могут отсутствовать в API-ссылке.

Например, отчёт с выбором периода может содержать:

* `parameters.varDateFrom` — дата начала периода;
* `parameters.varDateTo` — дата окончания периода.

В ссылке могут присутствовать дополнительные параметры конкретного отчёта, например идентификатор кабинета `parameters.cabinetId=123`, категория, бренд или другие фильтры. Не удаляйте неизвестные параметры: они могут влиять на состав выгрузки. Меняйте только те параметры, которые вам действительно нужно изменить (чаще всего — даты или пагинацию).

Если назначение параметра непонятно, обратитесь в [службу поддержки](https://wiki.indeepa.com/2.0/sluzhba-podderzhki/kak-obratitsya-v-sluzhbu-podderzhki). Укажите название параметра и приложите скриншот ссылки, предварительно скрыв `token`, `userid` и другие конфиденциальные значения. Полную API-ссылку отправляйте только по прямому запросу специалиста через согласованный канал.

Если отчёт использует параметры `parameters.varDateFrom` и `parameters.varDateTo`, даты указываются в формате `ГГГГ-ММ-ДД`. Например: `2026-05-01` и `2026-05-31`.

### Шаг 3. Как скачать данные

Выберите удобный способ в зависимости от ваших задач:

* **Через браузер:** вставьте ссылку в адресную строку, нажмите Enter. API возвращает данные в формате JSON. Браузер может показать JSON на странице или предложить сохранить его в файл. Для получения CSV данные нужно дополнительно преобразовать, например через PowerShell, Python или Excel. Подходит для разовых выгрузок.
* **Через Excel** (Power Query): Данные → Получить данные → Из веб → вставьте ссылку → Загрузить. Удобно для регулярного обновления отчётов одной кнопкой.
* **Через код** (Python, cURL, PowerShell): для автоматизации и интеграции с внешними системами.

#### Примеры получения данных через код

{% hint style="danger" %}
**Важно:** API-ссылка содержит `token` и `userid`. Не публикуйте её, не передавайте третьим лицам и не сохраняйте в открытых репозиториях.

Во всех примерах ниже замените `ВАША_ПОЛНАЯ_API_ССЫЛКА` на ссылку, скопированную из отчёта INDEEPA. Без этой замены код не сможет получить данные.
{% endhint %}

***

#### cURL: скачать одну страницу в JSON — macOS и Linux

{% hint style="info" %}
Замените `ВАША_ПОЛНАЯ_API_ССЫЛКА` на ссылку, скопированную из отчёта INDEEPA.
{% endhint %}

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```bash
#!/usr/bin/env bash

set -euo pipefail

# Вставьте полную API-ссылку между кавычками.
API_URL="ВАША_ПОЛНАЯ_API_ССЫЛКА"

if [[ "$API_URL" == "ВАША_ПОЛНАЯ_API_ССЫЛКА" ]]; then
    echo "Сначала вставьте полную API-ссылку в переменную API_URL." >&2
    exit 1
fi

timestamp="$(date '+%Y-%m-%d_%H-%M-%S')"

# $$ — идентификатор процесса. Он дополнительно защищает
# от совпадения имён при двух запусках в одну секунду.
output_file="indeepa-report-one-page-${timestamp}-$$.json"

if ! curl \
    --fail \
    --silent \
    --show-error \
    --location \
    "$API_URL" \
    --output "$output_file"
then
    rm -f "$output_file"
    echo "Не удалось получить отчёт через cURL." >&2
    exit 1
fi

echo "Файл сохранён:"
echo "$(pwd)/$output_file"
```

{% endcode %}

После выполнения данные будут сохранены в файл. Пример имени:

```
indeepa-report-one-page-2026-01-01_01-01-01-12345.json
```

#### cURL: скачать одну страницу в JSON — Windows

Код запускается в PowerShell, но сам запрос выполняет `curl.exe`.

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```powershell
# Вставьте полную API-ссылку между кавычками.
$url = "ВАША_ПОЛНАЯ_API_ССЫЛКА"

if ($url -eq "ВАША_ПОЛНАЯ_API_ССЫЛКА") {
    Write-Error "Сначала вставьте полную API-ссылку в переменную `$url."
    exit 1
}

$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss-fff"
$outputFile = "indeepa-report-one-page-$timestamp.json"

& curl.exe `
    --fail `
    --silent `
    --show-error `
    --location `
    $url `
    --output $outputFile

if ($LASTEXITCODE -ne 0) {
    Remove-Item `
        -LiteralPath $outputFile `
        -ErrorAction SilentlyContinue

    Write-Error "Не удалось получить отчёт через cURL."
    exit $LASTEXITCODE
}

Write-Host "Файл сохранён:"
Write-Host ([System.IO.Path]::GetFullPath($outputFile))
```

{% endcode %}

После выполнения данные будут сохранены в файл. Пример имени:

```
indeepa-report-one-page-2026-01-01_01-01-01-123.json
```

***

#### PowerShell: скачать одну страницу в JSON — Windows

Этот вариант не использует cURL. Запрос выполняется средствами PowerShell.

{% hint style="info" %}
Замените `ВАША_ПОЛНАЯ_API_ССЫЛКА` на ссылку, скопированную из отчёта INDEEPA.
{% endhint %}

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```powershell
# Вставьте полную API-ссылку между кавычками.
$url = "ВАША_ПОЛНАЯ_API_ССЫЛКА"

if ($url -eq "ВАША_ПОЛНАЯ_API_ССЫЛКА") {
    Write-Error "Сначала вставьте полную API-ссылку в переменную `$url."
    exit 1
}

$timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss-fff"
$outputFile = "indeepa-report-one-page-$timestamp.json"

try {
    $data = Invoke-RestMethod `
        -Uri $url `
        -Method Get `
        -TimeoutSec 120

    # Оборачиваем результат в массив, чтобы даже одна строка
    # сохранилась как корректный JSON-массив.
    $rows = @($data)

    $json = ConvertTo-Json `
        -InputObject $rows `
        -Depth 100

    Set-Content `
        -LiteralPath $outputFile `
        -Value $json `
        -Encoding UTF8

    Write-Host "Получено строк: $($rows.Count)"
    Write-Host "Файл сохранён:"
    Write-Host ([System.IO.Path]::GetFullPath($outputFile))
}
catch {
    $statusCode = $null

    if (
        $_.Exception.Response -and
        $_.Exception.Response.StatusCode
    ) {
        $statusCode = [int]$_.Exception.Response.StatusCode
    }

    if ($statusCode) {
        Write-Error "API вернул HTTP $statusCode."
    }
    else {
        Write-Error (
            "Не удалось получить отчёт. " +
            "Проверьте API-ссылку, соединение и доступ."
        )
    }

    Remove-Item `
        -LiteralPath $outputFile `
        -ErrorAction SilentlyContinue

    exit 1
}
```

{% endcode %}

После выполнения данные будут сохранены в файл. Пример имени:

```
indeepa-report-one-page-2026-01-01_01-01-01-123.json
```

***

#### Python: скачать одну страницу в JSON

Работает в Windows, macOS и Linux.

> Для запуска потребуется Python 3.9 или новее и библиотека `requests`.

Для примера используется библиотека `requests`.

**Установка:**

```bash
pip install requests
```

Python 3.9 нужен из-за синтаксиса типов:

```python
list[dict[str, Any]]
```

**Код:**

{% hint style="info" %}
Замените `ВАША_ПОЛНАЯ_API_ССЫЛКА` на ссылку, скопированную из отчёта INDEEPA.
{% endhint %}

{% code overflow="wrap" lineNumbers="true" expandable="true" %}

```python
import json
import sys
from datetime import datetime
from pathlib import Path
from typing import Any
from urllib.parse import urlsplit

import requests


# Вставьте полную API-ссылку между кавычками.
API_URL = "ВАША_ПОЛНАЯ_API_ССЫЛКА"


# Дата и время формируются один раз при запуске.
RUN_TIMESTAMP = datetime.now().strftime(
    "%Y-%m-%d_%H-%M-%S-%f"
)

OUTPUT_PATH = Path(
    f"indeepa-report-one-page-{RUN_TIMESTAMP}.json"
)


def validate_api_url() -> None:
    """Проверяет, что указана полная API-ссылка."""
    if API_URL == "ВАША_ПОЛНАЯ_API_ССЫЛКА":
        raise ValueError(
            "Сначала вставьте полную API-ссылку "
            "в переменную API_URL."
        )

    parsed_url = urlsplit(API_URL)

    if (
        parsed_url.scheme not in {"http", "https"}
        or not parsed_url.netloc
    ):
        raise ValueError(
            "API_URL должен содержать полную ссылку, "
            "начинающуюся с http:// или https://."
        )


def describe_request_error(
    error: requests.RequestException,
) -> str:
    """Описывает ошибку без вывода API-ссылки и токена."""
    if isinstance(error, requests.Timeout):
        return "превышено время ожидания ответа"

    if isinstance(error, requests.ConnectionError):
        return "не удалось подключиться к API"

    if (
        isinstance(error, requests.HTTPError)
        and error.response is not None
    ):
        return (
            f"API вернул HTTP "
            f"{error.response.status_code}"
        )

    return type(error).__name__


def main() -> None:
    try:
        validate_api_url()

        response = requests.get(
            API_URL,
            timeout=(10, 120),
        )
        response.raise_for_status()

        data: Any = response.json()

    except ValueError as error:
        print(
            str(error),
            file=sys.stderr,
        )
        raise SystemExit(1)

    except requests.RequestException as error:
        print(
            "Не удалось получить отчёт: "
            f"{describe_request_error(error)}.",
            file=sys.stderr,
        )
        raise SystemExit(1)

    if not isinstance(data, list):
        print(
            "Ожидался JSON-массив, "
            "но API вернул другой формат.",
            file=sys.stderr,
        )
        raise SystemExit(1)

    try:
        # Режим x дополнительно запрещает перезапись файла.
        with OUTPUT_PATH.open(
            "x",
            encoding="utf-8",
        ) as file:
            json.dump(
                data,
                file,
                ensure_ascii=False,
                indent=2,
            )

    except OSError as error:
        print(
            f"Не удалось сохранить файл: {error}",
            file=sys.stderr,
        )
        raise SystemExit(1)

    print(f"Получено строк: {len(data)}.")
    print(
        f"Файл сохранён:\n"
        f"{OUTPUT_PATH.resolve()}"
    )


if __name__ == "__main__":
    main()
```

{% endcode %}

Файл будет сохранён в папке, из которой запущен скрипт или команда.

Пример имени:

```
indeepa-report-one-page-2026-01-01_01-01-01-123456.json
```

***

#### Python: выгрузить все страницы отчёта

Работает в Windows, macOS и Linux.

Этот вариант последовательно меняет `skip` и получает данные частями по 1000 строк.

Вставьте полную API-ссылку в переменную `API_URL`. Скрипт сохранит все параметры отчёта и будет автоматически изменять только `skip` и `take`.

> Для запуска потребуется Python 3.9 или новее и библиотека `requests`.

Для примера используется библиотека `requests`.

**Установка:**

```bash
pip install requests
```

Python 3.9 нужен из-за синтаксиса типов:

```python
list[dict[str, Any]]
```

{% hint style="info" %}
Замените `ВАША_ПОЛНАЯ_API_ССЫЛКА` на полную ссылку, скопированную из отчёта INDEEPA. Скрипт сохранит дополнительные параметры отчёта и будет автоматически изменять только `skip` и `take`.
{% endhint %}

{% code title="" lineNumbers="true" expandable="true" %}

```python
import csv
import hashlib
import json
import sys
import time
from datetime import datetime
from pathlib import Path
from typing import Any
from urllib.parse import (
    parse_qsl,
    urlencode,
    urlsplit,
    urlunsplit,
)

import requests


# Вставьте полную API-ссылку между кавычками.
API_URL = "ВАША_ПОЛНАЯ_API_ССЫЛКА"

# Количество строк, запрашиваемых за один раз.
TAKE = 1000

# Защитный лимит количества страниц.
# При TAKE = 1000 это максимум 1 000 000 строк.
MAX_PAGES = 1000

# Пауза между запросами в секундах.
REQUEST_DELAY_SECONDS = 7.5


# Одна метка времени используется для всех файлов запуска.
RUN_TIMESTAMP = datetime.now().strftime(
    "%Y-%m-%d_%H-%M-%S-%f"
)

FINAL_JSON_PATH = Path(
    f"indeepa-report-all-page-{RUN_TIMESTAMP}.json"
)

FINAL_CSV_PATH = Path(
    f"indeepa-report-all-page-{RUN_TIMESTAMP}.csv"
)

PARTIAL_JSON_PATH = Path(
    f"indeepa-report-all-page-"
    f"{RUN_TIMESTAMP}-partial.json"
)

PARTIAL_CSV_PATH = Path(
    f"indeepa-report-all-page-"
    f"{RUN_TIMESTAMP}-partial.csv"
)


def validate_api_url() -> None:
    """Проверяет, что указана полная API-ссылка."""
    if API_URL == "ВАША_ПОЛНАЯ_API_ССЫЛКА":
        raise ValueError(
            "Сначала вставьте полную API-ссылку "
            "в переменную API_URL."
        )

    parsed_url = urlsplit(API_URL)

    if (
        parsed_url.scheme not in {"http", "https"}
        or not parsed_url.netloc
    ):
        raise ValueError(
            "API_URL должен содержать полную ссылку, "
            "начинающуюся с http:// или https://."
        )


def build_page_url(skip: int) -> str:
    """
    Изменяет skip и take, сохраняя остальные
    параметры исходной API-ссылки.
    """
    parsed_url = urlsplit(API_URL)

    query_pairs = parse_qsl(
        parsed_url.query,
        keep_blank_values=True,
    )

    updated_pairs: list[tuple[str, str]] = []
    skip_added = False
    take_added = False

    for key, value in query_pairs:
        if key == "skip":
            if not skip_added:
                updated_pairs.append(
                    ("skip", str(skip))
                )
                skip_added = True

        elif key == "take":
            if not take_added:
                updated_pairs.append(
                    ("take", str(TAKE))
                )
                take_added = True

        else:
            # Сохраняем дополнительные параметры отчёта,
            # в том числе повторяющиеся.
            updated_pairs.append((key, value))

    if not skip_added:
        updated_pairs.append(
            ("skip", str(skip))
        )

    if not take_added:
        updated_pairs.append(
            ("take", str(TAKE))
        )

    return urlunsplit(
        (
            parsed_url.scheme,
            parsed_url.netloc,
            parsed_url.path,
            urlencode(updated_pairs),
            parsed_url.fragment,
        )
    )


def get_page(
    session: requests.Session,
    skip: int,
) -> list[dict[str, Any]]:
    """Получает одну страницу отчёта."""
    response = session.get(
        build_page_url(skip),
        timeout=(10, 120),
    )

    response.raise_for_status()
    data = response.json()

    if not isinstance(data, list):
        raise TypeError(
            "Ожидался JSON-массив, "
            f"получен объект типа "
            f"{type(data).__name__}."
        )

    if not all(
        isinstance(row, dict)
        for row in data
    ):
        raise TypeError(
            "Ожидался JSON-массив объектов, "
            "но в ответе есть элементы "
            "другого типа."
        )

    return data


def get_page_signature(
    rows: list[dict[str, Any]],
) -> str:
    """
    Создаёт контрольную сумму страницы
    для выявления повторной выдачи.
    """
    serialized = json.dumps(
        rows,
        ensure_ascii=False,
        sort_keys=True,
        separators=(",", ":"),
    )

    return hashlib.sha256(
        serialized.encode("utf-8")
    ).hexdigest()


def save_json(
    rows: list[dict[str, Any]],
    path: Path,
) -> None:
    """Сохраняет данные в JSON."""
    with path.open(
        "x",
        encoding="utf-8",
    ) as file:
        json.dump(
            rows,
            file,
            ensure_ascii=False,
            indent=2,
        )


def save_csv(
    rows: list[dict[str, Any]],
    path: Path,
) -> bool:
    """Сохраняет данные в CSV."""
    if not rows:
        return False

    fieldnames = sorted(
        {
            field
            for row in rows
            for field in row.keys()
        }
    )

    with path.open(
        "x",
        encoding="utf-8-sig",
        newline="",
    ) as file:
        writer = csv.DictWriter(
            file,
            fieldnames=fieldnames,
            extrasaction="ignore",
        )

        writer.writeheader()
        writer.writerows(rows)

    return True


def save_result(
    rows: list[dict[str, Any]],
    *,
    partial: bool,
) -> None:
    """Сохраняет итоговый или частичный результат."""
    if not rows:
        print(
            "Полученных строк нет — "
            "сохранять нечего."
        )
        return

    if partial:
        json_path = PARTIAL_JSON_PATH
        csv_path = PARTIAL_CSV_PATH
        result_type = "Частичный"
    else:
        json_path = FINAL_JSON_PATH
        csv_path = FINAL_CSV_PATH
        result_type = "Итоговый"

    save_json(
        rows,
        json_path,
    )

    csv_created = save_csv(
        rows,
        csv_path,
    )

    print(
        f"{result_type} результат "
        f"сохранён в JSON:\n"
        f"{json_path.resolve()}"
    )

    if csv_created:
        print(
            f"{result_type} результат "
            f"сохранён в CSV:\n"
            f"{csv_path.resolve()}"
        )


def describe_request_error(
    error: requests.RequestException,
) -> str:
    """Описывает ошибку без вывода API-ссылки и токена."""
    if isinstance(error, requests.Timeout):
        return "превышено время ожидания ответа"

    if isinstance(error, requests.ConnectionError):
        return "не удалось подключиться к API"

    if (
        isinstance(error, requests.HTTPError)
        and error.response is not None
    ):
        return (
            f"API вернул HTTP "
            f"{error.response.status_code}"
        )

    return type(error).__name__


def main() -> None:
    try:
        validate_api_url()

    except ValueError as error:
        print(
            str(error),
            file=sys.stderr,
        )
        raise SystemExit(1)

    all_rows: list[dict[str, Any]] = []
    seen_page_signatures: set[str] = set()

    skip = 0
    pages_loaded = 0
    completed = False
    exit_code = 0

    try:
        with requests.Session() as session:
            while True:
                # Защита №1: лимит количества страниц.
                if pages_loaded >= MAX_PAGES:
                    raise RuntimeError(
                        "Достигнут защитный лимит: "
                        f"{MAX_PAGES} страниц. "
                        "Проверьте параметры отчёта "
                        "или увеличьте MAX_PAGES."
                    )

                page = get_page(
                    session,
                    skip,
                )

                pages_loaded += 1

                # Защита №2: получена пустая страница.
                if not page:
                    print(
                        f"Страница {pages_loaded}, "
                        f"skip={skip}: "
                        "получен пустой массив — "
                        "выгрузка завершена."
                    )

                    completed = True
                    break

                page_signature = (
                    get_page_signature(page)
                )

                # Защита №3: API повторно вернул
                # уже загруженную страницу.
                if (
                    page_signature
                    in seen_page_signatures
                ):
                    raise RuntimeError(
                        "API повторно вернул уже "
                        "полученную страницу. "
                        "Возможно, параметр skip "
                        "не применяется. "
                        "Выгрузка остановлена, "
                        "чтобы избежать зацикливания."
                    )

                seen_page_signatures.add(
                    page_signature
                )

                all_rows.extend(page)

                print(
                    f"Страница {pages_loaded}, "
                    f"skip={skip}: "
                    f"получено {len(page)} строк, "
                    f"всего {len(all_rows)}."
                )

                # Защита №4: неполная страница
                # считается последней.
                if len(page) < TAKE:
                    print(
                        "Получено меньше строк, "
                        "чем указано в TAKE — "
                        "это последняя страница."
                    )

                    completed = True
                    break

                skip += TAKE

                time.sleep(
                    REQUEST_DELAY_SECONDS
                )

    except KeyboardInterrupt:
        print(
            "\nВыгрузка остановлена "
            "пользователем. "
            "Сохраняю уже полученные данные...",
            file=sys.stderr,
        )

        exit_code = 130

    except requests.RequestException as error:
        print(
            "Ошибка запроса: "
            f"{describe_request_error(error)}. "
            "Сохраняю уже полученные данные...",
            file=sys.stderr,
        )

        exit_code = 1

    except (
        ValueError,
        TypeError,
        RuntimeError,
    ) as error:
        print(
            "Ошибка обработки выгрузки: "
            f"{error}. "
            "Сохраняю уже полученные данные...",
            file=sys.stderr,
        )

        exit_code = 1

    try:
        save_result(
            all_rows,
            partial=not completed,
        )

    except OSError as error:
        print(
            f"Не удалось сохранить результат: "
            f"{error}",
            file=sys.stderr,
        )

        exit_code = 1

    print(
        f"Получено строк: {len(all_rows)}. "
        f"Обработано страниц: "
        f"{pages_loaded}."
    )

    if exit_code:
        raise SystemExit(exit_code)


if __name__ == "__main__":
    main()
```

{% endcode %}

Файлы `indeepa-report-all-page-<дата и время>.json` и `indeepa-report-all-page-<дата и время>.csv` будут сохранены в папке, из которой запущен скрипт или команда.

При остановке через `Ctrl+C`, ошибке запроса, повторении страницы или достижении защитного лимита будут созданы частичные файлы, если к этому моменту скрипт уже получил хотя бы одну страницу данных.

* `indeepa-report-all-page-<дата и время>-partial.json`
* `indeepa-report-all-page-<дата и время>-partial.csv`

> Подробнее о параметрах `skip` и `take` — в Шаге 4.

***

### Шаг 4. Если данных много: как выгрузить всё по частям (пагинация)

Система не отдаёт все данные сразу — чтобы не перегружать сервер. Поэтому используется постраничная выгрузка через параметры skip (пропустить) и take (взять).

**Какой размер страницы выбрать?**

* `take=100` — для проверки ссылки или небольшой тестовой выгрузки.
* `take=1000` — для основной выгрузки.

Если данных больше 1000 строк, используйте пагинацию через `skip`.

{% hint style="info" %}
Максимальное значение `take` — 1000. За один запрос можно получить не более 1000 записей. Если вам нужно выгрузить большой объём данных, вы можете увеличить take до 1000 — так выгрузка пройдёт быстрее, меньшим количеством запросов.
{% endhint %}

#### **Как считать skip, если take=1000:**

<table data-header-hidden><thead><tr><th valign="top"></th><th valign="top"></th><th valign="top"></th><th valign="top"></th></tr></thead><tbody><tr><td valign="top"><p><strong>Запрос</strong></p><p> </p></td><td valign="top"><p><strong>Skip</strong></p><p> </p></td><td valign="top"><p><strong>Take</strong></p><p> </p></td><td valign="top"><strong>Что получаем</strong></td></tr><tr><td valign="top">1</td><td valign="top">0</td><td valign="top">1000</td><td valign="top">Записи 1–1000</td></tr><tr><td valign="top">2</td><td valign="top">1000</td><td valign="top">1000</td><td valign="top">Записи 1001–2000</td></tr><tr><td valign="top">3</td><td valign="top">2000</td><td valign="top">1000</td><td valign="top">Менее 1000 записей → стоп</td></tr></tbody></table>

#### Как понять, что выгрузка завершена?

* Если в ответе меньше строк, чем указано в take, это последняя порция.
* Если получено ровно take строк, запросите следующую страницу.
* Если следующая страница пустая, выгрузка завершена.

### Если данные не совпали с ожиданиями

* Проверьте даты и дополнительные фильтры в ссылке.
* Убедитесь, что skip увеличивается ровно на значение take: иначе строки могут повторяться или пропускаться.
* Если ссылка перестала открываться, сформируйте новую ссылку через интерфейс отчёта. Не пытайтесь самостоятельно создавать новый token.
* При обращении в [чат поддержки](/2.0/sluzhba-podderzhki/kak-obratitsya-v-sluzhbu-podderzhki.md) укажите название отчёта, период, значения skip и take и опишите ожидаемый результат. Секретные параметры предварительно скройте.

&#x20;


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://wiki.indeepa.com/2.0/vzaimodeistvie-s-indeepa-cherez-api/kak-skachivat-dannye-iz-otchyotov-indeepa-po-api.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
