Chat API
POST /v1/chat is a small chat completion endpoint for apps and agents that need a fast, cheap model next to the tools. It is billed per token from the same credits as every API, and it powers the chat in Obsidian and chat.highlnd-intel.group.
| Model | @cf/zai-org/glm-4.7-flash (GLM 4.7 Flash on Cloudflare Workers AI) |
| Auth | Authorization: Bearer hk_live_... (or a signed-in session in the browser) |
| Messages | up to 50, roles system, user, assistant, each up to 16,000 characters |
max_tokens | default 512, max 2048 |
| Price | per 1,000 input and output tokens, live at GET /v1/chat/rates; minimum 1 credit per call |
| Privacy | PII is redacted before the model sees it. Messages and replies are never logged or stored. |
Rates
bash
curl -s https://api.highlnd-intel.group/v1/chat/ratesjson
{
"model": "@cf/zai-org/glm-4.7-flash",
"markup": 1.35,
"currency": "CAD",
"cents_per_1k_in": 0.01,
"cents_per_1k_out": 0.07,
"min_charge_cents": 1,
"max_tokens": { "default": 512, "max": 2048 }
}The numbers above are an example: always read the live endpoint.
Send a chat
curl
curl -s https://api.highlnd-intel.group/v1/chat \
-H "authorization: Bearer $HIGHLAND_KEY" \
-H "content-type: application/json" \
-d '{
"messages": [
{"role": "system", "content": "You are a concise security analyst."},
{"role": "user", "content": "Explain DMARC p=reject in two sentences."}
],
"max_tokens": 300
}'json
{
"reply": "DMARC p=reject tells receiving mail servers to refuse ...",
"model": "@cf/zai-org/glm-4.7-flash",
"tokens_in": 38,
"tokens_out": 61,
"charged_cents": 1,
"balance_cents": 1287
}The response headers x-highland-charged and x-highland-balance carry the same two numbers, so you can read them without parsing the body.
python
import os, requests
history = [{"role": "system", "content": "Answer in plain English."}]
def ask(text):
history.append({"role": "user", "content": text})
r = requests.post("https://api.highlnd-intel.group/v1/chat",
headers={"authorization": f"Bearer {os.environ['HIGHLAND_KEY']}"},
json={"messages": history[-50:], "max_tokens": 512}, timeout=60)
r.raise_for_status()
body = r.json()
history.append({"role": "assistant", "content": body["reply"]})
return body["reply"], body["balance_cents"]
print(ask("What is an SPF record?"))
print(ask("And how is DKIM different?"))javascript
const res = await fetch("https://api.highlnd-intel.group/v1/chat", {
method: "POST",
headers: { authorization: `Bearer ${process.env.HIGHLAND_KEY}`, "content-type": "application/json" },
body: JSON.stringify({ messages: [{ role: "user", content: "Write a commit message for: fix null check in parser" }], max_tokens: 120 }),
});
const { reply, charged_cents } = await res.json();
console.log(reply, `(${charged_cents} credit)`);How billing works
- Hold. Before the model runs, the worst case for your
max_tokensis reserved from your balance. If your balance is small,max_tokensis lowered so the worst case fits. - Run. The model answers.
- Settle. You are charged the real token cost (never more than the hold), rounded up to the next credit.
- Release. The rest of the hold goes back. If the model fails, all of it goes back.
Errors
| Status | Error | Meaning |
|---|---|---|
| 400 | invalid_json | The body is not a JSON object. |
| 400 | invalid_messages | Missing or malformed messages, more than 50, one over 16,000 characters, a bad role, or no user message. |
| 400 | invalid_max_tokens | max_tokens is not a whole number from 1 to 2048. |
| 401 | api_key_required / invalid_api_key | Missing or wrong key. |
| 413 | body_too_large | The request body is too big. |
| 402 | insufficient_credits | Balance too low even for the minimum. Top up or redeem a coupon. |
| 502 | ai_error | The model failed. Nothing was charged; retry. |
| 503 | not_configured | Chat is temporarily off. |
Free credits from coupons work here, which makes them a good way to try chat and Obsidian.