Skip to content

Embeddings, reranks & speech

Three of the gateway’s doors serve catalog kinds that are not chat, image, or video:

POST https://api.sociaro.com/v1/embeddings # OpenAI Embeddings format → JSON
POST https://api.sociaro.com/v1/reranks # rerank (query + documents) → JSON
POST https://api.sociaro.com/v1/audio/speech # text-to-speech → raw binary audio

Like every door, they accept catalog slugs only (author/model, see GET /v1/models), resolve the slug, rewrite only the top-level model field to the provider-native id, and forward the rest of the body — and the entire response — byte-for-byte. There is no format translation. Billing, budgets, entitlements and attribution behave exactly as on every other door; the only difference is the request/response shape, which this page documents end-to-end.

These three doors are synchronous: they return the result in the same HTTP response. They do not create async jobs and do not appear in GET /gw/async — that ledger lists the async kinds: video, async image jobs, native-prefix async ASR (audio), and 3d once those models are enabled. Their usage records show up in GET /gw/usage like any other call.

Every /v1/* door serves exactly one catalog kind. The door reads the resolved slug’s kind (the same kind field you see in GET /gw/models) and rejects any slug whose kind does not match — before contacting any provider:

DoorServes kindSlug example
/v1/embeddingsembeddingalibaba/text-embedding-v4
/v1/reranksrerankalibaba/qwen3-rerank
/v1/audio/speechaudioalibaba/qwen3-tts-flash

A mismatch returns 400 with a hint, e.g. sending an LLM slug to /v1/embeddings:

{"error":{"type":"bad_request",
"message":"model \"alibaba/qwen3.7-max\" has kind \"llm\"; this endpoint serves only embedding models"}}

The same invariant keeps an embedder out of /v1/reranks (and a reranker out of /v1/embeddings): each gets a 400 pointing at its kind.

A door serves a slug only if the slug’s upstream exposes the matching compatible endpoint. Today the embedding, rerank and speech doors are served by Alibaba (DashScope International) slugs — the flagship embedding/rerank/audio slugs are all Alibaba slugs. Other model kinds (chat, images, videos) span more authors.

If a slug’s upstream has no compatible endpoint for the door you called, the gateway returns 400 (not a fallback) — the slug is valid, but it has no compatible endpoint for this door:

{"error":{"type":"bad_request",
"message":"model some/embed-model is not served by this endpoint; this model's provider has no OpenAI-compatible embeddings endpoint"}}

In practice the flagship embedding/rerank/audio slugs are all Alibaba slugs, so you only hit this if you point one of these doors at a slug from another author whose upstream has no compatible endpoint. As always, the provider is a routing detail the gateway resolves for you — you never name it.

OpenAI-shaped in both directions. The door passes through every field you send (including dimensions, encoding_format, and any other provider-supported field) and returns the provider’s OpenAI-shaped response untouched. Billing is input-token-only (input_per_mtok); usage.prompt_tokens from the response is what you are charged for.

The official OpenAI SDK works directly — client.embeddings.create is a real OpenAI method:

from openai import OpenAI
client = OpenAI(base_url="https://api.sociaro.com/v1", api_key="gw_live_...")
resp = client.embeddings.create(
model="alibaba/text-embedding-v4",
input="hello world",
# dimensions=1024, # passed through to the provider when supported
)
vector = resp.data[0].embedding

Or with curl:

Terminal window
curl https://api.sociaro.com/v1/embeddings \
-H "Authorization: Bearer gw_live_..." \
-H "Content-Type: application/json" \
-d '{"model": "alibaba/text-embedding-v4", "input": "hello world", "dimensions": 1024}'

Response (OpenAI shape — data[].embedding):

{
"object": "list",
"data": [
{"object": "embedding", "index": 0, "embedding": [0.0123, -0.0456, ""]}
],
"usage": {"prompt_tokens": 3, "total_tokens": 3}
}

Pass a list of strings as input to embed a batch; the response data array has one entry per input, each with its own index.

The rerank door scores how relevant each document is to a query. It is not an OpenAI endpoint — the OpenAI SDK has no reranks method, and neither does the sociaro SDK. Call it over raw HTTP (curl, httpx, fetch, …). The door rewrites only model; the rest of the body is forwarded verbatim.

Request — model, query, and a documents array (provider-supported fields such as top_n are passed through):

Terminal window
curl https://api.sociaro.com/v1/reranks \
-H "Authorization: Bearer gw_live_..." \
-H "Content-Type: application/json" \
-d '{
"model": "alibaba/qwen3-rerank",
"query": "What is the capital of France?",
"documents": [
"Paris is the capital and most populous city of France.",
"Berlin is the capital of Germany.",
"The Eiffel Tower is in Paris."
]
}'

Response — a results array sorted by relevance, each entry carrying the zero-based index into your documents array and a relevance_score, plus a usage block:

{
"results": [
{"index": 0, "relevance_score": 0.9},
{"index": 2, "relevance_score": 0.62},
{"index": 1, "relevance_score": 0.04}
],
"usage": {"prompt_tokens": 7, "total_tokens": 7}
}

Map each result back to your document by its index. Billing follows the provider’s usage (token-based) and is recorded per request in GET /gw/usage.

# No OpenAI / sociaro SDK method — call the door over raw HTTP.
import httpx
resp = httpx.post(
"https://api.sociaro.com/v1/reranks",
headers={"Authorization": "Bearer gw_live_..."},
json={
"model": "alibaba/qwen3-rerank",
"query": "What is the capital of France?",
"documents": [
"Paris is the capital and most populous city of France.",
"Berlin is the capital of Germany.",
"The Eiffel Tower is in Paris.",
],
},
)
ranked = resp.json()["results"] # [{"index": 0, "relevance_score": 0.9}, …]

The speech (TTS) door synthesizes audio from text. Its response is raw binary audio — not JSON. You must write the response bytes to a file (or stream them); there is no response object to parse. The request shape follows OpenAI’s audio/speech: a top-level input string (the text to speak), the model slug, a voice, and optional response_format / speed — all forwarded to the provider as sent.

Billing for this door is per-character, counted from the top-level input string of your request (the response has no token/usage block to meter). The gateway counts the Unicode characters of input and charges per_character accordingly, so cost is predictable from the text you submit.

Terminal window
curl https://api.sociaro.com/v1/audio/speech \
-H "Authorization: Bearer gw_live_..." \
-H "Content-Type: application/json" \
-d '{
"model": "alibaba/qwen3-tts-flash",
"input": "Hello from the Sociaro gateway.",
"voice": "alloy",
"response_format": "mp3"
}' \
--output speech.mp3

The --output speech.mp3 is essential: without it curl prints raw audio bytes to your terminal. Any HTTP client works the same way — read the response body as bytes and write them to a file:

# No OpenAI / sociaro SDK method — call the door over raw HTTP; response is bytes.
import httpx
resp = httpx.post(
"https://api.sociaro.com/v1/audio/speech",
headers={"Authorization": "Bearer gw_live_..."},
json={
"model": "alibaba/qwen3-tts-flash",
"input": "Hello from the Sociaro gateway.",
"voice": "alloy",
"response_format": "mp3",
},
)
resp.raise_for_status()
with open("speech.mp3", "wb") as f:
f.write(resp.content) # raw audio bytes, not JSON

Transcription (ASR): there is no /v1/audio/transcriptions door

Section titled “Transcription (ASR): there is no /v1/audio/transcriptions door”

Speech-to-text is intentionally not a dedicated door — no provider the gateway routes to exposes an OpenAI-compatible /v1/audio/transcriptions endpoint. Transcription runs through paths you already use:

  • Synchronous ASR via chat: send audio to an ASR-capable model on POST /v1/chat/completions (e.g. alibaba/qwen3-asr-flash). The transcript comes back in the chat response; billing is per second of audio, read from the provider’s usage.seconds.
  • Native async ASR: some ASR models (e.g. fun-asr) run as async jobs over a provider’s native prefix — create, then poll to a terminal status.

So if you are looking for transcription, use the chat door (or native async), not a /v1/audio/* door. Only text-to-speech has its own door (/v1/audio/speech, above).

All four door errors below come from the gateway itself (JSON envelope {"error":{"type","message"}}); recover by checking GET /v1/models:

StatustypeWhen
400bad_requestmodel is a bare name, not a catalog slug (use 'author/model')
404not_foundslug is not in the catalog
400bad_requestslug’s kind does not match the door (serves only embedding/rerank/audio models)
400bad_requestslug’s provider has no compatible endpoint for this door (is not served by this endpoint)

See the error contract for auth, budget, rate-limit and upstream errors that apply to every door.