Circassian AI API

Перевод и синтез речи для кабардинского и адыгейского языков. Один ключ, обычный REST, никаких SDK.

Быстрый старт

  1. Получите ключ. Регистрации нет — ключи выдаются вручную. Напишите владельцу сервиса, укажите, что собираетесь делать и примерный объём.
  2. Проверьте, что ключ жив. Один запрос, ничего не расходует.
curl https://api.circassian.ai/v1/me \
  -H "Authorization: Bearer $CIRCASSIAN_API_KEY"

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

{
  "name": "Мой продукт",
  "prefix": "ck_a1b2c3d4e5f6",
  "limits": {
    "requests_per_minute": 60,
    "requests_per_day": 5000,
    "translate_chars_per_day": 500000,
    "speech_seconds_per_day": 3600
  },
  "content_logging": true
}

Ключ доступа

Ключ передаётся в каждом запросе заголовком Authorization:

Authorization: Bearer ck_a1b2c3d4e5f6.LmNvbnRlbnQtc2VjcmV0LXZhbHVl

Ключ состоит из двух частей через точку: публичного префикса и секрета. Секрет показывается один раз при выдаче и нигде больше не отображается — сохраните его сразу.

Ключ — это доступ к вашему счёту расхода. Держите его на сервере, а не в коде мобильного приложения и не в браузерном JavaScript: оттуда его достанет любой желающий. Если ключ утёк — сообщите владельцу, отзыв действует немедленно, и вы получите новый.

Перевод текста

POST /v1/translate

Языки: ru, en, tr, kbd (кабардинский), ady (адыгейский). Поле source_lang можно не указывать — язык определится сам, но с ним ответ приходит быстрее.

curl https://api.circassian.ai/v1/translate \
  -H "Authorization: Bearer $CIRCASSIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "text": "Привет, как дела?",
    "source_lang": "ru",
    "target_lang": "kbd"
  }'

Ответ:

{
  "text": "ФӀэхъус, дауэ ущыт?",
  "source_lang": "ru",
  "target_lang": "kbd"
}
Палочка приводится автоматически. Символы, которыми люди заменяют «Ӏ» — латинская I, единица, вертикальная черта — распознаются и заменяются на настоящую палочку перед обращением к модели. Присылайте текст как есть.

Пакетный перевод

POST /v1/translate/batch

До 50 текстов и 20 000 символов суммарно за запрос. Результаты приходят в том же порядке, что и присланные тексты. Один пакет заметно быстрее, чем 50 отдельных запросов, и экономит лимит по частоте.

curl https://api.circassian.ai/v1/translate/batch \
  -H "Authorization: Bearer $CIRCASSIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "texts": ["Доброе утро", "Спасибо", "До свидания"],
    "source_lang": "ru",
    "target_lang": "ady"
  }'

Словарь

POST /v1/dictionary

Словарные значения одного слова. В отличие от перевода, не расходует квоту символов — считается как обычный запрос.

curl https://api.circassian.ai/v1/dictionary \
  -H "Authorization: Bearer $CIRCASSIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"word": "вода", "lang": "ru", "dialect": "kbd"}'

Определение языка

POST /v1/detect

Определяет язык текста, ничего не переводя. Полезно, когда нужно понять, на каком языке пишет пользователь, до того как решать, что с этим делать.

curl https://api.circassian.ai/v1/detect \
  -H "Authorization: Bearer $CIRCASSIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Сэлам алейкум"}'

# {"language": "kbd"}

Синтез речи

POST /v1/speech

Голоса: male и female — те же, что звучат в боте и на сайте. Языки синтеза: kbd, ady, ru, en.

Турецкий доступен в переводе, но не в синтезе. Запрос с "language": "tr" будет отклонён с ошибкой invalid_request.

Короткий текст (до 1500 символов) озвучивается сразу: приходит файл audio/wav, а длительность — в заголовке X-Audio-Duration.

curl https://api.circassian.ai/v1/speech \
  -H "Authorization: Bearer $CIRCASSIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"text": "Сэлам, узыншэу ущыт", "language": "kbd", "voice": "female"}' \
  --output speech.wav

Свой голос вместо готового

Два дополнительных способа задать голос:

ПолеЧто делает
instructОписание голоса словами по-английски: "calm elderly man, slow". Заменяет выбор voice.
ref_audio_urlСсылка на образец голоса — синтез повторит его тембр. Файл используется только во время генерации и не сохраняется.
speedСкорость речи, от 0.2 до 2.0. По умолчанию 0.85.

Длинный текст: задания

Текст длиннее 1500 символов не озвучивается в одном запросе — соединение не переживёт долгую генерацию. Вместо аудио приходит идентификатор задания, а результат забирается отдельно.

GET /v1/jobs/{job_id}
GET /v1/audio/{artifact_id}

Ответ на длинный текст:

{
  "job_id": "5960d90303ab4626acccf69d980d1593",
  "status": "queued",
  "poll_url": "/v1/jobs/5960d90303ab4626acccf69d980d1593"
}

Состояния задания: queuedrunningdone либо failed. Когда задание готово, в ответе появляются ссылка на аудио и длительность. Ссылка живёт час, потом файл удаляется.

Готовый клиент, который сам разбирается, вернулся ли файл сразу или пришло задание:

import os, time, httpx

API = "https://api.circassian.ai"
KEY = os.environ["CIRCASSIAN_API_KEY"]
AUTH = {"Authorization": f"Bearer {KEY}"}


def synthesize(text: str, language: str = "kbd", voice: str = "male") -> bytes:
    """Возвращает wav независимо от длины текста."""
    r = httpx.post(
        f"{API}/v1/speech",
        headers=AUTH,
        json={"text": text, "language": language, "voice": voice},
        timeout=120,
    )
    if r.status_code != 200:
        raise RuntimeError(r.json()["error"]["message"])

    # Короткий текст — аудио пришло сразу.
    if r.headers["content-type"].startswith("audio/"):
        return r.content

    # Длинный текст — ждём задание.
    job_id = r.json()["job_id"]
    while True:
        time.sleep(2)
        job = httpx.get(f"{API}/v1/jobs/{job_id}", headers=AUTH, timeout=30).json()

        if job["status"] == "done":
            audio = httpx.get(f"{API}{job['audio_url']}", headers=AUTH, timeout=60)
            return audio.content
        if job["status"] == "failed":
            raise RuntimeError(job["error"])


with open("long.wav", "wb") as f:
    f.write(synthesize("Длинный текст…" * 200))
Опрашивайте не чаще раза в две секунды. Каждый запрос статуса расходует лимит по частоте. Секундная генерация речи занимает примерно десятую долю секунды, так что текст на 5000 символов будет готов ориентировочно через минуту.

Голоса и языки

GET /v1/voices
GET /v1/languages

Списки лучше запрашивать, чем зашивать в код — они пополняются.

curl https://api.circassian.ai/v1/voices -H "Authorization: Bearer $CIRCASSIAN_API_KEY"

{
  "voices": [
    {"id": "male",   "title": "Male"},
    {"id": "female", "title": "Female"}
  ],
  "languages": [
    {"id": "kbd", "title": "Kabardian"},
    {"id": "ady", "title": "Adyghe"},
    {"id": "ru",  "title": "Russian"},
    {"id": "en",  "title": "English"}
  ]
}

Лимиты

У каждого ключа четыре независимых ограничения. Текущие значения — в GET /v1/me.

ОграничениеЧто считаетЧто приходит при превышении
Запросов в минутуЛюбые обращения, включая неудачныеrate_limited, заголовок Retry-After: 60
Запросов в суткиТо же, за календарные сутки UTCquota_exceeded
Символов перевода в суткиСимволы входа и выходаquota_exceeded
Секунд синтеза в суткиДлительность полученного аудиоquota_exceeded

Отдельно ограничено число одновременных обращений к моделям. Если свободного места нет дольше положенного, приходит queue_timeout — соединение при этом не рвётся, запрос просто можно повторить. Синтез выполняется по одной задаче за раз, поэтому в час пик длинные тексты лучше отправлять заданиями, а не ждать.

Ошибки

Формат ответа при любой ошибке одинаков — разбирайте error.type, а не текст сообщения:

{
  "error": {
    "type": "quota_exceeded",
    "message": "daily quota of 500000 characters is spent (499880 used), it resets in about 7h"
  }
}
Сообщения приходят на английском. Поле type машиночитаемо и стабильно — стройте логику на нём. Текст в message написан для разработчика и не предназначен для показа вашим пользователям как есть.
ТипКодЧто делать
unauthorized401Проверить ключ. Возможно, он отозван — запросить новый
invalid_request422Исправить запрос: язык, длину, набор полей. Повтор не поможет
rate_limited429Подождать Retry-After секунд и повторить
quota_exceeded429Дневной лимит исчерпан. Повторять до обновления бесполезно
queue_timeout503Модель занята. Повторить через несколько секунд
upstream_unavailable503, 504Модель недоступна. Повторить с нарастающей паузой
upstream_error502Сбой на стороне модели. Повторить один раз, затем сообщить
not_found404Задание или файл не существует, либо срок ссылки истёк
internal_error500Ошибка сервиса. Повторить позже и сообщить, если повторяется

Повтор с нарастающей паузой — только для 429, 502, 503 и 504. Ошибки 4xx кроме 429 повторять бессмысленно, запрос надо исправить.

import time, httpx

RETRIABLE = {429, 502, 503, 504}


def request_with_retry(method: str, url: str, attempts: int = 4, **kwargs):
    for attempt in range(attempts):
        r = httpx.request(method, url, **kwargs)
        if r.status_code not in RETRIABLE:
            return r

        # Сервис сам говорит, когда повторять — слушаем его.
        pause = int(r.headers.get("Retry-After", 2 ** attempt))
        if r.json().get("error", {}).get("type") == "quota_exceeded":
            break  # суточный лимит: ждать до завтра, а не повторять
        time.sleep(pause)
    return r

Данные

Что сохраняется по каждому обращению всегда: время, ключ, операция, модель, объём в символах или секундах аудио, успех или тип ошибки, адрес обращения. Это нужно для учёта расхода и разбора сбоев.

Содержимое запросов и ответов — исходный текст и результат — хранится 30 дней, после чего удаляется автоматически. Это нужно, чтобы разбирать жалобы на качество перевода и сбои.

Сохранение содержимого отключается для отдельного ключа: напишите владельцу, и тексты по вашему ключу перестанут сохраняться. Учёт расхода при этом продолжит работать — лимиты и счётчики от логов не зависят.

Референсное аудио для клонирования голоса не сохраняется: оно используется во время генерации, в логе остаётся только отметка о факте использования. Сгенерированные файлы удаляются вместе с истечением срока ссылки.

Все операции

МетодПутьНазначениеКлюч
POST/v1/translateПеревод текстанужен
POST/v1/translate/batchПеревод пакета текстовнужен
POST/v1/dictionaryСловарные значения слованужен
POST/v1/detectОпределение языканужен
POST/v1/speechСинтез речинужен
GET/v1/jobs/{job_id}Состояние заданиянужен
GET/v1/audio/{artifact_id}Готовое аудионужен
GET/v1/voicesГолоса и языки синтезанужен
GET/v1/languagesЯзыки переводанужен
GET/v1/meСведения о ключе и лимитахнужен
GET/healthСостояние сервисане нужен
GET/openapi.jsonСхема OpenAPIне нужен

Схема OpenAPI подходит для генерации клиента под ваш язык.