> ## Documentation Index
> Fetch the complete documentation index at: https://oblodai.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

Официальная библиотека для Python. Пакет `oblodai`. Синхронный и асинхронный клиенты, типы (pydantic v2).

**Требования:** Python 3.9+.

***

## Установка

```bash theme={null}
pip install oblodai
```

<Info>
  **Новое в v1.1.0** (текущая версия в PyPI): группы `batches` / `links` / `splits` / `payout_links`,
  методы `create_batch` / `refund_batch` / `send_email` / `resolve` — и **ломающее изменение**
  идемпотентности: вместо авто-`order_id` теперь заголовок `Idempotency-Key` (см. «Ошибки и повторы»).
</Info>

***

## Аутентификация (ключи из окружения)

Ключи берутся в кабинете [my.oblodai.com](https://my.oblodai.com) — см.
[Регистрация и ключи](/guides/get-keys).

```bash theme={null}
export OBLODAI_PUBLIC_ID=oblodai_ваш_public_id
export OBLODAI_SECRET=oblodai_ваш_secret
# необязательно: export OBLODAI_BASE_URL=https://api.oblodai.com
```

```python theme={null}
from oblodai import OblodaiClient

client = OblodaiClient.from_env()  # OBLODAI_PUBLIC_ID / OBLODAI_SECRET / OBLODAI_BASE_URL
```

Или явно:

```python theme={null}
client = OblodaiClient(public_id="...", secret="...", base_url="https://api.oblodai.com")
```

***

## Быстрый старт: принять платёж

```python theme={null}
payment = client.payments.create(
    amount="10",
    currency="USD",
    order_id="order-1",
    to_currency="USDT",
    network="tron",
    url_callback="https://ваш-сайт.ру/oblodai/webhook",
    url_success="https://ваш-сайт.ру/thanks",
)

# редиректите покупателя на payment.url
print(payment.address, payment.url)
```

<Tip>
  Не хотите указывать валюту/сеть за покупателя? Не передавайте `to_currency` и `network` —
  получится «валюто-агностичная» ссылка, где покупатель сам выберет монету на странице оплаты.
</Tip>

### Асинхронно

```python theme={null}
from oblodai import AsyncOblodaiClient

async with AsyncOblodaiClient.from_env() as client:
    payment = await client.payments.create(amount="10", currency="USD", order_id="order-1")
```

***

## Ресурсы и методы

| Группа                             | Методы                                                                                                                                                                                                                                                                           |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client.payments`                  | `create`, `info`, `history`, `services`, `qr`, `resend`, `refund`, `list_accepted`, `set_accepted`, `get_accuracy`, `set_accuracy`, `get_autorefund`, `set_autorefund`, `set_discount`, `list_discounts` · **с v1.1.0:** `create_batch`, `refund_batch`, `send_email`, `resolve` |
| `client.payouts`                   | `create`, `create_mass`, `info`, `history`, `services`, `calculate`, `approve`, `get_fee_config`, `set_fee_config`, `get_refund_fee_config`, `set_refund_fee_config`, `refund` · **с v1.1.0:** `create_batch`                                                                    |
| `client.batches` **(v1.1.0)**      | `info`                                                                                                                                                                                                                                                                           |
| `client.links` **(v1.1.0)**        | `create`, `list`, `info`, `toggle`, `public_get`, `checkout`                                                                                                                                                                                                                     |
| `client.payout_links` **(v1.1.0)** | `create`, `create_batch` (до 500), `list`, `info`, `cancel` + публичные без подписи: `claim_info(token)`, `claim(token, address=…, memo=…)`                                                                                                                                      |
| `client.splits` **(v1.1.0)**       | `split_to_address`, `split_to_merchant`, `list_rules`, `delete_rule`, `get_config`, `set_config`                                                                                                                                                                                 |
| `client.wallets`                   | `create`, `block`, `blocked_address_refund`, `qr`                                                                                                                                                                                                                                |
| `client.account`                   | `balance`, `referral`, `transfer_to_personal`, `vrcs`                                                                                                                                                                                                                            |
| `client.webhooks`                  | `register`, `deliveries`, `test_payment`, `test_wallet`, `test_payout`                                                                                                                                                                                                           |
| `client.settings`                  | `list_auto_withdraw`, `set_auto_withdraw`, `delete_auto_withdraw`, `list_allowlist`, `add_allowlist`, `remove_allowlist`, `enable_allowlist`                                                                                                                                     |
| `client.rates`                     | `list` (курсы), `currencies` (публичный каталог)                                                                                                                                                                                                                                 |

Точные поля — в [Справочнике](/reference/overview). У синхронного и асинхронного клиента API одинаков.

<Info>
  Всё, что помечено «с v1.1.0», доступно начиная с версии **1.1.0** — если у вас стоит v1.0.x,
  обновите пакет (`pip install -U oblodai`). Свой ключ идемпотентности в методах создания —
  kwarg `idempotency_key` (уйдёт в заголовок).
</Info>

```python theme={null}
# Массовые операции: до 5000 элементов одним подписанным запросом (одна отметка rate-limit).
sub = client.payments.create_batch([
    {"amount": "10", "currency": "USD", "order_id": "a-1", "to_currency": "USDT", "network": "tron"},
    {"amount": "20", "currency": "EUR", "order_id": "a-2", "to_currency": "USDT", "network": "tron"},
], on_error="continue")            # "continue" (по умолчанию) или "stop"

info = client.batches.info(sub.batch_id, limit=100)   # прогресс и результат по каждому элементу
if info.done:
    print(info.succeeded, info.failed)

# Платёжная ссылка: платят многие, каждый платёж — свой инвойс. Работает без вашего бэкенда.
link = client.links.create(amount_mode="open", currency="USD")

# Сплит: доля каждого входящего платежа автоматически уходит партнёру.
client.splits.split_to_address(address="T...", network="tron", percent=10, note="партнёр А")

# Счёт на e-mail (письмо с кнопкой «Оплатить»).
client.payments.send_email(uuid=payment.uuid, email="buyer@example.com")

# Резолв недоплаты: принять частичную оплату (глушит авто-возврат) или вернуть плательщику.
client.payments.resolve(uuid=payment.uuid, action="accept")   # или action="refund"
```

**Крипто-чек за 4 строки** — выплата без адреса получателя (заберёт сам по ссылке):

```python theme={null}
check = client.payout_links.create(
    amount="25", currency="USDT", network="tron",
    reference="bonus-42", expires_in_hours=72, email="winner@example.com",
)
print(check.claim_url)  # отдайте получателю — он введёт свой адрес сам
```

* Задавайте `expires_in_hours` **явно**: без него чек живёт всего **1 час**.
* `claim_url` / `claim_token` возвращаются **только из `create`, один раз** — сохраните сразу.
* Дедупликация — поле `reference` (заголовок `Idempotency-Key` на этих эндпоинтах не действует).

Подробности — [Массовые операции](/guides/batch-operations),
[Платёжные ссылки](/guides/payment-links), [Сплит-платежи](/guides/split-payments),
[Счета на e-mail](/guides/email-invoices), [Крипто-чеки](/guides/payout-links),
[Резолв платежа](/reference/payment-resolve).

***

## Валюта цены и валюта расчёта

В примере выше `currency="USD"` — **валюта цены**, а `to_currency="USDT"` — **валюта расчёта**.
Это разные вещи:

* **`currency` (цена)** — одна из **23 фиатных валют**: USD, EUR, GBP, RUB, UAH, PLN, CZK, TRY, CNY,
  INR, BRL, CAD, AUD, CHF, AED, ZAR, MXN, IDR, THB, VND, NGN, JPY, KRW — **или любая монета**
  (USDT, BTC, TRX, …).
* **`to_currency` (расчёт)** — **только крипта.** Фиата здесь не бывает: баланс, выплаты и возвраты
  всегда в монете, шлюз не хранит фиат.
* У **JPY и KRW ноль знаков** после запятой (`amount="10000"`, не `"10000.00"`); у остальных — 2.
* **KZT, KGS, UZS пока не поддерживаются** → `payment.unknown_currency`.
* Фиатная цена + **только** `network` без `to_currency` → `400 payment.to_currency_required`.
  Либо задайте `to_currency`, либо не задавайте **ни то, ни другое** (тогда монету выберет покупатель).

Полный список валют цены — `client.rates.currencies()`, поле `pricing_currencies`.

***

## Проверьте, что заработало

1. Запустите код создания платежа из «Быстрого старта». В ответе должен прийти `url` — это ссылка на
   hosted-страницу оплаты. Откройте её в браузере: если страница открылась, приём платежей настроен.
2. Чтобы реально **поймать вебхук** об оплате на локальной машине, нужен публичный HTTPS-адрес —
   поднимите туннель (ngrok / cloudflared) и укажите его URL в `url_callback`. Подробнее — в
   [Тестировании](/guides/testing) и [Настройке вебхуков](/guides/webhooks-setup).

***

## Вебхуки

```python Шаг 1. Регистрация (один раз) theme={null}
endpoint = client.webhooks.register("https://ваш-сайт.ру/oblodai/webhook")
webhook_secret = endpoint.secret  # сохраните — им проверяются вебхуки (не API-секрет!)
```

```python Шаг 2. Приём (Flask, сырое тело) theme={null}
from flask import Flask, request
from oblodai import verify_webhook, OblodaiSignatureError

app = Flask(__name__)

@app.post("/oblodai/webhook")
def webhook():
    raw = request.get_data()  # сырые байты, не request.json!
    if b'"is_test"' in raw:   # тестовые вебхуки не подписаны
        return "", 200
    try:
        # третий аргумент — сами заголовки (mapping); функция сама достанет
        # X-Webhook-Timestamp / X-Webhook-Signature и проверит окно свежести.
        verify_webhook(webhook_secret, raw, request.headers)
    except OblodaiSignatureError:
        return "", 403

    event = request.get_json()
    info = client.payments.info(uuid=event["uuid"])
    if info.payment_status in ("paid", "paid_over"):
        ...  # пометить заказ info.order_id оплаченным (идемпотентно)
    return "", 200
```

Оплаченными считайте только `paid` и `paid_over`.

***

## Ошибки и повторы

```python theme={null}
from oblodai import OblodaiAPIError

try:
    client.payouts.create(...)
except OblodaiAPIError as e:
    e.code       # "payout.insufficient_funds" — ветвитесь по коду
    e.status     # HTTP-статус
    e.is_retriable
```

Клиент **сам** повторяет 5xx/429/сетевые сбои с backoff и учётом `Retry-After` — **повторы включены по
умолчанию**. Отключить: `retry=None`.

**Повтор безопасен, но механизм зависит от версии:**

|                  | **v1.0.x** (предыдущая)                             | **v1.1.0** (текущая, в PyPI)                                        |
| ---------------- | --------------------------------------------------- | ------------------------------------------------------------------- |
| Защита от дублей | `order_id`: не задали — **SDK подставит свой**      | заголовок `Idempotency-Key`, одинаковый во всех внутренних повторах |
| Ваш `order_id`   | если не задан — в платеже будет сгенерированный SDK | уходит **как есть**, SDK не подставляет и не переписывает           |
| Свой ключ        | `idempotency_key` **не существует**                 | можно передать `idempotency_key` — уйдёт в заголовок                |

В обеих версиях таймаут/обрыв сети не создаст дубль счёта или перевода. Для **выплат** `order_id`
обязателен всегда — его задаёте вы (иначе `payout.order_id_required`).

***

## Логи и отладка

Включите логи одной переменной окружения — увидите каждый запрос/ответ/ретрай:

```bash theme={null}
export OBLODAI_LOG=debug   # уровни: debug · info · warning · error
```

Строки идут в stderr с префиксом `oblodai:` (метод, путь, статус, номер попытки, задержка ретрая).
**Секреты, подпись и тела запросов в лог не попадают.** Вместо переменной можно задать свой логгер: настройте логгер `logging.getLogger("oblodai")`.

<Note>
  **Проверяете подпись вебхука статичным (старым) вектором?** По умолчанию действует окно свежести
  5 минут — старый `timestamp` отклонит replay-защита (валидная подпись → всё равно ошибка). Для
  офлайн-проверки задайте окно 0: `verify_webhook(secret, raw, headers, max_age_seconds=0)`.
</Note>

***

## Если не получилось

| Симптом                                 | Причина и что делать                                                                                                                          |
| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `OblodaiAPIError` `401` на каждый вызов | Не заданы `OBLODAI_PUBLIC_ID`/`OBLODAI_SECRET` или перепутаны. SDK подписывает сам.                                                           |
| `ValueError` при `from_env()`           | Обязательная переменная окружения не задана (её имя — в тексте ошибки).                                                                       |
| Вебхук не проходит проверку             | Нужно **сырое** тело — `request.get_data()`, а **не** `request.json`; и **webhook‑секрет** из `webhooks.register()`, не API‑секрет.           |
| Вебхук вообще не приходит               | Не зарегистрировали endpoint (`webhooks.register(url)`) или сайт недоступен из интернета.                                                     |
| `429`                                   | SDK повторяет сам с учётом `Retry-After`. Не поллите `payments.info()` в цикле. Много операций — шлите их [пачкой](/guides/batch-operations). |

Полная диагностика — [Что делать, если не работает](/guides/troubleshooting).

## Связанные страницы

<CardGroup cols={2}>
  <Card title="Обзор SDK" href="/sdk/overview" icon="arrow-right" horizontal />

  <Card title="Приём первого платежа" href="/guides/accept-first-payment" icon="arrow-right" horizontal />

  <Card title="Настройка вебхуков" href="/guides/webhooks-setup" icon="arrow-right" horizontal />

  <Card title="Коды ошибок" href="/reference/errors-catalog" icon="arrow-right" horizontal />
</CardGroup>
