Skip to content
Resources

Authentication

API keys, the device login flow, and the child-protection pipeline every request runs.

Every request to the Trace gateway carries an API key in an Authorization header. Keys are issued to a Trace account and belong to that account's credit balance.

Key format#

trace_sk_<43 url-safe characters>

Trace stores the SHA-256 digest of the key and a display prefix (trace_sk_ plus the first eight characters of the secret). The full key exists in exactly one place: the response that created it. Nobody — including Trace staff — can read it back afterwards. If you lose a key, revoke it and make another.

A Clerk browser session is never accepted by the gateway. The two credentials are separate on purpose: a signed-in browser tab must not be replayable as API access.

Getting a key#

With trace login#

The normal path. Run:

trace login --name "Laptop"
  1. The SDK asks Trace to start a login and receives a device_code (its own secret) and a user_code (the short string it prints).
  2. Your browser opens /account/link?code=USER-CODE. Approve there while signed in to Trace.
  3. The SDK polls until the key comes back, then writes it to disk.

Login codes expire after 10 minutes and are single use. Approving a code does not create the key; the key is minted at the moment the SDK collects it, so no readable key ever sits in the database waiting.

From the account page#

Go to /account/developer, name a key, and create it. The key is shown once. Use this when you cannot run a browser and a terminal on the same machine, or when you want a separate key for a specific project.

How the SDK finds your key#

In this order, first match wins:

  1. The api_key argument: Trace(api_key="trace_sk_...").
  2. The TRACE_API_KEY environment variable.
  3. ~/.trace/credentials.json, which is what trace login writes.

If none of those produce a key, the first request raises AuthenticationError telling you to run trace login.

from trace_sdk import Trace

client = Trace()                               # stored key or TRACE_API_KEY
client = Trace(api_key="trace_sk_...")         # explicit
client = Trace(model="openai/gpt-5.6-luna")    # and a default model, if you want one

How the SDK finds your Trace#

The base URL resolves the same way:

  1. The base_url argument.
  2. The TRACE_BASE_URL environment variable.
  3. The base_url recorded in ~/.trace/credentials.json.
  4. The hosted app.

Both the app origin and the full gateway path are accepted:

You writeThe SDK uses
http://localhost:8000http://localhost:8000/api/sdk/v1
http://localhost:8000/api/sdk/v1http://localhost:8000/api/sdk/v1

The credentials file#

~/.trace/credentials.json, created mode 0600 inside a 0700 directory:

{
  "api_key": "trace_sk_…",
  "base_url": "https://trace.edu/api/sdk/v1",
  "key_name": "Laptop"
}

Set TRACE_CONFIG_DIR to move the directory — useful in a shared lab image or a container.

trace logout deletes this file. It does not revoke the key on your account; revoke at /account/developer if the key may have been seen by someone else.

Keeping a key safe#

  • Do not commit a key. Read it from the environment instead.
  • Do not paste a key into a notebook you will share or screen-share. trace whoami prints only the prefix for this reason.
  • One key per machine or project makes revoking one harmless to the others.
  • Revoking is immediate: the next request with that key gets a 401.

Child protection#

This is the reason the gateway exists. Trace does not hand a sixteen-year-old a raw provider key, because a raw provider key has no roster, no redaction, no ceiling, and no way for a school to answer for what happened. A Trace key has all four, and every SDK request runs the same student-safety pipeline as an agent-builder run — writing Python is not a way around anything the visual builder enforces.

The approved roster#

Only models on Trace's reviewed child-safe roster can be called, whatever id you send. The chat models available to the SDK are exactly the ones the agent builder offers. An unapproved id is a 404 rather than a request that leaves the building, and a model can be withdrawn deployment-wide without shipping new code to any student. See Models and pricing.

Personal information is redacted server-side#

Before any message text reaches a provider, Trace runs the same sanitize_prompt an agent-builder run uses, over the same identifier list:

RedactedReplaced with
The signed-in student's own name, email, user id, and date of birth[REDACTED_STUDENT_IDENTIFIER]
Any email address[REDACTED_EMAIL]
Any phone number[REDACTED_PHONE]
Anything shaped like a social security number[REDACTED_SSN]
A date of birth introduced as one[REDACTED_DOB]
A street address[REDACTED_ADDRESS]
A student id or username introduced as one[REDACTED_STUDENT_IDENTIFIER]

It runs server-side, in the gateway, not in this package. A student cannot turn it off, and neither can a modified client, because the redaction happens after the request arrives and before it is forwarded. It covers plain string content and the text parts of OpenAI content arrays.

Tool arguments and results are decoded, redacted recursively, and encoded again so personal information is removed without corrupting JSON.

Redaction is a safety net, not a licence. Do not send classmates' names, and do not send anything you would not put on a noticeboard.

Caps and ceilings#

ControlEffect
max_tokens capped at 4000 per requestNo single request can run away.
Request bodies capped at 256 KBNo pasting a data set into a prompt.
30 requests per minute per keyA runaway loop stops itself.
Credit balanceMust cover the reservation before a request starts.

If provider cost exceeds an estimate, Trace records the difference and refuses future requests until the balance covers a new reservation. See Credits and limits.

Data handling#

Requests are forwarded with the provider's data collection denied and zero-data-retention requested. Trace stores the ledger row for each request — which model, how many tokens, what it cost — and does not store prompt or reply text for SDK traffic.

Using the official openai package#

The gateway is wire-compatible enough that the official client works for non-streaming chat completions:

from openai import OpenAI

client = OpenAI(api_key="trace_sk_...", base_url="https://trace.edu/api/sdk/v1")
reply = client.chat.completions.create(
    model="deepseek/deepseek-v4-flash-0731:nitro",
    messages=[{"role": "user", "content": "Say hello in three words."}],
)

The call is identical to the one you would write with trace_sdk, because trace_sdk mirrors this library on purpose. What you lose by using the official client here is the Trace-specific part: the optional model=, the .text and .credits_spent shortcuts, Python functions as tools, client.credits, and errors written for a student mid-tutorial rather than for a backend engineer. The OpenAI client's typed response also drops credits_spent, which the curriculum's checkpoints refer to.

Everything on this page still applies: the same key, the same roster, the same server-side redaction, the same caps. The protection is in the gateway, not in the client, so no client can opt out of it.

This is an advanced note, not the recommended path.