Telluvian

curl recipes

Copy-paste requests for every shape of call, including streaming.

Every example assumes your key is in the environment:

export TELLUVIAN_API_KEY="sk_live_..."

Basic request

curl https://api.telluvian.ai/v1/chat/completions \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31B-it",
    "messages": [{"role": "user", "content": "When was the Eiffel Tower built?"}]
  }'

The response is standard Chat Completions plus two extension fields:

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "model": "google/gemma-4-31B-it",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "The Eiffel Tower opened in 1889."},
    "finish_reason": "stop"
  }],
  "usage": {"prompt_tokens": 9, "completion_tokens": 7, "total_tokens": 16},
  "tokens": ["The", " Eiffel", " Tower", " opened", " in", " 1889", "."],
  "scores": {"hallucination": [0.01, 0.02, 0.02, 0.05, 0.03, 0.61, 0.01]}
}

tokens[i] lines up with scores.hallucination[i], and joining tokens gives back content exactly.

Streaming

Set "stream": true. Use curl -N to disable buffering, or you will see the whole response arrive at once and streaming will look broken.

curl -N https://api.telluvian.ai/v1/chat/completions \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31B-it",
    "messages": [{"role": "user", "content": "Tell me about Ada Lovelace."}],
    "stream": true
  }'

Server-sent events, one JSON object per data: line:

data: {"choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}

data: {"choices":[{"index":0,"delta":{"content":"The"},"finish_reason":null}],"scores":{"hallucination":0.01}}

data: {"choices":[{"index":0,"delta":{"content":" Eiffel"},"finish_reason":null}],"scores":{"hallucination":0.02}}

data: {"choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":9,"completion_tokens":7,"total_tokens":16}}

data: [DONE]

Three chunk shapes, and code that assumes every chunk has content will crash on two of them:

  1. The first chunk carries delta.role and no content.
  2. Content chunks carry delta.content and one scores value for that token.
  3. The final chunk carries finish_reason, usage, and no content.

The stream ends with the literal line data: [DONE], which is not JSON — parse for it before attempting JSON.parse.

Usage is always on the last chunk

You do not need stream_options. Token counts arrive on the final chunk of every stream, and they are what your request is billed from.

Streaming, scores only

Pull just the per-token scores out of a stream with jq. This reads the probe name out of each chunk rather than hardcoding it, so it works whichever probe the model is serving:

curl -sN https://api.telluvian.ai/v1/chat/completions \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31B-it",
    "messages": [{"role": "user", "content": "Tell me about Ada Lovelace."}],
    "stream": true
  }' \
  | grep '^data: {' \
  | sed 's/^data: //' \
  | jq -r '(.scores // {}) as $s
           | ($s | to_entries | map(.value | tostring) | join("\t")) as $vals
           | select($vals != "")
           | "\($vals)\t\(.choices[0].delta.content // "")"'
0.01	The
0.02	 Eiffel
0.02	 Tower
0.61	 1889

No output means no scores, not a broken command

Chunks without a score omit scores entirely, so a filter that selects on it prints nothing at all rather than reporting an error — which looks exactly like a mistyped pipeline. If you get no score column, drop the jq and look at the raw data: lines to see whether the scores are there.

Scores are keyed by probe name. hallucination is the usual one, but a model can expose a different probe or several at once, so it is worth checking what you are getting rather than assuming:

# Which probes does this model report?
curl -sN https://api.telluvian.ai/v1/chat/completions \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31B-it",
    "messages": [{"role": "user", "content": "hi"}],
    "stream": true
  }' \
  | grep '^data: {' | sed 's/^data: //' \
  | jq -r 'select(.scores) | .scores | keys | join(", ")' | head -1

Why am I not getting scores?

If no chunk has a scores field, check these in order:

  1. include_scores is being set to false somewhere in your stack — a shared client wrapper, a proxy, or a framework that injects defaults. Omitting the field is not a cause: it defaults to true.
  2. The probe name is different from the one you are filtering on. Run the keys command above; if it prints a name, the scores are arriving under that key.
  3. You are calling a model you did not mean to. Confirm the exact model value your request sent; GET /v1/models lists every available model along with its probe mode.

If the keys command prints nothing for a model listed as supporting probes, that is on our side rather than yours — get in touch with the model name and roughly when you called it.

Omitting include_scores does not turn scoring off

include_scores defaults to true, so leaving it out gives you scores. Adding "include_scores": true explicitly is harmless but changes nothing — if that appeared to fix missing scores, something else changed too, usually the model being called. See include_scores.

A single null score is different, and expected: occasionally one token's reading is unavailable, most often the last token of a response cut short by max_tokens. Alignment is preserved and only that token is affected. See Scores.

Without scoring

curl https://api.telluvian.ai/v1/chat/completions \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31B-it",
    "messages": [{"role": "user", "content": "Hello"}],
    "include_scores": false
  }'

scores and tokens come back null, and the probe surcharge is not charged. See include_scores.

Multi-turn conversation

Send the whole history; the API is stateless.

curl https://api.telluvian.ai/v1/chat/completions \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31B-it",
    "messages": [
      {"role": "system", "content": "You are a concise assistant."},
      {"role": "user", "content": "When was the Eiffel Tower built?"},
      {"role": "assistant", "content": "It opened in 1889."},
      {"role": "user", "content": "Who designed it?"}
    ]
  }'

Sampling controls

The usual OpenAI parameters are supported — max_tokens, temperature, top_p, stop, seed, presence_penalty, frequency_penalty.

curl https://api.telluvian.ai/v1/chat/completions \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "google/gemma-4-31B-it",
    "messages": [{"role": "user", "content": "List three facts about Mars."}],
    "max_tokens": 200,
    "temperature": 0.2,
    "stop": ["\n\n"]
  }'

Listing models

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

Returns each model with its current price and probe mode. This endpoint requires a key, like OpenAI's.

# Just the ids and prices
curl -s https://api.telluvian.ai/v1/models \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  | jq -r '.data[] | "\(.id)\t\(.pricing.prompt)\t\(.pricing.completion)"'

Checking your rate limit

Rate-limit state is in the response headers:

curl -i https://api.telluvian.ai/v1/chat/completions \
  -H "Authorization: Bearer $TELLUVIAN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"google/gemma-4-31B-it","messages":[{"role":"user","content":"hi"}]}' \
  | grep -i 'x-ratelimit\|retry-after'

See Errors and rate limits.