Telluvian

curl recipes

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

Copy this page as Markdown to paste into an AI assistant, or open it in one.

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?"
    }
  ],
  "include_scores": true
}'

This example explicitly enables verification. The response adds 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,
  "include_scores": 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 when available.
  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,
  "include_scores": 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,
  "include_scores": 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: true was not sent. Omitting the field or setting it to false disables probing. Check client wrappers and proxies too.
  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.

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.

Verification must be explicitly enabled

Omitted include_scores defaults to false. Add "include_scores": true when you want scores, and allow enough output tokens for a reasoning model to produce visible text. Reasoning-only responses have no visible tokens to score.

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 verification

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 verification fee 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. 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.