Entelecy
PT Login

AI orchestration

A single entry point for all of your AI.

Text, vision, image, voice, video and search — every bit of AI your product needs, behind one key and a contract that doesn't move when the model underneath does.

You integrate once, in the shape your code already speaks. The rest — picking the route, stopping what shouldn't get through, measuring what was actually used, and keeping pace with a market that reinvents itself every quarter — is on us.

Base URL https://api.entelecy.ai Auth Authorization: Bearer kriou_live_… Formats JSON · SSE · multipart

Overview

An AI project rarely stalls on the model. It stalls on everything around it: every provider with its own shape, its own keys, its own limits and its own invoice; the proof of concept that runs in an afternoon and the full migration that shows up three months later, when something better lands. That part stops being yours.

The surface is compatible with what the industry already speaks — the same body you send an OpenAI or Anthropic client today works here, streaming included. What changes is what comes with it:

  • One credential, every capability. Text, image, voice, video and search under the same kriou_live_… key. No provider key ever travels in your code.
  • Nothing is spent before it counts. Image size, video duration, character ceiling, enabled model — all checked on the way in. A malformed request comes back in milliseconds and costs zero credits.
  • The bill closes per call. Tokens, seconds, characters and calls become a debit on the wallet of the key that spent them, from real usage. No estimates, no surprise at the end of the month.
  • The model improves without you rewriting anything. Each public name is a quality tier we measure and revisit. When something better shows up behind it, you get the swap — and your code stays as it is.

The root is public and doubles as a health probe: it answers without authentication and reports which build is live.

curl
curl https://api.entelecy.ai/ \
  -H "Accept: application/json"

# 200 OK
{
  "service": "Entelecy.Api",
  "environment": "Production",
  "build": "c2-forwarding",
  "forwarding": true,
  "timestamp": "2026-08-11T18:55:49.231Z"
}
It doesn't stop here

This page is what already stands. If your product needs a route that doesn't exist, a specific model, a flow the market doesn't ship ready-made — that is work we do, and it's usually where the conversation gets interesting. Talk to us

Authentication

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
AuthorizationrequestBearer + Entelecy key. Required on every endpoint except the root.
Content-Typerequestapplication/json, or multipart/form-data on upload endpoints.
X-Image-ProviderrequestApplies only to /v1/images/generations: picks the image provider when more than one is enabled. It has no effect on /v1/images/edits. 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].

text
data: {"id":"...","object":"chat.completion.chunk","choices":[{"delta":{"content":"Ent"}}]}

data: {"id":"...","choices":[],"usage":{"prompt_tokens":812,"completion_tokens":214,"total_tokens":1026}}

data: [DONE]
Note

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.

What every response carries

On all three conversation routes the response is normalised before it reaches you. Three guarantees that hold on any of them, streaming or not:

  • model echoes the name you asked for. It holds at the root of the body, in message.model on the first event of the Anthropic shape, and in response.model on every event of the Responses shape. What you send and what comes back speak the same language.
  • Reasoning arrives in reasoning. It shows up under choices[].message on a whole response and under choices[].delta on a stream. The older name reasoning_content stays alongside it while clients migrate — read reasoning.
  • Provider-internal fields do not come through. system_fingerprint, a third party's build id, is stripped. model already answers "who served this".
Note

Normalisation never breaks a response: a body that doesn't parse, a chunk split mid-flight, or an error coming from the provider all pass through untouched. If a new field appears, it reaches you even when the gateway doesn't know it yet.

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.

json
{
  "error": {
    "type": "invalid_request_error",
    "message": "Campo 'model' e obrigatorio."
  }
}
Status type When it happens
400invalid_request_errorEmpty body, invalid JSON, missing required field, or an envelope rejected by one of the gateway rules.
401Missing header, malformed key, revoked key, or an environment that isn't allowed.
402insufficient_creditsBalance below the operation's minimum. The body carries the balance, the shortfall and a top-up URL.
4xx / 5xxpassed throughProvider error (rate limit, refused content, outage). Status and body arrive as they came.
503upstream_unavailableThe 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_sizesize outside the model's envelope: format, pixel step, minimum or maximum area, edge or aspect ratio.
gateway_unsupported_backgroundTransparent background requested from a model that doesn't support it, with no configured alternative.
gateway_unknown_modelModel not enabled for the endpoint. The message lists the accepted ones.
gateway_invalid_durationduration missing, not an integer, or outside the allowed range for video.
gateway_invalid_resolutionVideo resolution outside the accepted list.
gateway_invalid_inputText above the speech-synthesis character ceiling. Split it up.

Image-in-chat codes

Refusals specific to an image inside the conversation. These are also 400s decided before the provider, and the message always names the content block that caused it.

code Description
gateway_vision_unsupportedThe requested model does not declare image support on this route. This is fail-closed on purpose: a new model that hasn't declared vision refuses rather than sending the image to a text-only destination.
gateway_image_invalidThe image block could not be read: corrupt base64, malformed data URI, or content that isn't an image.
gateway_image_mime_unsupportedImage type outside the accepted set. Use PNG, JPEG or WebP.
gateway_image_too_largeOne of the images is above the per-file ceiling.
gateway_too_many_imagesToo many images in one turn. Split the call, or send only the ones that matter.
gateway_payload_too_largeThe body as a whole is above the request ceiling, even with each image inside its individual limit.
gateway_remote_image_blockedThe https:// URL could not be fetched: internal address, refused host, or a download that didn't complete. When in doubt, send the image as a base64 data URI.

402 body

json
{
  "error": {
    "type": "insufficient_credits",
    "message": "Saldo insuficiente pra chamar a API.",
    "balance": 3,
    "required": 10,
    "missing": 7,
    "plan_id": "starter",
    "renews_at": "2026-09-01T00:00:00Z",
    "upgrade_url": "https://account.entelecy.ai/plans"
  }
}
Note

This is the body for the conversation, image, audio and video routes. /v1/search returns a short version with only message and upgrade_url — read the balance fields defensively, not as guaranteed.

Endpoints

Fourteen routes, grouped by capability. The path links to the section that details it; the right-hand column previews the billing unit — the detail is under Billing.

Endpoint Description Billed unit
POST /v1/chat/completionsConversation with text and images, in the Chat Completions shape.tokens
POST /v1/messagesSame conversation, in the Anthropic Messages shape — for clients that already speak it.tokens
POST /v1/responsesThe same conversation in the Responses shape — this is what Codex speaks.tokens
GET /v1/modelsCatalogue of the public names and the routes each one is valid on.not billed
POST /v1/images/generationsText-to-image generation.tokens
POST /v1/images/editsImage editing with an optional mask.tokens
POST /v1/audio/transcriptionsAudio transcription (speech → text).seconds of audio
POST /v1/audio/speechSpeech synthesis (text → speech).characters
GET /v1/audio/voicesCatalog of available voices.not billed
POST /v1/videos/generationsVideo generation (submit).generated seconds
GET /v1/videos/generations/{id}Poll a video in progress.not billed
POST /v1/videos/uploadsUpload a reference media for image-to-video.not billed
POST /v1/searchWeb search with structured results.per call
GET /Service status. Anonymous.not billed

Conversation

The same conversation in three protocols: /v1/chat/completions (OpenAI shape), /v1/messages (Anthropic shape) and /v1/responses (Responses shape, which Codex speaks). The model names are the same across all three — the name is the quality tier, and it doesn't change meaning with the protocol. Pick the one your client already speaks; the first is the general-purpose one.

POST/v1/chat/completionssupports streaming (SSE)

Fields the gateway reads or handles. Everything else is forwarded untouched.

Field Type Description
modelrequiredstringPublic model name: loom-flash or loom-pro.
effortstringHow much the model should think: none, high or max.
messagesrequiredarrayConversation turns, with role (system, user, assistant, tool) and content.
streambooleantrue turns the response into an SSE stream. Defaults to false.
max_tokensintegerCeiling on generated tokens in the response. Omitted, the model's own ceiling applies.
temperaturenumberSampling randomness: low values make the answer more predictable, high values more varied.
toolsarrayUp to 128 tools of type function, each with name (letters, digits, _ and -, up to 64 characters), description and parameters as a JSON Schema. See the shape below.
tool_choicestring / objectThe choice policy: none, auto, required, or an object naming the tool to force. See below.
response_formatobjecttext, the default, or json_object. When asking for JSON, say so in the prompt too — without it the model may emit whitespace until it hits the token ceiling.
thinkingobjectThe protocol's own thinking axis, forwarded as sent. Use it to steer by hand; for the gateway's shortcut, use effort.
reasoning_effortstringLikewise forwarded as sent. The provider's rule is high or max; to switch thinking off, use thinking with { "type": "disabled" }.

Tools

The shape of tools, and the four ways to steer the choice with tool_choice:

json
"tools": [
  {
    "type": "function",
    "function": {
      "name": "buscar_pedido",
      "description": "Busca um pedido pelo numero.",
      "parameters": {
        "type": "object",
        "properties": { "numero": { "type": "string" } },
        "required": ["numero"]
      }
    }
  }
]

// tool_choice — as quatro formas
"tool_choice": "none"       // responde sem chamar ferramenta
"tool_choice": "auto"       // o modelo decide
"tool_choice": "required"   // obriga a chamar alguma
"tool_choice": { "type": "function", "function": { "name": "buscar_pedido" } }

Model and effort

Two independent axes, both in the body: model picks the tier and effort picks how much the model thinks. Neither implies the other — loom-flash with max and loom-pro with none are both valid, and both useful.

model Profile When to use it
loom-flashFast and cheap, for volumeShort answers, classification, autocomplete, extraction — where latency rules.
loom-proMore capable, for hard workCode, multi-step analysis, long-form writing, agents with tools.
effort Profile When to use it
noneNo explicit reasoningFastest and cheapest. Answers directly, with no thinking step.
highReasoning onThe recommended default when the answer has to be right, not just quick.
maxReasoning at the ceilingHard problems: large refactors, long planning, correctness over cost.
Note

Without effort in the body, thinking and reasoning_effort travel exactly as you sent them — anyone already steering thinking by hand keeps working as before. And the response echoes back in model the same name you asked for: what you send and what comes back speak the same language.

The response, field by field

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
idstringUnique identifier for this call. Opaque: derive nothing from its shape.
objectstringchat.completion for a full response; chat.completion.chunk for each stream event.
createdintegerCreation time, in Unix seconds (UTC).
modelstringThe same name you asked for (loom-flash).
choicesarrayList of answers. Without n, exactly one.
choices[].indexintegerPosition of this choice in the list.
choices[].message / deltaobjectThe generated message: role (assistant) and content. In a stream this field is called delta and carries the new piece, not the whole text.
….reasoningstringThe 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_contentstringOld name for the same content, kept in parallel while clients migrate. It will go away; use reasoning.
choices[].finish_reasonstringWhy it stopped: stop (natural end), length (hit the token ceiling), tool_calls (wants to call a tool).
usageobjectUsage for the call — this is what billing reads. In a stream it arrives in the last event, with an empty choices.
usage.prompt_tokensintegerInput tokens (what you sent).
usage.completion_tokensintegerOutput tokens (what the model generated), reasoning included.
usage.total_tokensintegerThe sum. This is the number billing uses.
…details.cached_tokensintegerFull path: usage.prompt_tokens_details.cached_tokens. The part of the input that hit the prompt cache — repeating a large prefix is far cheaper than resending it.
…details.reasoning_tokensintegerFull path: usage.completion_tokens_details.reasoning_tokens. 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.

Sending an image

An image travels inside the conversation, as one more block of content next to the text — there is no separate endpoint and no extra field. Send several blocks to send several images.

bash
IMG=$(base64 -w0 tela.png)

curl https://api.entelecy.ai/v1/chat/completions \
  -H "Authorization: Bearer $ENTELECY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "loom-flash",
    "messages": [
      { "role": "user", "content": [
        { "type": "text", "text": "O que ha de errado neste layout?" },
        { "type": "image_url", "image_url": { "url": "data:image/png;base64,'"$IMG"'" } }
      ]}
    ]
  }'

The url takes either a base64 data URI, as above, or a public https:// address. detail (low, high, auto) controls how finely the image is read.

Note

The same block works on /v1/messages, in the Anthropic shape: {"type":"image","source":{"type":"base64","media_type":"image/png","data":"..."}}. Images are billed as part of the turn, not as a separate unit.

Request

curl
curl https://api.entelecy.ai/v1/chat/completions \
  -H "Authorization: Bearer $ENTELECY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "loom-flash",
    "effort": "high",
    "messages": [
      { "role": "system", "content": "Responda em pt-BR, direto ao ponto." },
      { "role": "user",   "content": "Explique entelequia em tres linhas." }
    ]
  }'

Response200

json
{
  "id": "bf1b8201-5085-455c-9556-8ebd39a0a34e",
  "object": "chat.completion",
  "created": 1786061432,
  "model": "loom-flash",
  "choices": [
    { "index": 0, "finish_reason": "stop",
      "message": { "role": "assistant", "content": "Entelequia e…" } }
  ],
  "usage": {
    "prompt_tokens": 812,
    "completion_tokens": 214,
    "total_tokens": 1026,
    "prompt_tokens_details": { "cached_tokens": 640 },
    "completion_tokens_details": { "reasoning_tokens": 159 }
  }
}
Note

effort only applies to /v1/chat/completions. On the other two routes it has no effect. To control thinking there, use the protocol's own axis: thinking in the Anthropic shape, reasoning.effort in Responses.

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
modelrequiredstringPublic name of the image model: loom-image. See Models.
promptrequiredstringWhat to generate. Specific prompts beat stacked adjectives.
sizestringWIDTHxHEIGHT in pixels, or auto. There is no fixed list: anything that passes the envelope rules below is valid. Valid examples: 1024x1024, 1536x1024, 1024x1536, 1920x1088, 3840x2160.
qualitystringlow, medium, high or auto. Higher quality spends more output tokens, and billing follows.
backgroundstringauto, opaque or transparent.
nintegerNumber of images per call, from 1 to 10. Each one is generated and billed separately.
output_formatstringpng, jpeg or webp. Omitted, it returns png. jpeg is faster — worth the swap when latency matters.
output_compressionintegerCompression level, from 0 to 100. Applies to jpeg and webp.

Envelope validated at the gateway

There is no fixed list of sizes. Anything that satisfies the four rules below is valid, and they are checked before the call goes out — an envelope error comes back in milliseconds and costs zero. Omitting size, or sending auto, skips the check and lets the default decide.

  • Both sides must be multiples of 16.
  • Total area sits between 655,360 and 8,294,400 pixels, and the longest edge does not exceed 3840.
  • The ratio between the sides does not exceed 3:1.
  • A transparent background asked of a model without support is rerouted to the alternate model — and billing follows the model actually used.

The response

The image comes back as base64 in the body, not as a URL to fetch later:

Field Type Description
createdintegerCreation moment, in Unix seconds.
dataarrayList with the generated images. Each item carries b64_json, the image in base64 — decode it and write it out.
usageobjectUsage for the call: input_tokens (the prompt), output_tokens (the image) and total_tokens. This is what billing reads.
…cached_tokensintegerUnder input_tokens_details.cached_tokens, the share of input that hit the cache.
Heads up

For 16:9, use 1920x1088 or 3840x2160 — at Full HD, 1088 stands in for 1080, which is not a multiple of 16. For a square, start at 1024x1024: it is the smallest multiple of 16 that clears the minimum area. The 400 message always names the rule that caught it, and the number behind it.

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
  }'

Response200

json
{
  "created": 1785969142,
  "data": [{ "b64_json": "iVBORw0KGgoAAAANSUhEUg…" }],
  "usage": {
    "input_tokens": 42,
    "input_tokens_details": { "cached_tokens": 0 },
    "output_tokens": 1568,
    "total_tokens": 1610
  }
}
POST/v1/images/edits

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.

Field Type Description
modelrequiredstringPublic name of the image model: loom-image. See Models.
promptrequiredstringWhat to change in the image you sent. Describe the edit, not the whole scene.
imagerequiredarrayBase image in base64. More than one is accepted; the mask goes with the first.
maskstringOptional mask in base64. The transparent area is the one that may change.
sizestringWIDTHxHEIGHT in pixels, or auto. There is no fixed list: anything that passes the envelope rules below is valid. Valid examples: 1024x1024, 1536x1024, 1024x1536, 1920x1088, 3840x2160.
Note

The mask marks what may change: the transparent area is the editable one. Send mask and base image at the same dimensions.

Request

json
{
  "model": "loom-image",
  "prompt": "Troque o fundo por um ceu limpo no fim da tarde",
  "image": ["iVBORw0KGgo…"],
  "mask": "iVBORw0KGgo…",
  "size": "1024x1024"
}

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.

POST/v1/audio/transcriptionsmultipart/form-data
Field Type Description
filerequiredfileAudio file: m4a, mp3, wav, ogg or webm.
modelstringloom-audio. You can leave it out — this route already knows it is speech-to-text. Same name as the voice: the endpoint decides the direction.
languagestringISO-639 code of the spoken language: pt, en, es. Omitted, the language is detected from the audio.

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.

Request

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"

Response200 · text/plain

text
Bom dia. Comecando a reuniao de quinta…
POST/v1/audio/speechreturns audio bytes
Field Type Description
inputrequiredstringText to speak, up to 5,000 characters per call. For longer texts, split it and join the audio — one call per part.
voicestringVoice identifier, taken from /v1/audio/voices. Omitted, it uses the account's default voice.
modelstringloom-audio. You can leave it out — the route already knows which engine to use. See Models.
response_formatstringmp3, a shortcut for the default, or a format in the codec_rate_bitrate shape. Omitted, it returns mp3_44100_128.
voice_settingsobjectFine voice tuning. Fields and defaults: stability (0.5) — lower means broader emotional range; similarity_boost (0.75) — how closely it holds to the original voice; style (0) — style exaggeration; use_speaker_boost (true); speed (1.0) — from 0.7 to 1.2, below 1 is slower, above 1 is faster.

Audio formats

The format name follows codec_rate_bitratemp3_44100_128 is MP3 at 44.1 kHz and 128 kbps. The available codecs:

  • mp3_22050_32, mp3_44100_32, mp3_44100_64, mp3_44100_96, mp3_44100_128, mp3_44100_192
  • opus_48000_32, opus_48000_64, opus_48000_96, opus_48000_128, opus_48000_192
  • pcm_8000, pcm_16000, pcm_22050, pcm_24000, pcm_32000, pcm_44100, pcm_48000
  • wav_8000, wav_16000, wav_22050, wav_24000, wav_32000, wav_44100, wav_48000
  • ulaw_8000 and alaw_8000 — the telephony formats, used by platforms such as Twilio.

Request

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.

Note

The catalogue is forwarded exactly as the provider returns it — the gateway does not rewrite the body. That is why this page pins no response shape here: read the list from the response and use the identifier it carries. It's the same reason the voice field has no closed list in the table above.

Request

curl
curl https://api.entelecy.ai/v1/audio/voices \
  -H "Authorization: Bearer $ENTELECY_API_KEY"

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.

POST/v1/videos/generationsasynchronous (submit + poll)
Field Type Description
modelstringPublic name of the video model: loom-video. Optional — leave it out and the mode is picked from the shape of the request (see Modes, below).
promptrequiredstringThe scene, the camera movement and the pacing.
durationrequiredintegerDuration in seconds, from 4 to 15. Always send a number: it is what sets the billing.
resolutionstring480p or 720p. Omitted, the model's default applies.
ratiostringFrame ratio: 16:9, 9:16, 4:3, 3:4, 1:1, 21:9 or adaptive. Omitted, adaptive applies — the model picks from the content.
generate_audiobooleantrue generates a soundtrack with the video, false returns it silent. Omitted, it comes with audio — the model's default is true.
bitrate_modestringstandard or high. Omitted, standard applies.
watermarkbooleantrue watermarks the video. Omitted, false applies.
return_last_framebooleantrue also returns the last frame, handy for stitching one clip into the next. Omitted, false applies.
image_urlstringURL of an image to animate. The presence of this field is what switches on image-to-video.
image_urlsarrayURLs of reference images — a character, an object or a style to hold across the scene.
video_urlsarrayURLs of reference videos, for movement or continuity.
audio_urlsarrayURLs of reference audio, when the generation should follow a track.

Modes

One route serves three paths. What picks between them is the shape of the body, not a mode field — which is why model can be left out:

Mode What triggers it Description
text → videonone of the othersJust prompt. The scene is born entirely from the description.
image → videoimage_urlAnimates an image that already exists. The URL comes from /v1/videos/uploads or from a public address.
reference → videoimage_urls
video_urls
audio_urls
Generates while holding on to elements from media you supply — character, style, movement or track.
Note

Reference wins over plain image: if the body carries image_urls, video_urls or audio_urls, the request goes down that path even when a loose image_url is also present. Sending both does not combine the modes — pick one.

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.

Request

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 '{
    "prompt": "Plano aereo de uma feira livre ao amanhecer, camera avancando devagar",
    "duration": 6,
    "resolution": "720p"
  }'

# 2) poll — ate status completed
curl https://api.entelecy.ai/v1/videos/generations/pred_01J8Z… \
  -H "Authorization: Bearer $ENTELECY_API_KEY"

Response200

json
// 1) submit
{ "data": { "id": "pred_01J8Z…", "status": "queued" } }

// 2) poll, ja pronto
{ "data": { "status": "completed", "outputs": [{ "url": "https://…/video.mp4" }] } }
GET/v1/videos/generations/{id}not billed

The submit returns an identifier; this route says where it stands. Poll until it reaches a terminal state — the poll itself is never billed.

Field Type Description
data.idstringIdentifier for the generation, the same one that goes in the poll path. It arrives in data.id.
data.statusstringCurrent state. Done is completed or succeeded — treat both as a successful end.
data.outputsarrayList with the generated media; the video URL is in outputs[].url.
Note

It is the first poll that sees a terminal state that fires the charge for the seconds requested on submit. Polling again afterwards does not charge twice.

Response200

json
{
  "data": {
    "id": "pred_01J8Z…",
    "status": "completed",
    "outputs": [{ "url": "https://…/video.mp4" }]
  }
}
POST/v1/videos/uploadsmultipart/form-data

Uploads the reference media and returns the URL that goes in image_url (or in the reference fields) of the generation body. It is multipart/form-data with a single field, and is never billed.

Field Type Description
filerequiredfileThe media to upload. It is the form's only field.

Request

curl
curl https://api.entelecy.ai/v1/videos/uploads \
  -H "Authorization: Bearer $ENTELECY_API_KEY" \
  -F "[email protected]"

# a URL devolvida aqui e a que vai em image_url no submit da geracao

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.

POST/v1/search
Field Type Description
qrequiredstringThe query. It is the one field the search always asks for.
glstringCountry for the results, as a two-letter code: br, us, pt. Changes what counts as local.
hlstringLanguage of the results, as a two-letter code: pt, en, es.
numintegerNumber of results. Omitted, 10 come back.
pageintegerResult page, to paginate beyond the first.

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.

Request

curl
curl https://api.entelecy.ai/v1/search \
  -H "Authorization: Bearer $ENTELECY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "q": "relatorio anual industria de embalagens brasil",
    "gl": "br",
    "hl": "pt",
    "num": 10
  }'

Models

The model field takes an Entelecy public name. It describes the capability you want, and is valid on the routes listed next to it:

model Endpoint Description
loom-flash/v1/chat/completions
/v1/messages
/v1/responses
Text and images, fast and cheap for volume. Accepts effort.
loom-pro/v1/chat/completions
/v1/messages
Text and images, higher capability for hard work. Accepts effort.
loom-image/v1/images/generations
/v1/images/edits
Image generation. Transparent background is handled by the gateway — you do not switch names.
loom-audio/v1/audio/speechSpeech synthesis with natural Portuguese prosody.
loom-audio/v1/audio/transcriptionsAudio transcription. Same name as the voice: the route decides the direction.
loom-video/v1/videos/generationsShort video generation, from text or from a reference image.
GET/v1/modelsnot billed

The text and image names from the table above, served by the API: if a name shows up here, it works on the routes listed beside it. The catalog does not list loom-audio or loom-video — they work on the audio and video routes all the same, but do not go through this registry. Requires authentication and is never billed.

Note

This is the route Codex uses to validate the provider: it calls GET {base_url}/models and only accepts 2xx, 401 or 403 before attempting any turn.

Request

curl
curl https://api.entelecy.ai/v1/models \
  -H "Authorization: Bearer $ENTELECY_API_KEY"

Response200

json
{
  "object": "list",
  "data": [
    {
      "id": "loom-flash",
      "object": "model",
      "owned_by": "entelecy",
      "endpoints": ["/v1/chat/completions", "/v1/messages", "/v1/responses"]
    },
    {
      "id": "loom-image",
      "object": "model",
      "owned_by": "entelecy",
      "endpoints": ["/v1/images/edits", "/v1/images/generations"]
    }
  ]
}

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.

Families tracked in this segment

GPT ImageImagenFLUXMidjourneyIdeogramStable DiffusionRecraftRunway

Voice

Speech synthesis

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.

Families tracked in this segment

ElevenLabsCartesiaHumePlay.htChirpAzure NeuralOpenAI TTS

Transcription

Speech to text

Meetings, dictation, noisy field audio. Accuracy in Portuguese, per-word timing, speaker separation and cost per hour of audio all weigh in.

Families tracked in this segment

WhisperScribeDeepgram NovaAssemblyAI UniversalSpeechmaticsParakeet

Video

Text to video and image to video

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.

Families tracked in this segment

SerperExaTavilyBrave SearchPerplexity SonarSerpAPIFirecrawl
Note

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
/v1/messages
tokensThe response's usage, with input, output and cache broken out.
/v1/responsestokensThe response's usage, under the Responses names: input_tokens, output_tokens, and the cached share inside input_tokens_details.
/v1/images/*tokensThe response's usage, including generated image tokens.
/v1/audio/transcriptionsseconds of audioAudio duration measured during transcription, rounded up.
/v1/audio/speechcharactersCharacter count of the submitted text.
/v1/videos/generationsgenerated secondsDuration requested on submit, billed when the video is ready.
/v1/searchper callOne 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;
}

Integrations

The API speaks the formats coding CLIs already use, so you can point your tool here without an adapter in between. Below is what has been tested.

Claude Code

It speaks the Anthropic format (/v1/messages), which the API exposes. Two variables point the CLI here; the other two pick the model, because the /model picker only lists its own native models.

bash
export ANTHROPIC_BASE_URL=https://api.entelecy.ai
export ANTHROPIC_AUTH_TOKEN=$ENTELECY_API_KEY

# The main model and the background one come from variables:
# its /model picker only lists its own native models.
export ANTHROPIC_MODEL=loom-flash
export ANTHROPIC_DEFAULT_HAIKU_MODEL=loom-flash

claude

Before opening the CLI, confirm the key and the address with a one-token request. A response starting with {"id":"msg_ proves both are right:

bash
curl -X POST "$ANTHROPIC_BASE_URL/v1/messages" \
  -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model":"loom-flash","max_tokens":1,"messages":[{"role":"user","content":"."}]}'

OpenCode

Uses the @ai-sdk/openai-compatible package over /v1/chat/completions. Drop the block below into your opencode.json and export ENTELECY_API_KEY:

json
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "entelecy": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "Entelecy",
      "options": {
        "baseURL": "https://api.entelecy.ai/v1",
        "apiKey": "{env:ENTELECY_API_KEY}"
      },
      "models": {
        "loom-flash": { "name": "Loom Flash" }
      }
    }
  }
}

Codex

Speaks the Responses API (/v1/responses). The profile goes in a separate file so your everyday config.toml stays untouched — in Codex 0.142.0 the [profiles.x] table became legacy. Save it as ~/.codex/entelecy.config.toml:

toml
# ~/.codex/entelecy.config.toml   (Windows: C:\Users\<user>\.codex\...)
# A SEPARATE file: your everyday config.toml is left alone.
# ORDER MATTERS: in TOML, a key written after a [header] belongs to
# that table; that is why the root ones come first.

model                = "loom-flash"
model_provider       = "entelecy"
model_context_window = 128000
model_catalog_json   = 'C:\Users\<user>\.codex\entelecy_models.json'
forced_login_method  = "api"

[model_providers.entelecy]
name     = "Entelecy"
base_url = "https://api.entelecy.ai/v1"   # WITH /v1: Codex builds the routes from here
env_key  = "ENTELECY_API_KEY"
wire_api = "responses"                    # required; the "chat" value was removed from Codex
supports_websockets    = false              # the gateway speaks HTTP/SSE, not WebSocket
stream_idle_timeout_ms = 360000             # a long turn streams for a while with no new byte

Download the model catalog to ~/.codex/entelecy_models.json (without it Codex doesn't know the context window or the reasoning levels), export the key and call the profile:

bash
setx ENTELECY_API_KEY "kriou_live_..."    # Windows; on bash: export ENTELECY_API_KEY=...
codex --profile entelecy

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].