# Быстрый старт для ИИ и разработчика

Origin: https://leinapi.com. Создайте ключ в https://leinapi.com/#account/keys,
сохраните его в окружении LEIN_API_KEY. Примеры выполняют реальные запросы:
запускайте генерацию только по заданию пользователя и с учётом цены.

## 1. Проверьте доступ без генерации

```sh
curl --fail-with-body https://leinapi.com/v1/models \
  -H "Authorization: Bearer $LEIN_API_KEY"
curl --fail-with-body https://leinapi.com/v1/balance \
  -H "Authorization: Bearer $LEIN_API_KEY"
curl --fail-with-body https://leinapi.com/api/public/pricing
```

Выберите точный id разрешённой модели. Сохраните его в LEIN_MODEL.
Не выбирайте другую модель автоматически при отказе. Каталог /v1/models содержит
data; публичный /api/public/models содержит items и next_cursor. Публичный каталог
пагинируется через limit и after=next_cursor до next_cursor=null.

## 2. Текст через OpenAI SDK

Установите пакет openai в окружение своего приложения. Нужна capability
chat_completions/json. Лимит 512 здесь — выбор примера, а не максимум сервиса.

```python
import os
from openai import OpenAI

client = OpenAI(
    base_url="https://leinapi.com/v1",
    api_key=os.environ["LEIN_API_KEY"],
    max_retries=0,
    timeout=180.0,
)
response = client.chat.completions.create(
    model=os.environ["LEIN_MODEL"],
    messages=[{"role": "user", "content": "Объясни, что такое API."}],
    max_tokens=512,
)
print(response.choices[0].message.content)
print(response.choices[0].finish_reason)
```

Для Responses выберите responses/json и используйте
client.responses.create(model=..., input="...", max_output_tokens=512).
Не отправляйте max_tokens в Responses. Для Messages и Gemini используйте их
отдельные HTTP-контракты из [текстового справочника](text.md).

## 3. Редактирование изображения одним POST с base64

Установите пакет requests. Нужна capability images/edit с подходящим размером.
LEIN_IMAGE_SIZE — точный size из выбранной output_options или sizes.
Создайте уникальный LEIN_OPERATION_ID для новой операции и сохраните его вместе
с параметрами до отправки. При сетевой неопределённости не меняйте этот ID.
Этот код не создаёт предварительную загрузку и не использует ID входного файла.

```python
import base64
import os
import time
from pathlib import Path
import requests

origin = "https://leinapi.com"
headers = {"Authorization": "Bearer " + os.environ["LEIN_API_KEY"]}
image = "data:image/png;base64," + base64.b64encode(Path("reference.png").read_bytes()).decode("ascii")
payload = {
    "model": os.environ["LEIN_MODEL"],
    "prompt": "Замени фон на белый, сохрани основной объект.",
    "size": os.environ["LEIN_IMAGE_SIZE"],
    "image": image,
    "n": 1,
}
response = requests.post(
    origin + "/v1/images/edits",
    headers={**headers, "Idempotency-Key": os.environ["LEIN_OPERATION_ID"], "Prefer": "respond-async"},
    json=payload, timeout=(10, 180),
)
response.raise_for_status()
task_id = response.json()["id"]
Path("leinapi-task.txt").write_text(task_id, encoding="utf-8")
deadline = time.monotonic() + 1800
while time.monotonic() < deadline:
    state = requests.get(origin + "/v1/tasks/" + task_id, headers=headers, timeout=(10, 30))
    state.raise_for_status()
    task = state.json()
    if task["status"] == "succeeded":
        for index in range(task["result_count"]):
            result = requests.get(origin + "/v1/tasks/" + task_id + "/content",
                                  params={"index": index}, headers=headers, timeout=(10, 60))
            result.raise_for_status()
            mime = result.headers.get("Content-Type", "").split(";")[0]
            if mime not in {"image/png", "video/mp4"}:
                raise RuntimeError("Unexpected result type")
            suffix = ".png" if mime == "image/png" else ".mp4"
            Path(f"result-{index + 1}{suffix}").write_bytes(result.content)
        break
    if task["status"] in {"failed", "cancelled"}:
        raise RuntimeError("Task ended: " + task["status"])
    time.sleep(5)
else:
    raise TimeoutError("Task ID saved; resume polling without submitting a new generation")
```

Если опрос прервался, прочитайте сохранённый task_id и продолжите GET. Если POST
потерял ответ, повторите только прежнюю операцию с тем же ключом, телом и исходником.
В этом минимальном примере HTTP-ошибки останавливают выполнение; правила 429 и
неопределённых исходов — в [справочнике ошибок](errors-idempotency.md).

Генерация без исходника: /v1/images/generations, уберите image и выберите capability
images/generation. Видео: /v1/videos с полями из [видеоконтракта](videos.md),
затем тот же цикл /v1/tasks. JSON-расширения медиа через OpenAI SDK отправляйте
client.post("/images/edits", cast_to=dict, body=payload, options={"headers": ...}),
а не подменяйте ими стандартную multipart-сигнатуру images.edit.
