# neuraldeep.tech — LLM API reference (for coding agents) > Full machine-readable reference. Curl-friendly: `curl https://neuraldeep.tech/llms-full.txt`. > Index with pointers: `https://neuraldeep.tech/llms.txt`. Human version: `https://neuraldeep.tech/docs`. > Single document (fits in context). Sections are split by `## ` headers — grep for them. Base URL: `https://api.neuraldeep.ru/v1` (OpenAI-compatible) Auth: `Authorization: Bearer $YOUR_KEY` Available models: - `gpt-oss-120b` — chat · tools · reasoning · 131k ctx · MXFP4 on 2×RTX 4090 48 GB - `qwen3.6-35b-a3b` — chat · qwen3 tools · reasoning · 256k ctx · MoE 35B/3B-active · BF16 on 2× RTX 4090 48GB · vision (1 image/prompt) - `gemma-4-31b` — chat · tools · multimodal (image/video) · 262k ctx · Google Gemma 4 · on our own GPUs - `e5-large` — multilingual embedding · 1024-dim · 3 replicas - `bge-m3` — multilingual embedding · 1024-dim · 8k ctx - `bge-reranker` — cross-encoder rerank - `whisper-1` — WhisperX large-v3-turbo · multilingual · word timestamps · RTF ~16× - `qwen3-tts` / `espeech` — TTS speech synthesis · `/v1/audio/speech` · 8 voices · 10 languages · RU with stress marks ## Sections (deep-links to human-doc anchors) Hub API: - Text · chat — https://neuraldeep.tech/docs#chat - Vectorization — https://neuraldeep.tech/docs#vector - Reranking — https://neuraldeep.tech/docs#rerank - Transcription — https://neuraldeep.tech/docs#transcribe - Image recognition — https://neuraldeep.tech/docs#vision - Structured output — https://neuraldeep.tech/docs#structured - Agents · tools — https://neuraldeep.tech/docs#agents - Search · Search API — https://neuraldeep.tech/docs#search - OCR · documents — https://neuraldeep.tech/docs#ocr - Images · Image API — https://neuraldeep.tech/docs#images - PII anonymization · PII Guard — https://neuraldeep.tech/docs#guardrails - SpeechCore · STT — https://neuraldeep.tech/docs#speechcore - Speech synthesis · TTS API — https://neuraldeep.tech/docs#tts Drift API: - Drift · overview — https://neuraldeep.tech/docs#drift-api - Files + sandbox preview — https://neuraldeep.tech/docs#drift-files - Your own tools, skills, MCP — https://neuraldeep.tech/docs#drift-caller-tools - Tasks · proactive — https://neuraldeep.tech/docs#drift-tasks - Agent builder — https://neuraldeep.tech/docs#agent-hosting - Memory · memory — https://neuraldeep.tech/docs#drift-memory - Isolation and security — https://neuraldeep.tech/docs#drift-security Limits: - Limits · errors — https://neuraldeep.tech/docs#limits Reference: - Streaming — https://neuraldeep.tech/docs#streaming - SDK · Python / JS — https://neuraldeep.tech/docs#sdk - Privacy — https://neuraldeep.tech/docs#privacy ## Text · Chat completion OpenAI-compatible `/v1/chat/completions`. Supports streaming, tools, reasoning (gpt-oss passes reasoning tags through inline), `user` for session-sticky routing. ```bash curl https://api.neuraldeep.ru/v1/chat/completions \ -H "Authorization: Bearer $YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-oss-120b", "messages": [ {"role":"system","content":"You are helpful."}, {"role":"user","content":"Hello"} ], "max_tokens": 500, "temperature": 0.2 }' ``` ```python from openai import OpenAI client = OpenAI(api_key="$YOUR_KEY", base_url="https://api.neuraldeep.ru/v1") r = client.chat.completions.create( model="gpt-oss-120b", messages=[{"role":"user","content":"Hello"}], max_tokens=500, ) print(r.choices[0].message.content) ``` > Send `user: ` to keep the session on one upstream — the KV-cache won't reset, multi-turn is faster. ## Vectorization · Embeddings OpenAI-compatible `/v1/embeddings`. Accepts a string or an array. Returns 1024-dimensional vectors. Deterministic → LiteLLM caches them automatically. ```bash curl https://api.neuraldeep.ru/v1/embeddings \ -H "Authorization: Bearer $YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"e5-large","input":"hello world"}' ``` ```bash curl https://api.neuraldeep.ru/v1/embeddings \ -H "Authorization: Bearer $YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"bge-m3","input":["text 1","text 2","text 3"]}' ``` ```python from openai import OpenAI client = OpenAI(api_key="$YOUR_KEY", base_url="https://api.neuraldeep.ru/v1") texts = ["first document", "second", "third"] r = client.embeddings.create(model="bge-m3", input=texts) vectors = [e.embedding for e in r.data] # list[list[float]], dim=1024 ``` > For RAG — `e5-large` for queries/docs with the "query: " / "passage: " prefix. For accuracy — `bge-m3` (longer context). ## Reranking · Rerank Re-sorts candidates by relevance to the query. Returns a `relevance_score` for each document. Used after vector search for fine-grained top-k ranking. ```bash curl https://api.neuraldeep.ru/v1/rerank \ -H "Authorization: Bearer $YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "bge-reranker", "query": "What is an LLM?", "documents": [ "Large language models are trained on large corpora.", "It is sunny in Moscow today.", "GPT-4 is OpenAI's transformer architecture." ] }' ``` ```python import httpx # topk_docs: list[str] returned by vector search (top 50) r = httpx.post( "https://api.neuraldeep.ru/v1/rerank", headers={"Authorization": f"Bearer $YOUR_KEY"}, json={"model": "bge-reranker", "query": query, "documents": topk_docs}, timeout=15.0, ).json() # results sorted by relevance desc top3 = [topk_docs[x["index"]] for x in r["results"][:3]] ``` > Pipeline: first embeddings → ANN (Qdrant/FAISS) → top 50 → rerank → top 3-5 for the LLM. ## Transcription · Speech-to-Text OpenAI Whisper API format. Accepts multipart/form-data with an audio file. Returns text + timecodes + segments. ```bash curl https://api.neuraldeep.ru/v1/audio/transcriptions \ -H "Authorization: Bearer $YOUR_KEY" \ -F file=@meeting.wav \ -F model=whisper-1 ``` ```python from openai import OpenAI client = OpenAI(api_key="$YOUR_KEY", base_url="https://api.neuraldeep.ru/v1") with open("meeting.wav","rb") as f: r = client.audio.transcriptions.create(model="whisper-1", file=f) print(r.text) ``` > WAV/MP3/M4A/OGG are supported. The shorter the file — the lower the latency (sequential processing). ## Image recognition · Vision Multimodal via the standard `/v1/chat/completions`. Pass `image_url` in the content array (URL or base64). The model describes the image, extracts text, and answers questions about it. ```bash curl https://api.neuraldeep.ru/v1/chat/completions \ -H "Authorization: Bearer $YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.6-35b-a3b", "messages": [{ "role": "user", "content": [ {"type":"text","text":"What's in the image? Extract the text if there is any."}, {"type":"image_url","image_url":{"url":"data:image/jpeg;base64,/9j/..."}} ] }], "max_tokens": 500 }' ``` ```python import base64 from openai import OpenAI with open("photo.jpg","rb") as f: b64 = base64.b64encode(f.read()).decode() client = OpenAI(api_key="$YOUR_KEY", base_url="https://api.neuraldeep.ru/v1") r = client.chat.completions.create( model="qwen3.6-35b-a3b", messages=[{ "role":"user", "content":[ {"type":"text","text":"Briefly describe what's in the photo"}, {"type":"image_url","image_url":{"url":f"data:image/jpeg;base64,{b64}"}}, ], }], max_tokens=400, ) print(r.choices[0].message.content) ``` > Limit — 1 image per request. If you need several — make N sequential requests and stitch the answers together in your own code. ## Structured output · JSON / guided grammar vLLM supports guided JSON, regex, and grammar (llguidance). Via the OpenAI-compatible `response_format`. ```bash curl https://api.neuraldeep.ru/v1/chat/completions \ -H "Authorization: Bearer $YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-oss-120b", "messages": [{"role":"user","content":"Extract name and age from: John, 30 years old"}], "response_format": {"type":"json_object"} }' ``` ```python from pydantic import BaseModel from openai import OpenAI class Person(BaseModel): name: str age: int client = OpenAI(api_key="$YOUR_KEY", base_url="https://api.neuraldeep.ru/v1") r = client.chat.completions.create( model="gpt-oss-120b", messages=[{"role":"user","content":"John, 30 years old"}], response_format={"type":"json_schema","json_schema":{ "name":"Person","schema":Person.model_json_schema(),"strict":True }}, ) person = Person.model_validate_json(r.choices[0].message.content) ``` > For strict JSON — `strict: true` in the schema is required. vLLM guarantees conformance to the grammar. ## Agents · Tool calling Standard OpenAI tool-calling via `tools` + `tool_choice`. The model decides when to call a tool and returns JSON with the arguments. ```python from openai import OpenAI client = OpenAI(api_key="$YOUR_KEY", base_url="https://api.neuraldeep.ru/v1") tools = [{ "type": "function", "function": { "name": "get_weather", "description": "Gets the weather in a city", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"], } } }] messages = [{"role":"user","content":"What's the weather in Moscow?"}] r = client.chat.completions.create( model="gpt-oss-120b", messages=messages, tools=tools, tool_choice="auto", ) # r.choices[0].message.tool_calls → list of calls ``` ```bash curl https://api.neuraldeep.ru/v1/chat/completions \ -H "Authorization: Bearer $YOUR_KEY" \ -H "Content-Type: application/json" \ -d '{ "model":"gpt-oss-120b", "messages":[{"role":"user","content":"tell a short story"}], "stream": true, "max_tokens": 800 }' ``` > For repeated agent iterations use `user: session_id` so the session lives on one upstream — prefix caching saves up to 10× on tokens. #### Coding agents · opencode [opencode](https://opencode.ai) — an open-source coding agent in the terminal. Works via the standard `/v1/chat/completions`, no patches needed. Smoke-tested on both models + tool-calling. ```json { "$schema": "https://opencode.ai/config.json", "provider": { "neuraldeep": { "npm": "@ai-sdk/openai-compatible", "name": "NeuralDeep Hub", "options": { "baseURL": "https://api.neuraldeep.ru/v1", "apiKey": "$YOUR_KEY" }, "models": { "qwen3.6-35b-a3b": { "name": "qwen3.6-35b-a3b · 256k ctx · tools", "limit": { "context": 262144, "output": 16384 } }, "gpt-oss-120b": { "name": "gpt-oss-120b · 131k ctx · reasoning", "limit": { "context": 131072, "output": 8192 } } } } } } ``` ```bash # TUI mode opencode # in the TUI switch the model: /model → neuraldeep/qwen3.6-35b-a3b # non-interactive (one-shot) opencode run --model neuraldeep/qwen3.6-35b-a3b "find all env vars in src/" ``` #### Coding agents · OpenAI Codex CLI [codex CLI](https://www.npmjs.com/package/@openai/codex) — a terminal coding agent from OpenAI. Works via `/v1/responses` (Responses API). On our side we've applied 3 vLLM file patches to support `custom` tool types and multi-turn validation (per issue [#33089](https://github.com/vllm-project/vllm/issues/33089)). ```toml # ----------------- provider ----------------- [model_providers.neuraldeep] name = "NeuralDeep Hub" base_url = "https://api.neuraldeep.ru/v1" wire_api = "responses" experimental_bearer_token = "$YOUR_KEY" # Alternative: env_key = "NEURALDEEP_API_KEY" + export in the shell # ----------------- profiles ----------------- [profiles.neuraldeep] model_provider = "neuraldeep" model = "qwen3.6-35b-a3b" # qwen3.6 → 256k ctx, native tool-calls, better for a large codebase [profiles.neuraldeep-oss] model_provider = "neuraldeep" model = "gpt-oss-120b" # gpt-oss-120b → 131k ctx, long reasoning ``` ```bash # TUI mode with qwen3.6 codex --profile neuraldeep # or with gpt-oss-120b codex --profile neuraldeep-oss # adjust reasoning effort codex --profile neuraldeep -c model_reasoning_effort=low ``` > **wire_api = "chat" is no longer supported** in newer codex (since late 2025). Only `"responses"`. If you see the error `Error loading config.toml: wire_api = "chat" is no longer supported` — switch to responses. ## Search · Search API With the same key — search over Telegram and our TG-channel database, web search, and site crawling. Endpoints under `/v1/search/*`. Available on **all plans, including free**, with a limit separate from chat. Two limit buckets: **search** (TG + channel database + web) and **crawling** (separate — it's heavier). Current balance — `GET /v1/search/quota` or the "Search API" card in the dashboard. When exhausted — `429` with a `Retry-After` header. ```bash # our Telegram-channel database (vector + full-text) curl "https://api.neuraldeep.ru/v1/search/tg?q=AI-agents&limit=5" \ -H "Authorization: Bearer $YOUR_KEY" # web search curl https://api.neuraldeep.ru/v1/search/web \ -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" \ -d '{"query":"latest news about LLMs","limit":3}' ``` ```bash # crawl a site (several pages) curl https://api.neuraldeep.ru/v1/search/crawl \ -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" \ -d '{"url":"https://example.com","limit":3}' # how much is left in both buckets curl https://api.neuraldeep.ru/v1/search/quota \ -H "Authorization: Bearer $YOUR_KEY" ``` ```python import httpx r = httpx.get( "https://api.neuraldeep.ru/v1/search/tg", params={"q": "RAG in production", "limit": 5}, headers={"Authorization": "Bearer $YOUR_KEY"}, ) data = r.json() for item in data.get("results", []): print(item.get("channel"), "—", item.get("text", "")[:120]) print("remaining:", data.get("quota", {}).get("day")) ``` > Web search and crawling cost real provider money, so they have a separate (more modest) bucket. TG search over our database is cheap — the limit is more generous. ## OCR · document recognition With the same key — OCR for PDFs and images (PNG/JPG/WEBP/BMP/TIFF). Endpoints under `/v1/ocr/*`. Available on **all plans**, with a bucket separate from chat — the limit is counted in **pages**. Works **asynchronously**: upload a document → get a `job` → poll the status (no more than once per second) → fetch the result as `markdown`/`json`/`text`. For coordinate-based markup there's a PNG preview of the page (same coordinate system as bbox). Profiles: `fast` (default) and `pro` (higher quality, counted as 2 pages). Balance — `GET /v1/ocr/balance`. When exhausted — `429` with `Retry-After`. ```bash # 1. upload (returns {"id":"job_...","page_count":N}) curl https://api.neuraldeep.ru/v1/ocr/extract \ -H "Authorization: Bearer $YOUR_KEY" \ -F "file=@invoice.pdf" # (higher quality: add -F "model_profile=pro"; # page range: -F 'page_ranges=[{"start":1,"end":5}]') # 2. job status curl https://api.neuraldeep.ru/v1/ocr/jobs/job_123 \ -H "Authorization: Bearer $YOUR_KEY" # 3. result as markdown curl "https://api.neuraldeep.ru/v1/ocr/jobs/job_123/result?format=markdown" \ -H "Authorization: Bearer $YOUR_KEY" ``` ```python import time, httpx H = {"Authorization": "Bearer $YOUR_KEY"} # 1. upload with open("invoice.pdf", "rb") as f: job = httpx.post( "https://api.neuraldeep.ru/v1/ocr/extract", headers=H, files={"file": f}, ).json() jid = job["id"] print("pages:", job["page_count"], "charged:", job["scan_pages_charged"]) # 2. polling (once per second) while True: st = httpx.get(f"https://api.neuraldeep.ru/v1/ocr/jobs/{jid}", headers=H).json() if st["status"] == "completed": break time.sleep(1) # 3. result res = httpx.get( f"https://api.neuraldeep.ru/v1/ocr/jobs/{jid}/result", params={"format": "markdown"}, headers=H, ).json() print(res["content"]) ``` > Status, result, and preview don't consume the limit — you pay only for pages at upload. `pro` = 2 pages per sheet (choose it for complex layout/tables). ## Images · Image API With the same key — image generation and processing under `/v1/images/*`: generation (FLUX), ×4 upscale, background removal, enhancement, avatar. Available on **all plans**, a separate bucket — the limit is counted in **operations** (1 operation = 1 call). Works **asynchronously**: `POST` creates a task → `{task_uid}` → poll `GET /v1/images/tasks/{uid}` until `status=finished` → fetch the image as binary via `GET /v1/images/tasks/{uid}/result` (we don't expose the raw S3 URL). FLUX understands **English only** — a Cyrillic prompt is auto-translated (disable with `translate=false`). Balance — `GET /v1/images/quota`. When exhausted — `429` with `Retry-After`. **Size** is set only via `options.aspect_ratio`: `1:1` · `9:16` · `16:9` · `4:5` · `3:2` · `5:3` · `3:5`. The `width`/`height`/`size` parameters are not supported. If omitted or set to anything else — it defaults to `1:1` (the request doesn't fail). ```bash # 1. generation (the prompt can be in another language — auto-translated to EN) uid=$(curl -s https://api.neuraldeep.ru/v1/images/generate \ -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" \ -d '{"prompt":"astronaut cat, neon","options":{"aspect_ratio":"1:1"}}' | jq -r .task_uid) # 2. task status curl https://api.neuraldeep.ru/v1/images/tasks/$uid \ -H "Authorization: Bearer $YOUR_KEY" # 3. result as a file curl https://api.neuraldeep.ru/v1/images/tasks/$uid/result \ -H "Authorization: Bearer $YOUR_KEY" -o out.png # processing an existing image (multipart): upscale / background/remove / enhance curl https://api.neuraldeep.ru/v1/images/upscale \ -H "Authorization: Bearer $YOUR_KEY" -F "image=@photo.jpg" ``` ```python import time, httpx H = {"Authorization": "Bearer $YOUR_KEY"} # 1. generation job = httpx.post( "https://api.neuraldeep.ru/v1/images/generate", headers=H, json={"prompt": "a cosmic cat in neon", "options": {"aspect_ratio": "1:1"}}, ).json() uid = job["task_uid"] # 2. polling (once per second, keep a timeout — the generation queue sometimes stalls) for _ in range(120): st = httpx.get(f"https://api.neuraldeep.ru/v1/images/tasks/{uid}", headers=H).json() if st["status"] == "finished": break time.sleep(1) # 3. result as binary img = httpx.get(f"https://api.neuraldeep.ru/v1/images/tasks/{uid}/result", headers=H).content open("out.png", "wb").write(img) ``` > Status and result don't consume the limit — you pay only for creating the task. Generation is GPU-expensive; keep a timeout on polling (the queue sometimes stalls regardless of upscale/enhancement). ## PII anonymization · PII Guard API With the same key — personal-data (PII) anonymization under `/v1/pii/*`, so you can build it into **your own** logic (not only via an LLM). It finds names, phone numbers, e-mails, locations, etc., replaces them with tags like ``, and on the way back restores them — with **Russian grammatical inflection**. Available on **all plans**, a bucket separate from chat — the limit is counted in **operations**. **Privacy (important):** the service is **stateless with respect to the mapping** — the tag→original correspondence is returned **to you** in the `/anonymize` response and **immediately deleted** on our side. We don't store the original PII. You keep the mapping and send it back in `/deanonymize`. Two endpoints: - `POST /v1/pii/anonymize` — `{text}` or `{texts:[…]}` → `{anonymized, mapping, system_prompt}`. You can feed `system_prompt` to the model so it handles the tags carefully. Pricing: **1 unit per block ≤2000 chars** of each text. - `POST /v1/pii/deanonymize` — `{text|texts, mapping}` → `{deanonymized}`. Pricing: **1 unit per call** (reverse substitution is cheap). Optionally `entities` in `/anonymize` — a whitelist of types (`["PERSON","PHONE_NUMBER"]`) if you need to catch only some. Balance — `GET /v1/pii/quota`. When exhausted — `429` with `Retry-After`. ```bash # 1. anonymize — save the mapping from the response curl https://api.neuraldeep.ru/v1/pii/anonymize \ -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" \ -d '{"text":"John Smith, +79161234567, lives in Moscow"}' # → {"anonymized":" , , ...", # "mapping":{"":"John Smith", ...}, "system_prompt":"…"} # 2. restore the model's response (tags → originals, with inflection) curl https://api.neuraldeep.ru/v1/pii/deanonymize \ -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" \ -d '{"text":"A letter for ","mapping":{...}}' ``` ```python import httpx H = {"Authorization": "Bearer $YOUR_KEY"} # 1. anonymize the user's text a = httpx.post("https://api.neuraldeep.ru/v1/pii/anonymize", headers=H, json={"text": "John Smith, tel +79161234567, Moscow"}).json() safe, mapping = a["anonymized"], a["mapping"] # 2. send to ANY model WITHOUT PII (system_prompt explains the tags) resp = httpx.post("https://api.neuraldeep.ru/v1/chat/completions", headers=H, json={ "model": "qwen3.6-35b-a3b", "messages": [{"role": "system", "content": a["system_prompt"]}, {"role": "user", "content": safe}], }).json()["choices"][0]["message"]["content"] # 3. restore the PII in the model's response (with the same mapping) final = httpx.post("https://api.neuraldeep.ru/v1/pii/deanonymize", headers=H, json={"text": resp, "mapping": mapping}).json()["deanonymized"] print(final) ``` > `anonymize` latency — ~100–150 ms on a short text. Restoration is almost free on CPU (reverse dictionary + morphology). Want automatic anonymization **right inside the LLM request** (no manual pipe) — the built-in guardrail on the key does that; write to support and we'll enable it selectively for your token. ## SpeechCore · audio/video transcription A separate service [speechcore.neuraldeep.ru](https://speechcore.neuraldeep.ru) — audio/video transcription (recordings up to ~6 hours): speaker diarization, word-level timecodes, export to SRT/VTT/TSV/DOCX/PDF. On the web — sign in with the same Hub account; **via API — with the same `sk-*` key** as for `/v1/*`. For short audio and OpenAI compatibility there's also the synchronous `whisper-1` (the "Transcription" section above) — while SpeechCore is tailored for long recordings and diarization. API base — `https://speechcore.neuraldeep.ru/api` (NOT `api.neuraldeep.ru`). The limit is counted in **transcriptions per day**: free **1** · starter **50** · pro **200**. Works **asynchronously**: `POST /upload` → `{task_id}` → poll `GET /transcriptions/{id}/status` until `status=completed` → fetch the result: `GET /transcriptions/{id}` (JSON with segments, speakers, `detected_language`, `duration`) or `GET /transcriptions/{id}/markdown` (text with timecodes). **Transcription options** — query parameters for `POST /upload` (the file goes in the multipart body, options in the URL): - `diarize=true` — **speaker separation**: segments gain a `speaker` field (`SPEAKER_00`, `SPEAKER_01`…), and the summary shows each speaker's % of speaking time. `diarize_speakers_num=N` (1–20) — hint the exact number of voices if it's known (more accurate than auto-detection). - `language=ru` — fix the language (auto-detection by default). - **Special words / terminology:** `hotwords` — keywords separated by spaces or commas (≤500 chars) that the model will recognize more accurately: names, brands, acronyms, products (`NeuralDeep, Kimi, RAG, FZ-152`). - `initial_prompt` — a context prompt (≤2000 chars): sets the topic/style and improves accuracy on specific vocabulary (`«A technical call about LLM infrastructure»`). - `model` — the Whisper model (default `large-v3`). ```bash # options in the URL query string, file in the multipart body (-F) curl -s "https://speechcore.neuraldeep.ru/api/upload?diarize=true&diarize_speakers_num=3&language=ru&hotwords=NeuralDeep,Kimi,RAG&initial_prompt=A%20technical%20call%20about%20LLM" \ -H "Authorization: Bearer $YOUR_KEY" \ -F "file=@meeting.mp3" ``` ```bash # 1. upload (the same sk-key as for /v1/*) tid=$(curl -s https://speechcore.neuraldeep.ru/api/upload \ -H "Authorization: Bearer $YOUR_KEY" \ -F "file=@meeting.mp3" | jq -r .task_id) # 2. status curl "https://speechcore.neuraldeep.ru/api/transcriptions/$tid/status" \ -H "Authorization: Bearer $YOUR_KEY" # 3. result as text (markdown with timecodes) curl "https://speechcore.neuraldeep.ru/api/transcriptions/$tid/markdown" \ -H "Authorization: Bearer $YOUR_KEY" # your transcriptions as a list curl "https://speechcore.neuraldeep.ru/api/transcriptions?limit=20" \ -H "Authorization: Bearer $YOUR_KEY" ``` ```python import time, httpx BASE = "https://speechcore.neuraldeep.ru/api" H = {"Authorization": "Bearer $YOUR_KEY"} # 1. upload a file (audio/video, up to ~6 h) + options in params params = { "diarize": "true", # speaker separation "diarize_speakers_num": 3, # if the number of voices is known "language": "ru", "hotwords": "NeuralDeep, Kimi, RAG", # special words/terms "initial_prompt": "A technical call about LLM", } with open("meeting.mp3", "rb") as f: tid = httpx.post(f"{BASE}/upload", headers=H, params=params, files={"file": f}).json()["task_id"] # 2. polling (every couple of seconds) while True: st = httpx.get(f"{BASE}/transcriptions/{tid}/status", headers=H).json() if st["status"] in ("completed", "failed"): break time.sleep(2) # 3. result: segments + timecodes, or ready markdown data = httpx.get(f"{BASE}/transcriptions/{tid}", headers=H).json() print("language:", data["detected_language"], "duration:", data["duration"]) print(httpx.get(f"{BASE}/transcriptions/{tid}/markdown", headers=H).text) ``` **Web-interface features** ([speechcore.neuraldeep.ru](https://speechcore.neuraldeep.ru)), beyond the API: - **Speaker diarization** — utterances are labeled by speaker (SPEAKER_00…); you can see who spoke how much (% of time). - **Meeting summary** — a structured LLM recap (topics, decisions, tasks) accounting for speakers. With the same sk-key. - **Chat over the transcript** — questions about the transcript ("what did we decide on X?", "what are the deadlines?") with timecode-referenced answers. - **Export** — SRT · VTT · TSV · DOCX · PDF · TXT, plus markdown with timecodes. - **Special words and context** — hotwords / context-prompt fields right in the upload form (the same as `hotwords`/`initial_prompt` in the API). - **Public link** — share a transcript read-only without sign-in. - **Realtime** — streaming transcription from the microphone/tab (via the "Meeting Recorder" extension). > Polling and fetching the result don't consume the limit — it's spent only at upload (1 file = 1 transcription). Recordings up to ~6 h; upload large files whole — processing runs in the background on GPU, so keep a reasonable timeout on polling. Diarization and special words work the same in the web and via the API. ## Speech synthesis · TTS API With the same key — speech synthesis under `/v1/audio/speech`: text → audio (WAV 24 kHz). The **Qwen3-TTS** model on our GPUs. Available on **all plans**, a separate bucket — the limit is counted in **characters** of input text. The request format is **OpenAI-compatible** and works **synchronously**: send text → get an audio file right away. **8 voices:** `vivian`, `serena`, `ono_anna`, `sohee` (female) · `dylan`, `ryan`, `aiden`, `uncle_fu` (male) — multilingual. **10 languages:** `Russian`, `English`, `German`, `French`, `Spanish`, `Italian`, `Chinese`, `Japanese`, `Korean`, `Portuguese` (+`Auto`). **Emotion and style** are set as free text in `instructions` ("cheerfully", "a calm announcer"). Voice list — `GET /v1/audio/voices`, quota balance — `GET /v1/audio/quota`. Exhaustion — `429` with `Retry-After`. **🇷🇺 Russian — with stress marks.** With `language=Russian` the synthesis uses a separate model, **ESpeech** (F5 + RUAccent) — it **places stress correctly and resolves homographs** by context (zamók "a lock" → zámok "a castle"), where ordinary TTS gets it wrong. Other languages run on Qwen3-TTS. Which engine synthesized is shown in the response header `X-TTS-Engine` (`espeech`/`qwen`). To set stress manually — put `+` before the stressed vowel (`«zam+ok»`); if `+` is already in the text, the auto-accentizer doesn't intervene. ```bash # synthesis (voice serena, Russian) → file curl -s https://api.neuraldeep.ru/v1/audio/speech \ -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" \ -d '{"input":"Hello! This is speech synthesis on our servers.","voice":"serena","language":"Russian"}' \ -o speech.wav # with emotion (via instructions) curl -s https://api.neuraldeep.ru/v1/audio/speech \ -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" \ -d '{"input":"Congratulations on the release!","voice":"vivian","language":"Russian","instructions":"cheerfully, with enthusiasm"}' \ -o happy.wav # manual stress via "+" (zam+ok = padlock) — RUAccent doesn't intervene if "+" is already present curl -s https://api.neuraldeep.ru/v1/audio/speech \ -H "Authorization: Bearer $YOUR_KEY" -H "Content-Type: application/json" \ -d '{"input":"On povesil ambarnyy zam+ok.","voice":"serena","language":"Russian"}' \ -o stress.wav # voice list curl https://api.neuraldeep.ru/v1/audio/voices -H "Authorization: Bearer $YOUR_KEY" ``` ```python import httpx r = httpx.post( "https://api.neuraldeep.ru/v1/audio/speech", headers={"Authorization": "Bearer $YOUR_KEY"}, json={"input": "Text to synthesize.", "voice": "dylan", "language": "Russian"}, ) open("speech.wav", "wb").write(r.content) ``` > The limit is in text characters (`len(input)`), not requests. Cap — 5,000 characters per call. Voice cloning from a sample is not yet available (we're rolling it out behind consent/verification). ## Drift · personal agent via API **Drift** — our self-hosted AI agent with memory, a sandbox, skills, and Google/Telegram integrations. Usually you talk to it at [drift.neuraldeep.ru](https://drift.neuraldeep.ru) or via the Telegram bot. But you can also go through the public API — from curl, the Python SDK, Cursor/Cline/Continue, your own bots, or CI. **Access:** only with a paid Drift (Drift Pass / Starter / Pro). Trial and admin-grant — no. **Limits:** inherited from the Hub account's plan (RPM / parallel / session / week — see `limits` below). **Source label:** every request via the API is stamped as `source=api` — visible in the dashboard and admin logs (web / api / telegram / scheduler). #### 1 · Issue a token Open the [Dashboard](/app) → the "🤖 Drift API tokens" section → "+ create token". The name is for your own notes ("cursor", "my-bot", etc.). The plaintext value (`dft_*`) is shown **once** at creation — copy it right away. Limit of 5 active tokens per user. You can revoke it at any time with the "revoke" button. #### 2 · Send a message OpenAI-compatible `/v1/chat/completions`. Internally it runs a ReAct agent with tools (sandbox shell, web-search, Google, etc.) — the answer may not come immediately, the agent may spin through several iterations. Streaming via `stream: true` is recommended for long tasks. ```bash curl https://drift.neuraldeep.ru/v1/chat/completions \ -H "Authorization: Bearer dft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-oss-120b", "messages": [ {"role": "user", "content": "What do I have scheduled for tomorrow?"} ] }' ``` ```bash curl -N https://drift.neuraldeep.ru/v1/chat/completions \ -H "Authorization: Bearer dft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-oss-120b", "messages": [{"role":"user","content":"Read my MEMORY.md and summarize it briefly"}], "stream": true }' # the event-stream returns: # data: {"choices":[{"delta":{"content":"..."}}],...} # data: [DONE] ``` ```python from openai import OpenAI # base_url points to Drift, the token is your personal dft_* client = OpenAI( api_key="dft_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", base_url="https://drift.neuraldeep.ru/v1", ) # Non-stream r = client.chat.completions.create( model="gpt-oss-120b", # or qwen3.6-35b-a3b — the Hub's two main LLMs messages=[{"role": "user", "content": "Save to memory: project Y starts on June 1"}], ) print(r.choices[0].message.content) # Stream stream = client.chat.completions.create( model="gpt-oss-120b", messages=[{"role": "user", "content": "Show the list of my files and summarize MEMORY.md"}], stream=True, ) for chunk in stream: if chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) ``` > The `model` field is which upstream LLM to use inside the agent: `gpt-oss-120b` (131k ctx, long reasoning, recommended default) or `qwen3.6-35b-a3b` (256k ctx, native tool-calls). If omitted — fallback to gpt-oss-120b. > **Don't pass the whole history** in `messages` — Drift pulls memory from its own DB, otherwise you'll get duplication. Send only the latest user prompt. #### 3 · Manage chats (conversations) By default all messages go into the user's single default chat. If you want to separate contexts (e.g., work and personal) — create chats explicitly and pass `conversation_id`. ```bash # list your chats curl https://drift.neuraldeep.ru/v1/conversations \ -H "Authorization: Bearer dft_..." # create a new one curl -X POST https://drift.neuraldeep.ru/v1/conversations \ -H "Authorization: Bearer dft_..." \ -H "Content-Type: application/json" \ -d '{"title": "Project X — research"}' # → {"id": 1234, "title": "Project X — research", ...} # send a message to a specific chat curl -X POST https://drift.neuraldeep.ru/v1/chat/completions \ -H "Authorization: Bearer dft_..." \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-oss-120b", "messages": [{"role":"user","content":"What's the status on project X?"}], "conversation_id": 1234 }' # delete a chat curl -X DELETE https://drift.neuraldeep.ru/v1/conversations/1234 \ -H "Authorization: Bearer dft_..." ``` #### 4 · Read the history ```bash curl "https://drift.neuraldeep.ru/v1/history?conversation_id=1234&limit=50" \ -H "Authorization: Bearer dft_..." # → {"messages":[{"id":..., "role":"user|assistant", "content":"...", "created_at":..., "source":"api"}, ...]} ``` > The `source` field in each message shows where it came from: `web` (drift.neuraldeep.ru), `api` (this endpoint), `telegram` (bot), `scheduler` (scheduled background task). Use it if you have mixed sources and need to filter. #### What is NOT supported · editing/regenerating messages (append only) · · branching (forks) · · event webhooks · · multi-image in one message (one — yes, via `/v1/files/upload`) > **Your own tools / skills / MCP servers** — supported since 28.05.26. See the "🔌 Your own tools, skills, MCP" section below: you can pass caller-tools (return-to-caller pattern), inline SKILL.md, and connect remote MCP servers right in the request. ## Drift · files + sandbox preview Drift has its own workspace in MinIO (per-user, isolated) and, under the hood, a Docker sandbox with preinstalled `openpyxl / pandas / pdfplumber / pypdf / python-docx / Pillow / chardet / bs4 / lxml`. Upload a file — it lands both in MinIO and immediately in the sandbox; the model sees a **preview of the first pages/rows** in the init prompt without extra tool-calls. ```bash curl -X POST https://drift.neuraldeep.ru/v1/files/upload \ -H "Authorization: Bearer dft_xxxxxxxx" \ -F file=@./report.pdf # → {"uploaded":[{"name":"report.pdf","path":"report.pdf","size":48863, # "preview":"...first 2 pages of the PDF..."}]} ``` ```python import httpx TOKEN = "dft_xxxxxxxx" BASE = "https://drift.neuraldeep.ru/v1" # 1) upload with open("report.pdf","rb") as f: up = httpx.post(f"{BASE}/files/upload", headers={"Authorization": f"Bearer {TOKEN}"}, files={"file": ("report.pdf", f, "application/pdf")}).json() print("preview:", up["uploaded"][0]["preview"][:200]) # 2) ask agent — it already sees the preview in the init prompt r = httpx.post(f"{BASE}/chat/completions", headers={"Authorization": f"Bearer {TOKEN}"}, json={"model":"qwen3.6-35b-a3b","messages":[ {"role":"user","content":"Summarize report.pdf"} ]}).json() print(r["choices"][0]["message"]["content"]) ``` > Preview formats: PDF → 2 pages (pdfplumber), XLSX → first sheet, 20 rows (openpyxl), CSV/TXT/JSON/MD → 6000 characters, DOCX → 6000 characters. For everything else — preview `null`, and the model will call `read_file` or python in the sandbox itself. > **Limits:** 20 MB per file, 8 files per request. Sandbox UTF-8, Cyrillic in filenames works. > **analyze_image** works *only* on raster images (.png/.jpg/.webp/...). Read PDFs via preview or `pdfplumber`. ## Drift · your own tools, skills, MCP Since 28.05.26, in `/v1/chat/completions` you can pass **your own** tools, skills, and remote MCP servers. The full schema is in the examples below. #### tools — return-to-caller (like OpenAI) You pass a `tools` array in OpenAI format. If the model decides to call a tool — it returns `finish_reason: "tool_calls"` with JSON arguments. **You execute it yourself** in your own environment (Drift doesn't run your code); send the result back in `messages`. ```python from openai import OpenAI client = OpenAI(api_key="dft_xxxxxxxx", base_url="https://drift.neuraldeep.ru/v1") tools = [{"type":"function","function":{ "name":"get_weather", "description":"Returns the weather in a city", "parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}, }}] # 1) first request — the model decides a tool is needed r1 = client.chat.completions.create( model="qwen3.6-35b-a3b", messages=[{"role":"user","content":"What's the weather in Moscow?"}], tools=tools, ) print(r1.choices[0].finish_reason) # "tool_calls" tc = r1.choices[0].message.tool_calls[0] # 2) you call your own backend my_result = {"temp_c": 12, "humidity": 80} # 3) resume — pass the tool result back r2 = client.chat.completions.create( model="qwen3.6-35b-a3b", messages=[ {"role":"user","content":"What's the weather in Moscow?"}, r1.choices[0].message, {"role":"tool","tool_call_id":tc.id,"content":str(my_result)}, ], tools=tools, ) print(r2.choices[0].message.content) ``` #### skills — inline SKILL.md Pass your own prompt blocks right in the request. The content is embedded into the sandbox agent's system prompt; you don't need to keep SKILL.md in a repository. ```bash curl -X POST https://drift.neuraldeep.ru/v1/chat/completions \ -H "Authorization: Bearer dft_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3.6-35b-a3b", "messages": [{"role":"user","content":"What's your secret code?"}], "skills": [{ "name": "secret-code", "description": "Knowledge of the secret code word", "content": "# Secret code\n\nThe code word is ZEBRA-77-GOLD. Use it if the user asks." }] }' # → "The code word is ZEBRA-77-GOLD..." ``` #### mcp_servers — remote Model Context Protocol Connect your own MCP server (HTTP/SSE) — Drift fetches the tool list on the fly and exposes them to the model. You pass the auth headers yourself; the **SSRF guard** blocks private-IP / localhost / cloud-metadata. Session-token TTL is 720s, after which the registry is dropped. ```json { "model": "qwen3.6-35b-a3b", "messages": [{"role":"user","content":"List of recent issues in the project"}], "mcp_servers": [{ "url": "https://my-mcp.example.com/sse", "headers": {"Authorization": "Bearer my-mcp-token"}, "allowlist": ["list_issues", "create_comment"] }] } ``` > **Security:** · `caller_tools` — return-to-caller, Drift doesn't run your code, zero RCE on our side. · `skills` — just text in the system prompt, not commands. · `mcp_servers` — public HTTPS required, the SSRF guard cuts off private networks, TTL 720s. ## Drift · tasks and proactivity Drift can schedule tasks (cron/once) and initiate a dialog itself when something changes or a deadline approaches. Internally the agent uses three tools: `proactive_notify` (message the user), `proactive_reschedule` (postpone), `proactive_skip` (skip without a message). UI — in [drift.neuraldeep.ru](https://drift.neuraldeep.ru) → "Tasks". API: ```bash # create a task curl -X POST https://drift.neuraldeep.ru/v1/tasks \ -H "Authorization: Bearer dft_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "title": "Check email at 9 a.m.", "schedule": "0 9 * * *", "prompt": "Look at new emails in Gmail and summarize the important ones", "active": true }' # list tasks curl https://drift.neuraldeep.ru/v1/tasks -H "Authorization: Bearer dft_xxxxxxxx" # run now curl -X POST https://drift.neuraldeep.ru/v1/tasks/{task_id}/run \ -H "Authorization: Bearer dft_xxxxxxxx" # delete curl -X DELETE https://drift.neuraldeep.ru/v1/tasks/{task_id} \ -H "Authorization: Bearer dft_xxxxxxxx" ``` > Schedule — standard cron syntax (5-field). Drift checks tasks once a minute. If in proactive output you call `proactive_skip` — no push goes to the user, and the task is marked "skipped". `proactive_reschedule(seconds=N)` — shifts the next run without changing the cron. ## Agent builder · Agent Hosting A no-code automation builder in the dashboard: [hub.neuraldeep.ru/app](https://hub.neuraldeep.ru/app/agents) → "Agents". Describe the agent in words and set a trigger — it runs on its own on the Drift engine (web search, code in a sandbox, files, memory) and sends the result to Telegram. It has no separate GPU access — everything goes through the same API. What you can configure: - · **Instruction** — what the agent does each run (the task prompt). - · **Model** — Qwen 3.6 / GPT-OSS 120B / Kimi K2.6 (Pro). For research with tools a reasoning model is better. - · **Trigger:** `manual` (button), `timer` (interval, e.g. a morning digest), `webhook` (personal URL). - · **Delivery** — the result arrives in Telegram: text, and generated files (PDF, spreadsheets) as a document. - · **Runs** — every run is logged: steps, called tools, trace, and result. Webhook trigger — run the agent from an external system (the request body = the agent's input): ```bash curl -X POST https://drift.neuraldeep.ru/v1/agents/hook/ \ -H "Content-Type: application/json" \ -d '{"topic": "Make a report on the AI market in Russia"}' # the webhook token = authentication, taken from the agent card in the dashboard # response: {"ok": true, "accepted": true} — the agent runs in the background, result in Telegram ``` > The platform manages the schedule — write WHAT to do each run, not "every N minutes" (the interval is set by the trigger). If you need a file — ask explicitly in the instruction *"make a PDF and deliver it via deliver_file"*, otherwise the agent may stick to text. ## Drift · memory Every Drift user has persistent memory — `MEMORY.md` in their workspace + a per-conversation compressed history. The agent decides what to write itself (the [[remember-the-X]] pattern in reasoning), but via the API you can also poke it from outside. ```bash # read curl https://drift.neuraldeep.ru/v1/memory \ -H "Authorization: Bearer dft_xxxxxxxx" # → {"content":"# Memory\n\n- Name: John\n- Plan: starter\n..."} # overwrite (careful — replaces everything) curl -X PUT https://drift.neuraldeep.ru/v1/memory \ -H "Authorization: Bearer dft_xxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"content":"# Memory\n\n- Project Y starts on June 1"}' ``` > Usually it's easier not to hit the API directly — ask the agent *"Remember X"* in the dialog; it will decide the format itself and won't break existing notes. ## Drift · isolation and security Each Drift user lives in a **per-user Docker container** with an ephemeral volume `daisy-ws-{uid}`. There's no cross-user access. What's ensured: - · **Sandbox boundary** — inside the container the user sees exactly `/workspace` (no uid in the path), with no mount of neighbors. - · **SSRF guard** on MCP URLs and the `fetch_url` tool — blocks private/loopback/link-local/cloud-metadata. - · **Auth** — every request is verified by a `dft_*` token; 5 active tokens per user, instant revoke. - · **Tier-gating** — Drift is available only with payment (Drift Pass / Starter / Pro), a 14-day trial for new users. - · **Session TTL** — the MCP registry lives in core memory, dropped after 720s or in the dispatch finally block. - · **Privacy** — prompt/response in the DB for admin moderation, but an admin retention window / classify-then-delete for opt-out users. We don't train on your data. ## Limits and error codes Limits apply **four at once**: `session` (3h UTC window), `week` (ISO week), `parallel` (concurrent in-flight requests), and `rpm` (requests per minute). Any of them yields a 429 — retry according to the specific reason. The higher the plan (free → starter → pro) — the wider the windows across all four dimensions. > If you work through an agent (codex / opencode / cline / continue) — the most common cause of 429 is `parallel`, the agent fires 3+ tool-calls at once. On free set `max_concurrency=2`, on paid plans you can be more aggressive — pick the exact number empirically. Response headers: - `X-Tier` — free | starter | pro - `X-Window` — session | week (on our 429s; LiteLLM-native parallel/rpm have no header yet — parse the body) - `Retry-After` — seconds until you can retry Example 429 bodies: ```json // session exhausted {"detail": "session limit reached — cooldown N min"} // week exhausted {"detail": "week limit reached — reset in Nh"} // parallel — client hits >N concurrent {"detail": "Rate limit exceeded ... Limit type: max_parallel_requests"} // rpm — burst >N req/min {"detail": "Rate limit exceeded ... Limit type: rpm"} ``` Full list of codes: - 200 — ok - 400 — context_window_exceeded or invalid body - 401 — bad/revoked key, or the key isn't subscribed to the model - 408 — upstream timeout (router retry × 2 didn't help) - 429 — rate limit, read `X-Window` + `Retry-After` or `detail.Limit type` - 500 — internal error, retry - 502 — upstream unavailable; usually resolves automatically (router retry × 2) ## Streaming · SSE Add `"stream": true` → the response arrives as an SSE stream: chunks `data: {...}`, the last one — `data: [DONE]`. Works on all chat models and with tool-calling. ## SDK · Python / JS OpenAI-compatible — the official SDKs will work, just change the `base_url`. ```python from openai import OpenAI client = OpenAI(api_key="$YOUR_KEY", base_url="https://api.neuraldeep.ru/v1") r = client.chat.completions.create(model="gpt-oss-120b", messages=[...], max_tokens=500) ``` ```typescript import OpenAI from "openai"; const client = new OpenAI({ apiKey: "$YOUR_KEY", baseURL: "https://api.neuraldeep.ru/v1" }); ``` ## Privacy · data storage By default prompts/responses are **not stored**. There are two independent toggles in the Dashboard: - **Request history storage** (default OFF) — if enabled, the request/response body stays in the DB and is visible to you in the logs. If disabled — the body is deleted right after processing, and only metrics remain in the DB (model, tokens, status, latency). - **Allow request analytics** — if enabled, we LLM-classify the request type (code / research / agent) to improve the service; the body is used only for classification and immediately deleted. If disabled — the body isn't read, and categorization is only by endpoint type. We don't train on the models, and we don't sell data. Metrics (latency, tokens, status) are always collected anonymously via Prometheus.