# Build an Execution Receipt Wrapper for LLM API Calls

> Source: <https://dev.to/_9de8b28cd0a409b80cfdc/build-an-execution-receipt-wrapper-for-llm-api-calls-2nji>
> Published: 2026-07-16 06:00:38+00:00

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.
