Text, vision, image, voice, video and search behind a single endpoint, in a format you already know: Chat Completions. You talk to Entelecy; Entelecy talks to the providers, holds the keys, validates the request before spending anything and bills credits on real usage.
The Entelecy API is a gateway. It takes requests in the industry-standard shape, applies authentication, validation and metering, routes them to the right provider and returns the response essentially untouched — streaming included, chunk by chunk. In practice, if your code already talks to an LLM API, swapping the base URL and the key is usually enough.
What the gateway adds along the way:
A single credential. No provider key ever reaches the client — they stay on the server, and what you hold is an Entelecy kriou_live_… key.
Validation before spending. Image size, video duration, character ceiling and enabled model are checked at the gateway; an envelope error comes back in milliseconds, at no cost.
Metering at the end of the call. Real usage (tokens, seconds, characters, calls) becomes a credit debit on the wallet of the key that made the call.
Provider swaps without breaking the contract. The model behind each capability may change; your request shape does not change with it.
The root is public and doubles as a health probe: it answers without authentication and reports which build is live.
Every call requires an Authorization header carrying an Entelecy key as a Bearer token. The key is created and lives in your account; the gateway validates it against Entelecy Account and keeps the result in a short cache, so that check costs almost nothing on the hot path.
http
POST /v1/chat/completions HTTP/1.1
Host: api.entelecy.ai
Authorization: Bearer kriou_live_7Qb3xk9_M2pN-VtR4sLu8Z
Content-Type: application/json
Accepted credentials:
kriou_live_… — production key. The normal way to integrate.
kriou_test_… — test key, useful for staging. Endpoints can be configured to reject it.
Heads up
A malformed, revoked or expired key returns 401 without explaining why. The key never appears in logs — not in full, not partially. If one leaks, revoke it in the account: the cached validation expires within seconds.
Conventions
Requests and responses are UTF-8 JSON, except where noted (audio and media uploads use multipart/form-data; speech synthesis returns audio bytes; transcription returns plain text). Fields the gateway doesn't know are forwarded to the provider untouched — which is what lets you use new features before they show up on this page.
Headers
Header
Where
Description
Authorization
request
Bearer + Entelecy key. Required on every endpoint except the root.
Content-Type
request
application/json, or multipart/form-data on upload endpoints.
X-Image-Provider
request
Picks the image provider when more than one is enabled. Optional; there is a server default.
Streaming
With "stream": true the response becomes text/event-stream: data: … lines, one per event, with no intermediate buffering. The last useful event carries the call's usage; after it comes data: [DONE].
The gateway always asks the provider for the usage block, so the final usage chunk arrives whether or not you request it. If a stream ends without [DONE], treat the answer as truncated and retry — the connection was cut mid-flight.
Errors
Errors raised by the gateway always take the same shape: an error object with type, message and, where it helps, code and param. Provider errors are passed through with their original status and body.
Empty body, invalid JSON, missing required field, or an envelope rejected by one of the gateway rules.
401
—
Missing header, malformed key, revoked key, or an environment that isn't allowed.
402
insufficient_credits
Balance below the operation's minimum. The body carries the balance, the shortfall and a top-up URL.
4xx / 5xx
passed through
Provider error (rate limit, refused content, outage). Status and body arrive as they came.
503
upstream_unavailable
The provider dropped before the first byte and the gateway's retries were exhausted. On streaming calls it arrives as an SSE event.
Gateway codes
These are the refusals decided before calling the provider. All return 400, cost zero credits and carry an actionable message — you can fix the request and retry without guessing.
code
Description
gateway_invalid_size
size outside the model's envelope: format, pixel step, minimum or maximum area, edge or aspect ratio.
gateway_unsupported_background
Transparent background requested from a model that doesn't support it, with no configured alternative.
gateway_unknown_model
Model not enabled for the endpoint. The message lists the accepted ones.
gateway_invalid_duration
duration missing, not an integer, or outside the allowed range for video.
gateway_invalid_resolution
Video resolution outside the accepted list.
gateway_invalid_input
Text above the speech-synthesis character ceiling. Split it up.
Twelve routes, grouped by capability. The right-hand column previews the billing unit — the detail is under Billing.
Endpoint
Description
Billed unit
POST /v1/chat/completions
Conversation and text generation in the Chat Completions shape.
tokens
POST /v1/images/generations
Text-to-image generation.
tokens
POST /v1/images/edits
Image editing with an optional mask.
tokens
POST /v1/audio/transcriptions
Audio transcription (speech → text).
seconds of audio
POST /v1/audio/speech
Speech synthesis (text → speech).
characters
GET /v1/audio/voices
Catalog of available voices.
not billed
POST /v1/videos/generations
Video generation (submit).
generated seconds
GET /v1/videos/generations/{id}
Poll a video in progress.
not billed
POST /v1/videos/uploads
Upload a reference media for image-to-video.
not billed
POST /v1/search
Web search with structured results.
per call
GET /
Service status. Anonymous.
not billed
Text — Chat Completions
A Chat Completions-compatible shape: the same body you already send to OpenAI-compatible clients works here, including tools, structured output and streaming. This is the general-purpose endpoint — chat, generation, extraction, classification, agents.
POST/v1/chat/completionssupports streaming (SSE)
Fields the gateway reads or handles. Everything else is forwarded untouched.
Field
Type
Description
modelrequired
string
Public model name: loom-flash or loom-pro. A vendor id is refused with 400 — the message lists what is accepted.
effort
string
How much the model should think: none, high or max. It is a gateway parameter — translated and stripped before forwarding. Omitted, nothing is rewritten.
messagesrequired
array
Conversation turns, with role (system, user, assistant, tool) and content.
stream
boolean
true turns the response into an SSE stream. Defaults to false.
max_tokens
integer
Ceiling on generated tokens.
temperature
number
Sampling randomness, where the model accepts it.
tools / tool_choice
array / object
Tool definitions and choice policy, in the standard shape.
response_format
object
Structured output — e.g. { "type": "json_object" }.
thinking
object
Provider field. Same rule: only rewritten when you send effort.
reasoning_effort
string
Provider field. Only touched if you send effort; otherwise it passes through.
Model and effort
Two independent axes, both in the body: model picks the tier and effort picks how much the model thinks. Deliberately separate — every combination is a config line in the gateway, not a magic number. The gateway translates both into the provider’s vocabulary and strips effort before forwarding.
model
Profile
When to use it
loom-flash
Fast and cheap, for volume
Short answers, classification, autocomplete, extraction — where latency rules.
loom-pro
More capable, for hard work
Code, multi-step analysis, long-form writing, agents with tools.
effort
Profile
When to use it
none
No explicit reasoning
Fastest and cheapest. Answers directly, with no thinking step.
high
Reasoning on
The recommended default when the answer has to be right, not just quick.
max
Reasoning at the ceiling
Hard problems: large refactors, long planning, correctness over cost.
Note
Without effort in the body the gateway does not touchthinking or reasoning_effort — anyone already sending those keeps working as before. And the response echoes back the same name you asked for in model (loom-flash, not the vendor id): what you send and what comes back speak the same language.
Request
curl
curl -N https://api.entelecy.ai/v1/chat/completions \
-H "Authorization: Bearer $ENTELECY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "loom-flash",
"effort": "high",
"stream": true,
"messages": [
{ "role": "system", "content": "Responda em portugues do Brasil, direto ao ponto." },
{ "role": "user", "content": "Resuma o conceito de entelequia em tres linhas." }
]
}'
Chat Completions format. The gateway normalizes the vendor response before returning it: model comes back with the public name you asked for, and vendor-internal fields do not pass through. Fields the vendor adds that the gateway does not yet know are forwarded — what is documented here is what is stable.
Field
Type
Description
id
string
Unique identifier for this call. Opaque: derive nothing from its shape.
object
string
chat.completion for a full response; chat.completion.chunk for each stream event.
created
integer
Creation time, in Unix seconds (UTC).
model
string
The public name you asked for (loom-flash). Not the vendor id.
choices
array
List of answers. Without n, exactly one.
choices[].index
integer
Position of this choice in the list.
choices[].message / delta
object
The generated message: role (assistant) and content. In a stream this field is called delta and carries the new piece, not the whole text.
….reasoning
string
The reasoning, when the model thinks before answering. It is diagnostic text, not the answer: do not render it in place of content, and do not rely on its language or format.
….reasoning_content
string
Old name for the same content, kept in parallel while clients migrate. It will go away; use reasoning.
choices[].finish_reason
string
Why it stopped: stop (natural end), length (hit the token ceiling), tool_calls (wants to call a tool).
usage
object
Usage for the call — this is what billing reads. In a stream it arrives in the last event, with an empty choices.
usage.prompt_tokens
integer
Input tokens (what you sent).
usage.completion_tokens
integer
Output tokens (what the model generated), reasoning included.
usage.total_tokens
integer
The sum. This is the number billing uses.
…prompt_tokens_details.cached_tokens
integer
The part of the input that hit the prompt cache. Repeating a large prefix is far cheaper than resending it.
…completion_tokens_details.reasoning_tokens
integer
How much of the output was reasoning. Already counted inside completion_tokens — do not add it.
The usage block is the billing basis: input, output, and the share of input that hit the prompt cache — replaying a large prefix is far cheaper than resending it cold.
Image
Image generation and editing in the standard shape, with envelope validation at the gateway. The response carries the image as base64 plus the call's token usage.
POST/v1/images/generations
Field
Type
Description
modelrequired
string
Public name of the image model: loom-image. See Models.
promptrequired
string
What to generate. Specific prompts beat stacked adjectives.
size
string
WIDTHxHEIGHT in pixels, or auto. Validated before sending.
quality
string
Quality level the model accepts.
background
string
auto, opaque or transparent.
n
integer
Number of images.
output_format
string
Returned file format, where the model lets you choose.
Envelope validated at the gateway
What the model accepts is the gateway's knowledge, not your code's. Before calling the provider, the request goes through these rules:
Both sides must be multiples of the model's pixel step.
Total area must sit between the model's minimum and maximum, and the longest edge below its ceiling.
The aspect ratio can't exceed the model's limit.
A transparent background asked of a model without support is rerouted to the alternate model — and billing follows the model actually used.
Request
curl
curl https://api.entelecy.ai/v1/images/generations \
-H "Authorization: Bearer $ENTELECY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "loom-image",
"prompt": "Fachada de uma padaria de bairro ao amanhecer, luz quente, fotografia",
"size": "1536x1024",
"quality": "high",
"n": 1
}'
Editing takes JSON: the base image (one or more) and an optional mask, both base64. The gateway converts it to the multipart shape the provider expects, so you don't have to build the upload.
json
{
"model": "loom-image",
"prompt": "Troque o fundo por um ceu limpo no fim da tarde",
"image": ["iVBORw0KGgo…"],
"mask": "iVBORw0KGgo…",
"size": "1024x1024"
}
Note
The mask marks what may change: the transparent area is the editable one. Send mask and base image at the same dimensions.
Audio
Two paths: transcribing speech into text and synthesizing text into speech. The transcription engine is selected by the model field; without it, the server default applies.
Public name of the transcription engine: loom-audio — same name as the voice; the route says whether it is speech-to-text or text-to-speech.
language
string
ISO-639 language hint (pt, en). Omitted, the language is detected.
The response is plain text (text/plain), not JSON — that's the contract Entelecy clients already consume. The audio duration measured during transcription is the billing basis.
curl
curl https://api.entelecy.ai/v1/audio/transcriptions \
-H "Authorization: Bearer $ENTELECY_API_KEY" \
-F "[email protected]" \
-F "model=loom-audio" \
-F "language=pt"
# 200 OK · text/plain
Bom dia. Comecando a reuniao de quinta…
POST/v1/audio/speechreturns audio bytes
Field
Type
Description
inputrequired
string
Text to speak. There's a per-request character ceiling; above it the gateway refuses before spending.
voice
string
Voice identifier. Look it up in /v1/audio/voices.
model
string
Public name of the voice model: loom-audio. See Models.
response_format
string
mp3 or a native format such as mp3_44100_128, opus_48000_64, pcm_24000.
voice_settings
object
Fine voice controls (stability, similarity, style), forwarded to the provider.
curl
curl https://api.entelecy.ai/v1/audio/speech \
-H "Authorization: Bearer $ENTELECY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "loom-audio",
"input": "A entrega de quinta esta confirmada. Qualquer mudanca, aviso por aqui.",
"response_format": "mp3_44100_128"
}' \
--output aviso.mp3
GET/v1/audio/voicesnot billed
Lists the voices available on the account, including the provider's premade ones. Requires authentication, is never billed, and is the correct source for the identifiers used in voice.
Video
Video generation is asynchronous: you submit the request, get an identifier and poll until the state is terminal. Image-to-video uses a reference media uploaded beforehand.
Public name of the video model: loom-video. It covers text-to-video and image-to-video — what changes is the reference media.
promptrequired
string
The scene, the camera movement and the pacing.
durationrequired
integer
Duration in seconds, within the allowed range. It is the billing basis, which is why automatic isn't accepted.
resolution
string
Resolution within the accepted list.
curl
# 1) submit — devolve o id da predicao
curl https://api.entelecy.ai/v1/videos/generations \
-H "Authorization: Bearer $ENTELECY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "loom-video",
"prompt": "Plano aereo de uma feira livre ao amanhecer, camera avancando devagar",
"duration": 6,
"resolution": "720p"
}'
# { "data": { "id": "pred_01J8Z…", "status": "queued" } }
# 2) poll — ate status completed
curl https://api.entelecy.ai/v1/videos/generations/pred_01J8Z… \
-H "Authorization: Bearer $ENTELECY_API_KEY"
# { "data": { "status": "completed", "outputs": [{ "url": "https://…/video.mp4" }] } }
Note
Billing is armed on submit and fired by the first poll that sees the video ready. Polling repeatedly does not double-charge; if the job fails, there is no debit.
POST/v1/videos/uploadsmultipart/form-data
Uploads the reference image for the image-to-video flow and returns the identifier that goes in the generation body. The upload is never billed.
Search
Web search with structured results — built to give an agent current context before it answers. The body is forwarded to the engine, so region, language and count parameters behave as they do upstream.
The response arrives as the engine returned it: a direct answer block where one exists, organic results, knowledge panels and so on. Billing is per successful call, regardless of how many results come back.
Models
The model field takes an Entelecy public name. It describes the capability you want; which model serves it is our call, revisited continuously. Resolution is by (name, route) — a name outside the route list is a 400 before anything is spent.
model
Endpoint
Description
loom-flash
/v1/chat/completions
Fast, cheap text for volume. Accepts effort.
loom-pro
/v1/chat/completions
Higher-capability text for hard work. Accepts effort.
loom-image
/v1/images/generations
Image generation. Transparent background is handled by the gateway — you do not switch names.
loom-audio
/v1/audio/speech
Speech synthesis with natural Portuguese prosody.
loom-audio
/v1/audio/transcriptions
Audio transcription. Same name as the voice: the route decides the direction.
loom-video
/v1/videos/generations
Short video generation, from text or from a reference image.
How we choose
We are not loyal to a vendor. Every segment is a moving field — a new model each month, prices falling, capabilities shifting — and what makes sense today may not next quarter. We track the relevant families in each, measure them on our own material, and switch when it pays.
The criteria, in order:
Quality in Brazilian Portuguese. Measured on real customer content, not on a translated benchmark.
Cost per accepted result. Not price per token: the price of the answer that survived review.
Predictable latency. A good model that swings from 3 to 40 seconds is no good for an interactive product.
Contract stability. A provider that changes shapes without notice is expensive over time.
Agents
Reasoning, code and tools
Chat, text generation and extraction, reading images and documents, and the agent loop with tools. This is where the gap between models shows most — and where the effort scale pays best.
Families tracked in this segment
ClaudeGPTGeminiDeepSeekLlamaMistralQwenGrok
Image
Generation and editing
Brand assets, photographic scenes, illustration, masked editing and transparent backgrounds. We weigh prompt adherence, legible typography inside the image, and consistency across variations.
Long narration, short interactive replies, expressive reading. The cut here is how natural it sounds in Brazilian Portuguese — most options still sound translated — plus latency and style control.
Short clips for content, animating an existing image, directed camera movement. Temporal coherence, prompt adherence and cost per second drive the choice.
Families tracked in this segment
VeoSoraSeedanceKlingRunwayLuma RayHailuoWan
Search
Web with structured results
Current context so an agent can answer without hallucinating: organic results, direct answers and panels, as clean JSON. Low latency matters more than volume — an agent searches several times per task.
This is the landscape we track per segment, not an availability catalog. What is active behind each public name is our curation and changes when something better shows up — without breaking your integration, because the name does not change with it.
Billing
Everything is billed in credits, on the wallet of the credential's owner. Each capability uses the unit that matches the real work:
Endpoint
Billed unit
Where the measure comes from
/v1/chat/completions
tokens
The response's usage, with input, output and cache broken out.
/v1/images/*
tokens
The response's usage, including generated image tokens.
/v1/audio/transcriptions
seconds of audio
Audio duration measured during transcription, rounded up.
/v1/audio/speech
characters
Character count of the submitted text.
/v1/videos/generations
generated seconds
Duration requested on submit, billed when the video is ready.
/v1/search
per call
One unit per successful call.
Before forwarding, the gateway checks a minimum balance for the operation — higher for image and video, which cost more. Without balance, the answer is 402 with the shortfall and a top-up link, and nothing is spent at the provider.
The debit happens after the response, on real usage, and is idempotent per call: a network retry doesn't charge twice. Calls that fail at the provider produce no debit.
Heads up
If the balance runs out between the initial check and the debit, the response has already been delivered and the debit is recorded anyway. That's the only case where a wallet can go negative — the next call comes back 402.
Implementation examples
Paste-ready code, using the same models Loom — our own product — runs in production, with the handling that usually gets skipped: insufficient balance, a chunk split mid-stream, and reading usage at the end of the call.
Streaming chat
No dependencies: fetch and TextDecoder are native on Node 18+. The parser keeps the buffer remainder because a network chunk can cut an SSE line in half.
javascript
const BASE = 'https://api.entelecy.ai';
const KEY = process.env.ENTELECY_API_KEY;
/**
* Chat com streaming. Devolve o texto completo e o usage do ultimo chunk.
* O gateway sempre pede usage no fim do stream — nao e preciso configurar nada.
*/
export async function chatStream(messages, { effort = 'high', onDelta } = {}) {
const res = await fetch(`${BASE}/v1/chat/completions`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${KEY}`,
'Content-Type': 'application/json',
},
// model e effort sao eixos separados: o nome escolhe a faixa, o effort a profundidade
body: JSON.stringify({ model: 'loom-flash', effort, stream: true, messages }),
});
if (!res.ok) {
// 402 = saldo insuficiente; o corpo traz balance/required/upgrade_url
const err = await res.json().catch(() => ({}));
throw new Error(`${res.status} ${err?.error?.type ?? 'erro'}: ${err?.error?.message ?? ''}`);
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '', text = '', usage = null;
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// SSE: eventos separados por linha; so nos importam as linhas "data: "
const lines = buffer.split('\n');
buffer = lines.pop() ?? '';
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const payload = line.slice(6).trim();
if (payload === '[DONE]') continue;
let chunk;
try { chunk = JSON.parse(payload); } catch { continue; } // chunk partido
const delta = chunk.choices?.[0]?.delta?.content;
if (delta) { text += delta; onDelta?.(delta); }
if (chunk.usage) usage = chunk.usage; // ultimo chunk
}
}
return { text, usage };
}
Generate an image and save it
javascript
import { writeFile } from 'node:fs/promises';
export async function gerarImagem(prompt, { size = '1024x1024' } = {}) {
const res = await fetch(`${BASE}/v1/images/generations`, {
method: 'POST',
headers: { 'Authorization': `Bearer ${KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model: 'loom-image', prompt, size, n: 1 }),
});
const body = await res.json();
if (!res.ok) {
// gateway_invalid_size chega aqui ANTES de custar credito
throw new Error(`${body.error?.code ?? res.status}: ${body.error?.message}`);
}
await writeFile('saida.png', Buffer.from(body.data[0].b64_json, 'base64'));
return body.usage;
}
Streaming chat
With requests. Note the paired timeout: short to connect, long to read — reasoning generations can run past a minute.
python
import json, os, requests
BASE = "https://api.entelecy.ai"
KEY = os.environ["ENTELECY_API_KEY"]
def chat_stream(messages, effort: str = "high"):
"""Chat com streaming. Retorna (texto, usage)."""
with requests.post(
f"{BASE}/v1/chat/completions",
headers={
"Authorization": f"Bearer {KEY}",
"Content-Type": "application/json",
},
json={"model": "loom-flash", "effort": effort, "stream": True, "messages": messages},
stream=True,
timeout=(10, 600), # conexao curta, leitura longa
) as res:
if res.status_code == 402:
raise RuntimeError(f"saldo insuficiente: {res.json()['error']}")
res.raise_for_status()
texto, usage = [], None
for raw in res.iter_lines(decode_unicode=True):
if not raw or not raw.startswith("data: "):
continue
payload = raw[6:].strip()
if payload == "[DONE]":
break
chunk = json.loads(payload)
if chunk.get("choices"):
delta = chunk["choices"][0].get("delta", {}).get("content")
if delta:
texto.append(delta)
print(delta, end="", flush=True)
if chunk.get("usage"):
usage = chunk["usage"]
return "".join(texto), usage
With HttpClient and System.Text.Json, reading the stream as it arrives instead of waiting for the whole body.
csharp
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
public sealed class EntelecyClient(HttpClient http, string apiKey)
{
private static readonly JsonSerializerOptions Json = new(JsonSerializerDefaults.Web);
/// <summary>Chat com streaming: entrega cada delta no callback e devolve o usage final.</summary>
public async Task<JsonElement?> ChatStreamAsync(
object[] messages, Action<string> onDelta, string effort = "high", CancellationToken ct = default)
{
var body = JsonSerializer.Serialize(new { model = "loom-flash", effort, stream = true, messages });
using var req = new HttpRequestMessage(HttpMethod.Post, "/v1/chat/completions")
{
Content = new StringContent(body, Encoding.UTF8, "application/json")
};
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
using var res = await http.SendAsync(req, HttpCompletionOption.ResponseHeadersRead, ct);
if (!res.IsSuccessStatusCode)
throw new InvalidOperationException(
$"{(int)res.StatusCode}: {await res.Content.ReadAsStringAsync(ct)}");
await using var stream = await res.Content.ReadAsStreamAsync(ct);
using var reader = new StreamReader(stream);
JsonElement? usage = null;
while (await reader.ReadLineAsync(ct) is { } line)
{
if (!line.StartsWith("data: ", StringComparison.Ordinal)) continue;
var payload = line[6..].Trim();
if (payload == "[DONE]") break;
JsonDocument doc;
try { doc = JsonDocument.Parse(payload); } catch (JsonException) { continue; }
using (doc)
{
if (doc.RootElement.TryGetProperty("choices", out var choices)
&& choices.GetArrayLength() > 0
&& choices[0].TryGetProperty("delta", out var delta)
&& delta.TryGetProperty("content", out var content))
{
onDelta(content.GetString() ?? "");
}
if (doc.RootElement.TryGetProperty("usage", out var u))
usage = u.Clone();
}
}
return usage;
}
}
Limits and good practice
Stream long responses. Beyond perceived speed, it avoids proxy timeouts on slow generations.
Retry with exponential backoff. Rate limits and provider outages are transient; gateway 4xx errors don't improve on retry.
Cache the stable prefix. On prompts with a repeated knowledge base, it's the easiest saving available.
Validate image size in your own form. The gateway refuses for free, but the round trip still costs the user time.
Split long text before synthesis. There is a per-request ceiling, and shorter passages sound better.
Keep the video identifier. Polling is the only way to retrieve the result, and it's what fires billing once it's ready.
Need a higher limit, a dedicated endpoint or a model outside this list? Talk to us at [email protected].