engyLive Kimi K3 now supports image input and 1M context

Getting started

Point any OpenAI-compatible client at https://api.engy.ai/v1 with your API key. Everything below is the same endpoint from a different client.

Claude Code

In ~/.claude/settings.json, then run claude:

{
  "env": {
    "ANTHROPIC_BASE_URL": "https://api.engy.ai",
    "ANTHROPIC_AUTH_TOKEN": "$ENGY_API_KEY",
    "ANTHROPIC_MODEL": "glm-5.2",
    "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-5.2"
  }
}

No /v1 on the URL; keep the haiku model set.

OpenAI API

curl https://api.engy.ai/v1/chat/completions \
  -H "Authorization: Bearer $ENGY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"glm-5.2","messages":[{"role":"user","content":"hello"}]}'
from openai import OpenAI
client = OpenAI(base_url="https://api.engy.ai/v1", api_key="$ENGY_API_KEY")
client.chat.completions.create(model="glm-5.2",
    messages=[{"role":"user","content":"hello"}])

Cursor

Cursor Settings → Models → API Keys, under OpenAI API Key:

OpenAI API Key           $ENGY_API_KEY
Override OpenAI Base URL https://api.engy.ai/v1   ← enable the toggle, then Verify

Under Models, + Add modelengy/glm-5.2, enable it, and select it in chat.

Cursor needs the slash. It wants a custom model named in vendor/model form, so the bare glm-5.2 the rest of this page uses is the one spelling it will not take. Prefix it with engy/ and any id from the models list works — engy/qwen3.8-27b, engy/deepseek-v4-flash-0731, and so on. Same model, same price; answers echo back whichever spelling you sent, and the id after the slash still has to be exact.

Custom endpoints drive the chat/plan panel; Tab and Composer stay on Cursor's own models. Requests are relayed by Cursor's servers, so it works from any machine. Needs a paid Cursor plan: on the free plan Cursor shows "Named models unavailable" for any custom model (free is Auto-only, that is Cursor's gating, not this API).

Codex

In ~/.codex/config.toml, then export ENGY_API_KEY=… and run codex:

model = "glm-5.2"
model_provider = "engy"

[model_providers.engy]
name = "engy"
base_url = "https://api.engy.ai/v1"
env_key = "ENGY_API_KEY"
wire_api = "responses"

Hermes

In ~/.hermes/config.yaml, then run hermes chat:

model:
  provider: "custom"
  default: "glm-5.2"
  base_url: "https://api.engy.ai/v1"
  api_key: "$ENGY_API_KEY"

Reasoning effort

Steer how long a model thinks with OpenAI's reasoning_effort, or OpenRouter's reasoning object. Both work on /v1/chat/completions, and you do not need to know which one a given model prefers.

curl https://api.engy.ai/v1/chat/completions \
  -H "Authorization: Bearer $ENGY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"glm-5.2",
       "reasoning_effort":"high",
       "messages":[{"role":"user","content":"Prove 2 is irrational."}]}'

The OpenRouter form is equivalent: "reasoning": {"effort": "high"}.

The tiers

Accepted values, lowest to highest: none, minimal, low, medium, high, xhigh, max. none and minimalboth mean “do not think”.

What each model hears

This is the part worth knowing: every model's chat template speaks its own dialect, so the same word does not mean the same thing everywhere. engy translates your tier into what the target model actually understands, rather than passing it through and letting the template decide. Sending high straight to Qwen3.8, for instance, is a 400 from its template — engy maps it to xhigh instead.

you sendGLMQwen3.8DeepSeek V4
lowhighlowlow
mediumhighmediumlow
highhighxhighhigh
xhighmaxxhighmax
maxmaxxhighmax

Two consequences to plan around. GLM collapses low/medium/high onto one level, so below xhigh you are choosing between only two settings. Qwen3.8 tops out at xhigh, which is also its default, so max asks for nothing extra. A model engy does not recognise gets the GLM mapping.

Turning thinking off

"reasoning_effort":"none" (or "minimal", or "reasoning":{"enabled":false}) drops the effort hint and sets chat_template_kwargs.enable_thinking = false, which every model here honours. If you set enable_thinking yourself, your value wins — engy never overrides an explicit one.

Image input

Vision models take images through the standard OpenAI image_url content part, so any client that already speaks multimodal chat works unchanged. Which models accept one is live fleet state, not a fixed list: the pricing table carries a modalities column, and https://api.engy.ai/v1/models reports the same thing as input_modalities per model. Send an image to a text-only model and you get a 400 naming the modality it does not support, before the request is dispatched.

Send an image

IMG="data:image/png;base64,$(base64 -w0 photo.png)"   # macOS: base64 -i photo.png

curl https://api.engy.ai/v1/chat/completions \
  -H "Authorization: Bearer $ENGY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- <<JSON
{"model": "kimi-k3",
 "messages": [{"role": "user", "content": [
   {"type": "text", "text": "What is in this image?"},
   {"type": "image_url", "image_url": {"url": "$IMG"}}]}]}
JSON

Text and images mix freely in one turn, and across turns — put as many image_url parts in the content list as you need, up to the limits below. Output is always text.

Inline, never linked

The image must be a base64 data: URI. An http(s) URL is rejected with a 400 rather than fetched, and this is deliberate: the gateway ships media inline to the miner, and miners run on egress-restricted GPU clusters that genuinely cannot reach most of the public internet. Fetching buyer-supplied URLs would also make the fleet an SSRF gadget. A clear error at the edge beats a confusing miner-side failure, so download the image yourself and inline it.

Limits

limitvalueover it
media per request12 MB decoded, all parts summed413
media parts per request16413
videonot supported400

The budget is on decoded bytes, so a base64 payload roughly a third larger than that still fits. It is sized well under the miner control-plane frame limit: one oversized request would otherwise take down a miner connection that is also carrying its heartbeats.

For billing headroom each image part reserves about 1,000 prompt tokens up front — a mid-size image's encoder cost. That only sizes the pre-flight reservation; you are settled on the tokens the model actually reports.

Anthropic-style blocks

On the Anthropic-compatible endpoint, a {"type":"image"} block with a base64 source is translated into the OpenAI part shape for you, media type included. A url source is passed through as-is and then meets the same data:-only rule above, so inline it there too.

Raw prompts and logprobs

/v1/completions takes a raw prompt instead of a message list: no chat template, no system prompt, and no reasoning parser rewriting <think>, so the model sees the exact bytes you send. Use it to continue a prompt from mid-turn, or to score text you supply.

Continue a raw prompt

The model picks up where your prompt stops:

curl https://api.engy.ai/v1/completions \
  -H "Authorization: Bearer $ENGY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"glm-5.2",
       "prompt":"<|user|>\nProve 2 is irrational.\n<|assistant|>\n<think>\n",
       "max_tokens":512,"temperature":0}'

Score a span

To read the log-probability of text you supplied, send logprob_start_len: the token offset where your span begins. The response carries token_logprobs and token_ids, one entry per scored token. A call scores up to 1024 tokens (the current default) over any prefix length. Pass prompt as token ids so the offsets are the ones you computed, and always send max_tokens=1:

from openai import OpenAI
client = OpenAI(base_url="https://api.engy.ai/v1", api_key="$ENGY_API_KEY")

ids = tokenizer.encode(prefix + action)   # your own tokenizer
span = len(tokenizer.encode(action))      # tokens you want scored

r = client.completions.create(model="glm-5.2", prompt=ids,
                              max_tokens=1, temperature=0,
                              extra_body={"logprob_start_len": len(ids) - span})
lp = r.choices[0].logprobs
print(sum(x for x in lp.token_logprobs if x is not None))

Memory scales with the tokens you SCORE, not with prompt length, which is what makes a short span over a 20k-token prefix practical. Prefill still costs what prefill costs: a 64-token span over a 14,900-token prefix measured 1.2 s to 4.2 s end to end on production, depending on what else the backend was serving.

The 1024 is on the SCORED span, not the prefix, which is unbounded up to the context window. 1024 passes, 1025 is refused with a 400 quoting the length it computed, so split longer spans across calls or ask us to raise the limit for your account. Send token ids rather than a string whenever the prompt is longer than the limit: a string is not tokenised at the edge, only estimated from its length, so a span over one is refused rather than scored against a number that can be off by 2x.

Score a whole prompt

echo returns a log-probability for every prompt token. The logits tensor behind it grows with prompt length until it will not fit, so echo is capped at the same 1024 tokens, applied here to the whole prompt. Past that the call is refused with a 400 pointing at logprob_start_len rather than sent to a backend that cannot serve it:

curl https://api.engy.ai/v1/completions \
  -H "Authorization: Bearer $ENGY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"glm-5.2","prompt":"The capital of France is",
       "max_tokens":1,"temperature":0,"echo":true,"logprobs":1}'

Gotchas

Always send max_tokens=1 on a scoring call. Leave it out and the request still succeeds, but the model generates to its own default first: 8,192 tokens and about two minutes on production, billed as output, for exactly the same log-probabilities a 1-token call returns in under a second.

max_tokens and logprobs must be at least 1 whenever you send them: 0 is refused with a 400 on both paths. Leaving logprobs out on the echo path returns a normal 200 with no log-probabilities at all, the one failure here that looks like success.

logprob_start_len is an absolute offset from the START of the prompt, so to score the last n tokens send prompt_tokens - n, not n. Backwards gets you a 400 quoting the span length it computed, which is usually enough to see the mistake.

text_offset comes back unpopulated on this path. Align on the returned token_ids instead.

With max_tokens=1 a scoring call is prefill plus one token, so it bills essentially as input. Log-probabilities are comparable only across calls that land on the same backend, so contact us to pin your account before a run where the numbers are compared to each other.

Agent API

Read-only, programmatic access to balance, usage, and models for agents. See the Agent API docs.