Загрузка и расшифровка
Пошаговый сценарий — загрузите аудио через API, дождитесь расшифровки и заберите текст, отчеты или Markdown-экспорт. Плюс пакетная загрузка папки.
Весь цикл — три вызова: загрузить файл, опросить статус до готовности, прочитать результат. В этом руководстве соберем небольшой рабочий скрипт — и прогоним через него целую папку записей.
Создайте ключ с правом загрузки
Для загрузки нужно явное разрешение на ключе. В Настройки → API & MCP нажмите Создать
ключ и включите Разрешить загрузку файлов. Ключи без этой опции (включая все старые) — только
для чтения и получают 403 upload_scope_required при загрузке.
Загрузите файл
Один POST с файлом в теле запроса (полный справочник):
import httpx
BASE = "https://app.memoai.tech/api/v1/developer"
HEADERS = {"Authorization": "Bearer mk_live_your_key_here"}
def upload(path: str, language: str | None = None, ai_metadata: bool = False) -> str:
"""Загружает медиафайл, возвращает UUID расшифровки."""
params = {"ai_metadata": str(ai_metadata).lower()}
if language:
params["language"] = language
with open(path, "rb") as f:
resp = httpx.post(
f"{BASE}/transcriptions",
params=params,
headers={
**HEADERS,
"Content-Disposition": f'attachment; filename="{path.split("/")[-1]}"',
},
content=f,
timeout=httpx.Timeout(10, write=None),
)
resp.raise_for_status()
return resp.json()["uuid"]
# ai_metadata=True → сгенерировать и AI-слой (саммари, отчеты) — нужен в шаге 3;
# по умолчанию API-загрузка дает только сырой текст — быстрее.
uuid = upload("standup.mp3", language="ru", ai_metadata=True)
print("в очереди:", uuid)Файлы до 2 ГБ загружаются точно так же — тело стримится с диска.
Дождитесь расшифровки
Опрашивайте раз в 5–10 секунд. Короткая запись обычно готова за пару минут:
import time
def wait(uuid: str, poll_seconds: int = 10) -> dict:
"""Опрашивает статус до терминального."""
while True:
t = httpx.get(f"{BASE}/transcriptions/{uuid}", headers=HEADERS).json()
if t["status"] == "completed":
return t
if t["status"] == "failed":
raise RuntimeError(f"ошибка расшифровки: {t.get('error_code')}")
if t["status"] == "insufficient_balance":
raise RuntimeError("не хватило минут — пополните баланс и загрузите заново")
time.sleep(poll_seconds) # queued / processing
t = wait(uuid)
print(t["title"])
print(t["text"][:500])
for report in t["prompt_results"]:
print("Отчет:", report["name"])queued означает лишь, что файл ждет свободный слот параллельной обработки вашего тарифа —
он стартует сам, делать ничего не нужно.
Используйте результат
text и AI-отчеты уже пришли на прошлом шаге. Нужен файл? Все read-эндпоинты работают с
загруженными расшифровками:
resp = httpx.get(
f"{BASE}/transcriptions/{uuid}/export",
params={"format": "md"},
headers=HEADERS,
)
with open("standup.md", "wb") as f:
f.write(resp.content)Загрузка целой папки
Пачкой загружать безопасно: файлы сверх параллельности тарифа просто ждут в queued, очередь
дренируется сама. Сначала загрузите все, потом опрашивайте:
import random
import uuid as uuidlib
from pathlib import Path
AUDIO = {".mp3", ".m4a", ".wav", ".flac", ".ogg", ".mp4", ".mov", ".mkv"}
def upload_with_retry(path: str, attempts: int = 5) -> str:
# Один Idempotency-Key на ФАЙЛ, общий для всех попыток: даже если ответ
# потерялся в полете, повтор вернет оригинальный uuid, а не дубль
# (и не второе списание).
idem_key = str(uuidlib.uuid4())
for attempt in range(attempts):
try:
with open(path, "rb") as f:
resp = httpx.post(
f"{BASE}/transcriptions",
headers={
**HEADERS,
"Content-Disposition": f'attachment; filename="{Path(path).name}"',
"Idempotency-Key": idem_key,
},
content=f,
timeout=httpx.Timeout(10, write=None),
)
resp.raise_for_status()
return resp.json()["uuid"]
except httpx.HTTPStatusError as e:
if e.response.status_code in (409, 429): # в полете / лимит
# Ждем СЛУЧАЙНУЮ долю Retry-After, а не ровно столько: иначе все
# заблокированные клиенты проснутся одновременно и снова создадут
# ту же очередь, которая только что разошлась.
wait = int(e.response.headers.get("Retry-After", "30"))
time.sleep(random.uniform(0, wait))
else:
raise # прочие 4xx: чинить, не повторять
except httpx.TransportError: # сетевой сбой — безопасно благодаря ключу
time.sleep(random.uniform(1, 5))
raise RuntimeError(f"не получилось: {path}")
uuids = [
upload_with_retry(str(p))
for p in Path("~/записи").expanduser().iterdir()
if p.suffix.lower() in AUDIO
]
results = [wait(u, poll_seconds=30) for u in uuids]
print(f"готово: {len(results)} расшифровок")Здесь встречаются два кода 429, оба временные:
too_many_concurrent_uploads— слишком много параллельныхPOSTодновременно. Загружайте последовательно или уважайтеRetry-After(в секундах).too_many_queued_files— больше 100 файлов в ожидании. Очередь дренируется; повторите после паузы.
С Idempotency-Key ретраи становятся скучными — в хорошем смысле
Один ключ на файл, общий для всех попыток, — и потерянный ответ не может стоить вам дубля
расшифровки: повтор просто вернет оригинальный uuid. Детали — в
справочнике загрузки.
Контроль расходов
Минуты резервируются при старте обработки, поэтому пачка не может уйти в минус: файлы, на которые
баланса не хватило, завершаются статусом insufficient_balance (и не сохраняются). Бюджет перед
большой пачкой проверяйте программно через GET /workspace:
ws = httpx.get(f"{BASE}/workspace", headers=HEADERS).json()
minutes_left = ws["balance"]["available_minutes"] # уже за вычетом активных резерваций
if minutes_left < expected_batch_minutes:
raise SystemExit(f"сначала пополните баланс: осталось {minutes_left} минут")Дальше
- Загрузка файла — справочник API — каждый заголовок, параметр и ошибка.
- Получение расшифровки — полный формат ответа.
- Массовый экспорт — выгрузка готовых расшифровок по 100 за раз.
Аутентификация
Как работают API-ключи Memo AI — создайте ключ mk_live_, передавайте его как Bearer-токен в заголовке Authorization, плюс scope, rate limits и безопасность.
Экспорт встреч в Obsidian
Перенесите встречи Memo AI в хранилище Obsidian как Markdown с YAML-frontmatter — через AI-ассистента без кода или коротким скриптом.