← Session Lens / API
Get a token

Driving Session Lens from code

Everything the page does you can do from a script: hand it a transcript of an agent session and get back a review, a handoff digest, or a root-cause analysis. Reading a session is a client-side concern — the API only sees the transcript you send it.

Base URL: https://api.skillsafe.ai/v1/app-api

The envelope

Every response is {"ok": true, "data": {...}} on success and {"ok": false, "error": {"code": "...", "message": "..."}} on failure. Check for error before reading data; the HTTP status matches the code but the body is where the detail is.

Two things that bite

The request body IS the input object. There is no input wrapper and no slug header — the slug is in the host. Wrapping the input returns 200 while hiding task from the model, so the run silently answers as a different lane than the one you asked for. That is the single most expensive mistake available here.

Send an Idempotency-Key on every run. Retrying with the same key returns the same job instead of billing twice. Derive it from a hash of the input plus an attempt counter, so a network blip replays and a genuine second attempt does not.

1 · Get a token

Open the tokens page in a browser: it shows the token this origin holds, mints a guest one, and signs you in for a personal one. A guest token can call /me and /estimate — enough to price a review. Running a lane is metered and needs a personal token.

2 · A tiny client

One helper that adds the bearer header and unwraps the envelope. Everything below assumes it.

3 · Who am I, and can I afford this?

GET /me returns the account, the kind of token, and the credit balance. Compare the balance against the hold from /estimate before running — a 402 after submit is a failure of your client, not of the user.

4 · Price the run (free)

POST /estimate costs nothing and creates no job. Assert that model_alias reads gpt-terra and markup_bps is 1000; a successful estimate proves the token, the input shape and the model binding are all valid. Re-estimate on every lane change, because the hold differs per lane.

5 · Run it and poll

POST /run returns a job_id; poll GET /jobs/{job_id} to a terminal state. charged_credits is the real cost and is usually far below the hold, which prices the full output cap. If truncated is true the answer was cut short for balance reasons — surface that rather than presenting a clipped answer as complete.

6 · Or stream it

POST /run-stream is the same run over server-sent events. Frames are {"text": "..."} deltas followed by a terminal {"status": ...}. Prefer this: the answer is long enough to want progress, and the section headings arriving in the stream are exactly what the page uses to advance its progress stages.

7 · The task field: three lanes

task is required and selects the lane. All three take the same input and return the same envelope; what differs is the question and the verdict vocabulary.

taskVerdict is one ofWhat it answers
reviewefficient | acceptable | wastefulWas the run a good use of the agent? Thrash, failure loops, instruction drift, token shape.
digestcomplete | partial | unclearWhat did the session change? The PR note: files written, decisions taken, what is unfinished.
debugroot-cause-found | probable-cause | insufficient-evidenceWhy did it fail? The earliest point the run went off the rails, working back from the errors.
Lanes

task: review

Was the run a good use of the agent? Thrash, failure loops, instruction drift, token shape.

task: digest

What did the session change? The PR note: files written, decisions taken, what is unfinished.

task: debug

Why did it fail? The earliest point the run went off the rails, working back from the errors.

8 · Building the transcript

This is the part that decides whether the answer is any good. A real session is megabytes; you have to clip, and how you clip matters more than the budget you pick.

The renderer the page itself uses, in outline. Per-block caps: user text 4000, agent text 2500, thinking 700, tool arguments 700, successful tool result 900, failed tool result 2200.

9 · The facts field, and why it matters

facts is the exact census of the session — turn count, tool calls by name, failures by tool, repeated identical calls, files touched, token totals, cache hit rate. It is arithmetic over the file, so it is true by construction, and the prompt tells the model that the census wins over its own reading.

Send it, then check the answer against it. Every count the model restates in its ## NUMBERS section, every turn number it cites, and every path it quotes as evidence can be compared to the census. The page prints the disagreements above the findings. That check is the only reason to trust any of this, and it is a dozen lines of code — do not skip it.

10 · The output contract

Every lane returns the same plain-text envelope. Parse it forgivingly: run the parser on every delta so a stream that dies two thirds of the way through still renders what arrived.

VERDICT: <one token from the lane's list>
HEADLINE: <one sentence, at most 140 characters>

## SUMMARY
<2-5 sentences>

## FINDINGS
### <short title> | <critical|major|minor> | turn <n or ->
<1-4 sentences>
EVIDENCE: <verbatim tool name, path, or quoted excerpt>
ACTION: <one concrete change, imperative>

## NUMBERS
- <label>: <value>

## NEXT
1. <concrete next step>

Notes that matter when you write the parser: the ### line has exactly three |-separated parts; every finding carries exactly one EVIDENCE: and one ACTION:; an empty findings section is the literal line None — the run was clean on this axis. rather than a missing heading; and a NUMBERS label containing rate, ratio or percent is a derived figure, not a restatement of a count — do not reconcile it as one.

Errors

HTTPcodeWhat to do
400VALIDATION_ERRORThe body is not a JSON object, or task is not one of review, digest, debug. Also what you get if you wrapped the input in an input key.
401UNAUTHORIZEDMissing or malformed Authorization header.
403FORBIDDENThe token is valid but not for this app, or a guest token tried to /run.
402INSUFFICIENT_CREDITSBalance is below min_credits. Call /estimate first and compare against /me so this never reaches a user.
404NOT_FOUNDUnknown job id on /jobs/{id}.
409IDEMPOTENCY_CONFLICTThe same Idempotency-Key was reused with a different body. Vary the key when the input changes.
429RATE_LIMITEDBack off and retry; never tight-loop.
500INTERNALRetry once with the same Idempotency-Key — that returns the original job rather than billing a second one.
Error codes

Rate limits and good manners

Debounce /estimate — the page waits 400 ms after the last keystroke. Space out retries and back off on 429. If you are batch-reviewing a directory of sessions, run them in sequence with the per-session hash in the Idempotency-Key, so re-running the batch after a crash costs nothing for the sessions that already finished.