Build an Execution Receipt Wrapper for LLM API Calls A developer proposes an execution receipt wrapper for LLM API calls that preserves operational metadata without logging full prompts or responses. The receipt includes fields like request ID, latency, validation outcome, and fallback status, enabling investigation of production responses while minimizing data exposure. The approach uses an append-oriented, versioned store separate from general-purpose logs. Model integrations are easy to instrument badly. Logging every prompt creates unnecessary data exposure. Logging only the model name leaves too little evidence when a production response needs investigation. An execution receipt offers a middle path: preserve operational metadata without copying the full interaction into a general-purpose log. What belongs in the receipt? Useful fields include: request and task identifiers; prompt, policy, and schema versions; endpoint alias; start time and latency; usage measurements; validation outcome; fallback or retry status. Application request ↓ Redaction and classification ↓ Model endpoint ↓ Output validation ↓ Execution receipt store Teams evaluating model endpoints for this architecture can place VectorEngine behind the same application-owned wrapper and receipt format. Disclosure: this article includes an external referral link for readers who want to explore the platform. TypeScript-style pseudocode type Receipt = { requestId: string; task: string; promptVersion: string; endpointAlias: string; startedAt: string; latencyMs?: number; contractPassed?: boolean; fallbackUsed: boolean; status: "started" | "completed" | "failed"; }; async function invokeWithReceipt input: unknown, ctx: Context { const started = Date.now ; const receipt: Receipt = { requestId: ctx.requestId, task: ctx.task, promptVersion: ctx.promptVersion, endpointAlias: ctx.endpointAlias, startedAt: new Date started .toISOString , fallbackUsed: false, status: "started" }; try { const result = await invokeEndpoint input, ctx.endpointAlias ; receipt.latencyMs = Date.now - started; receipt.contractPassed = validateOutput result ; receipt.status = "completed"; await appendReceipt receipt ; return result; } catch error { receipt.latencyMs = Date.now - started; receipt.status = "failed"; await appendReceipt receipt ; throw error; } } This example intentionally excludes raw input and output. If investigation requires content access, store it under a separate retention and authorization policy rather than embedding it in every receipt. Receipts should also be append-oriented and versioned. Editing old records in place makes later reconstruction harder. The goal is modest: retain enough evidence to understand how a model-backed feature executed, while collecting no more content than the workflow requires.