Data Model

June 21, 2026 · View on GitHub

Reference for the entry summary objects broadcast from server to dashboard via SSE, and persisted to ~/.ccxray/logs/index.ndjson.

For the aggregated schema that ccxray usage --json derives from these entries, see usage.md.

Entry summary

Produced by server/sse-broadcast.js summarizeEntry(). The full request/response payloads are NOT included — they live on disk and are lazy-loaded when a turn is selected.

Core identity

FieldTypeDescription
idstringTurn ID, format YYYY-MM-DDTHH-MM-SS-mmm
tsnumberUnix ms, when proxy first received the request
receivedAtnumber | nullAlias of ts retained for client-side gap timing
sessionIdstringSession ID (from request metadata.user_id, or inferred)
sessionInferredbooleantrue if session was attributed by inference (no explicit ID)
methodstringHTTP method (always POST for /v1/messages)
urlstringRequest path
cwdstring | nullWorking dir extracted from system prompt
isSubagentbooleantrue if request has no cwd in system prompt (spawned via Agent tool)

Request shape

FieldTypeDescription
modelstring | nullModel ID from request body (e.g., claude-opus-4-6)
msgCountnumberLength of messages[]
toolCountnumberLength of tools[]
toolCallsobject{ toolName: count } — tool_use blocks across messages
skillCallsobject{ skillName: count }Skill tool calls by invoked skill (Anthropic only; absent on older entries). Read by ccxray usage for per-skill stats

Response outcome

FieldTypeDescription
statusnumberHTTP status code
elapsedstringSeconds as 1-decimal string (e.g., "4.3")
isSSEbooleantrue if upstream response was streaming
stopReasonstringAPI stop_reason (end_turn | tool_use | max_tokens | …)
titlestring | nullTurn description. Cascade: response text → last user text → tool-result summary (↩ ToolA · ToolB) → subagent's first user text
thinkingDurationnumber | nullExtended-thinking seconds if captured from SSE

Usage & cost

FieldTypeDescription
usageobject | null{ input_tokens, output_tokens, cache_creation_input_tokens, cache_read_input_tokens }
costobject | null{ cost, inputCost, outputCost, cacheReadCost, cacheWriteCost }
maxContextnumberModel context window (200K / 1M based on [1m] suffix in system prompt)
tokensobject | null{ system, tools, messages, total, contextBreakdown, perMessage } — structural breakdown from tokenizeRequest

Risk signals

FieldTypeDescription
toolFailbooleanAt least one tool_result in the request had is_error: true
duplicateToolCallsobject | null{ toolName: extraCount } — repeated tool_use with same input
hasCredentialboolean | undefinedCredential pattern (API key, SSH key, etc.) detected in response or messages
toolSourcesobject | undefined{ tool_use_id: 'network' | 'local' | 'local:sensitive' } — taint classification

Backward compatibility

Older index.ndjson entries may lack newer fields. Consumers SHOULD use nullish-coalescing defaults:

const toolFail = entry.toolFail ?? false;
const duplicateToolCalls = entry.duplicateToolCalls ?? null;
const hasCredential = entry.hasCredential ?? false;

Where fields are computed

FieldSource file
usage, costserver/pricing.js
maxContextserver/config.js getMaxContext()
titleserver/forward.js title cascade using server/helpers.js extractors
tokensserver/helpers.js tokenizeRequest()
toolCalls, duplicateToolCallsserver/helpers.js extractToolCalls() / extractDuplicateToolCalls()
skillCallsserver/helpers.js extractSkillCalls()
toolFailserver/helpers.js hasToolFail()
hasCredentialserver/helpers.js entryHasCredential()
toolSourcesserver/helpers.js buildToolSources()
isSubagent, cwdserver/store.js extractCwd()