engyLive Kimi K3 and DeepSeek V4 Flash are now live on engy.ai!

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 modelglm-5.2, enable it, and select it in chat. 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"

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.