Look up a session later: verdict, evidence, identity and decisions
GET /api/v1/sessions/{sessionId} · operationId getSession
For developers: your server sends this request with a server key; it is not a button in the dashboard. To see the same information without code, open Sessions in the dashboard.
For a backend that stored the session ID with its own record (an exam attempt, an order) and checks it afterwards. Server key only, this Site only: another Site’s session, an unknown, erased or deleted one and one past retention are the same 404. Available while the session is kept: the Site’s session retention from its last activity (default 8 days, 1 to 400; at most 8 days after consent withdrawal). A read: it never extends the session or changes its verdict.
Authentication
Send Authorization: Bearer YOUR_SERVER_KEY (siteServerKey: Site server key in Authorization: Bearer <key> (the scheme is case-insensitive and required; a bare key is 401). Shown once on creation or rotation. Publishable keys cannot authenticate). Keep the server key in server-only configuration; never send it to a browser.
Parameters
sessionId(path, string, required): The session ID the browser SDK returns (getSessionId()). A reference, not a credential.
Responses
- 200: Session · body
SessionLookup· headersCache-Control - 400: bad_request: the request is invalid; message says what to fix and field names the request field · body
Error - 401: unauthorized (missing or invalid server key, missing Bearer scheme) · body
Error - 403: forbidden: the call came from a browser page or another Site; call from your server with this Site’s server key · body
Error - 404: not_found: nothing with this ID on this Site (or it is past retention), or an unknown path · body
Error - 429: rate_limited: wait for Retry-After · body
Error· headersRetry-After - 500: internal_error: fail the protected action; do not retry automatically · body
Error - 503: service_unavailable: temporarily unavailable; only verify retries · body
Error
Success body fields
sessionId(string, required) · pattern^session_[A-Za-z0-9_-]{8,64}$status("active" | "ended" | "consent_withdrawn", required)started(number, required): Unix epoch milliseconds.lastActivity(number, required): Unix epoch milliseconds.ended(number | null, required): When the session expired (ended) or consent was withdrawn, if recorded; null while active.verdict("human" | "agent" | "unknown", required): While active, the reading now (as /api/v1/verify computes it); once ended, the reading at the last activity, which is final; unknown after consent withdrawal. Unknown is not human.actionVerdict("human" | "agent" | "unknown", required): The stricter verdict used for protection and billing. It differs from verdict only when the session is analytics-only: it counts as AI agent in reports, but the evidence is not strong enough to act on, so actionVerdict stays unknown.verdictAt(number | null, required): The time the verdict was read at (now, or the last activity); null after withdrawal.agentEvidence("verified" | "automation-trace" | "injected-input" | "sustained-behaviour" | "behaviour-change" | "behaviour-corroborated" | "cursor-driven" | "behaviour" | null, required): What backs an AI agent action verdict; null otherwise. It sets the confidence band exactly as on verify: every value but behaviour is high confidence (follows the AI agent rule, Deny included, and is chargeable); behaviour (one behaviour family) is medium confidence (held for review by default, never chargeable).agentEvidenceLabel("Verified" | "Strong evidence" | "Behaviour" | "Unknown", required): The label the dashboard shows for agentEvidence: Verified and Strong evidence are high confidence, Behaviour is medium confidence, Unknown means no AI agent action verdict.everAgent(boolean | null, required): Whether the action verdict ever read AI agent in this session; null after consent withdrawal (withheld).agentFirstSeen(number | null, required)agentLastSeen(number | null, required)agent(object | null, required): The named agent and how we know, only when evidence names one beside an AI agent verdict.agent.name(string, required)agent.operator(string | null, required)agent.howWeKnow(object, required)agent.howWeKnow.strength(string, required)agent.howWeKnow.label(string, required)identity(object | null, required)identity.userId(string | null, required)identity.accountId(string | null, required)identity.verified(boolean, required): true: asserted by your server (identify); false: a browser claim.detectorVersion(string | null, required): The classifier that produced the verdict (its name, such as heuristic-v4.33); null after withdrawal.reasons(string[], required)decisions(object[], required): Newest first, at most 100; GET /api/v1/decisions.json?sessionId= reads every one.decisions[].decisionId(string, required)decisions[].actionId(string, required) · pattern^[A-Za-z0-9_-]{1,64}$decisions[].decision("allow" | "review" | "deny", required)decisions[].purpose("protect" | "display", required)decisions[].created(number, required): Unix epoch milliseconds.decisions[].outcome("performed" | "blocked" | "failed" | null, required)decisions[].review(string | null, required)decisionsComplete(boolean, required)goals(object[], required): What the session tried to do and whether it worked, one plain line each, newest first. Derived from tool runs, protected-action checks and their outcomes, work your server reported and refused tool calls; fixed templates, never page text. status is a category: succeeded, blocked, failed, waiting, abandoned or unconfirmed (no result reported, never counted as success).goals[].actionId(string, required)goals[].label(string, required)goals[].status("succeeded" | "blocked" | "failed" | "waiting" | "abandoned" | "unconfirmed", required)goals[].cause(string, required)goals[].line(string, required)goals[].attempts(integer, required)goals[].via("tool" | "protected" | "page" | "unknown"[], required)goals[].httpStatus(integer, optional)goals[].firstAt(number, required): Unix epoch milliseconds.goals[].lastAt(number, required): Unix epoch milliseconds.goalSummary(string | null, required): The top line: the one goal, or how many were tried and how they ended.retainedUntil(number, required): After this time the lookup is 404: from the last activity, the plan’s history period for a session with AI agent activity (never less than the session setting), else the Site’s session retention (default 8 days); at most 8 days after consent withdrawal.
Failures are not verdicts. Error bodies follow the Error schema: code is one of bad_request, unauthorized, session_reinit_required, forbidden, not_found, conflict, identity_required, payload_too_large, rate_limited, internal_error, service_unavailable, request_failed, approval_invalid, approval_used, approval_expired, approval_mismatch, step_up_not_held, step_up_mismatch, step_up_settled, step_up_expired, step_up_superseded, agent_tokens_disabled, tool_not_approved, tool_not_read_only. Errors and retries explains each code and what to do.
Example
curl -sS -X GET 'https://app.trytelltale.dev/api/v1/sessions/session_PASTE_SESSION_ID' \
-H "Authorization: Bearer $TELLTALE_SERVER_KEY"const response = await fetch("https://app.trytelltale.dev/api/v1/sessions/session_PASTE_SESSION_ID", {
method: "GET",
headers: {
Authorization: 'Bearer ' + process.env.TELLTALE_SERVER_KEY
},
signal: AbortSignal.timeout(5000)
});
const result = await response.json();Machine-readable contract: openapi.json. Request builder: the API explorer shows every operation as curl; it sends requests only on a local dashboard.