# How to Debug Provider API Payloads in pi-ai: A Complete Guide

> Debug provider API payloads in pi-ai by intercepting and inspecting JSON with the onPayload callback. Learn how to analyze requests before they are sent.

- Repository: [Mario Zechner/pi-mono](https://github.com/badlogic/pi-mono)
- Tags: how-to-guide
- Published: 2026-02-16

---

**Use the `onPayload` callback in `StreamOptions` to intercept and inspect the exact JSON payload sent to any AI provider before the HTTP request executes.**

Debugging provider API payloads in pi-ai is essential when troubleshooting request format errors or unexpected provider responses. The `badlogic/pi-mono` repository provides a built-in debugging hook through the `onPayload` callback available in all streaming options. This mechanism allows you to inspect the exact request body that gets transmitted to providers like OpenAI, Anthropic, Google, and Amazon Bedrock.

## Understanding the onPayload Callback Architecture

The debugging capability centers on the **`onPayload`** optional property defined in the core type definitions. In [`packages/ai/src/types.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/types.ts), the `StreamOptions` interface declares:

```typescript
export interface StreamOptions {
  // ... other options ...
  onPayload?: (payload: unknown) => void;
}

```

This callback receives the complete request object immediately before the provider's SDK or HTTP client transmits it. The `SimpleStreamOptions` class in [`packages/ai/src/providers/simple-options.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/simple-options.ts) propagates this callback from user-supplied options into the internal `StreamOptions`, ensuring all provider implementations inherit the debugging hook.

## Where the Debugging Hook Lives in the Source Code

Every provider implementation in the pi-ai codebase invokes `options?.onPayload?.(requestObject)` immediately before executing the network request:

| Provider | File Path | Line Reference |
|----------|-----------|----------------|
| **OpenAI Responses** | [`packages/ai/src/providers/openai-responses.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/openai-responses.ts) | Lines 93-94 |
| **OpenAI Completions** | [`packages/ai/src/providers/openai-completions.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/openai-completions.ts) | Line 108 |
| **Anthropic** | [`packages/ai/src/providers/anthropic.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/anthropic.ts) | Line 239 |
| **Google Gemini CLI** | [`packages/ai/src/providers/google-gemini-cli.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/google-gemini-cli.ts) | Line 363 |
| **Amazon Bedrock** | [`packages/ai/src/providers/amazon-bedrock.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/amazon-bedrock.ts) | Line 148 |

This consistent pattern means you can use identical debugging code across all providers supported by pi-ai.

## Practical Implementation: Logging Payloads

### Basic Console Logging

To quickly inspect payloads during development, pass a simple logging function to the `onPayload` option:

```typescript
import { streamSimpleOpenAIResponses } from "pi-mono/packages/ai/src/providers/openai-responses.js";

function logPayload(payload: unknown) {
  console.log("=== Provider payload ===");
  console.dir(payload, { depth: null, colors: true });
}

const options = {
  apiKey: process.env.OPENAI_API_KEY,
  onPayload: logPayload,
  maxTokens: 1024,
  temperature: 0.7,
};

const model = {
  api: "openai-responses",
  provider: "openai",
  id: "gpt-4o-mini",
  baseUrl: "https://api.openai.com/v1",
};

const context = { 
  messages: [{ role: "user", content: "Explain pi-ai debugging." }] 
};

const stream = streamSimpleOpenAIResponses(model, context, options);

for await (const event of stream) {
  if (event.type === "error") console.error(event.error);
}

```

When executed, this outputs the exact JSON structure sent to OpenAI, including normalized field names like `max_output_tokens` instead of `maxTokens`.

### Writing Payloads to File for Analysis

For debugging production issues or sharing request payloads with team members, write the payload to a timestamped file:

```typescript
import { writeFileSync } from "node:fs";
import { streamSimpleOpenAIResponses } from "pi-mono/packages/ai/src/providers/openai-responses.js";

function dumpPayloadToFile(payload: unknown) {
  const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
  const filename = `debug-payload-${timestamp}.json`;
  writeFileSync(filename, JSON.stringify(payload, null, 2));
  console.log(`Payload written to ${filename}`);
}

const options = {
  apiKey: process.env.OPENAI_API_KEY,
  onPayload: dumpPayloadToFile,
  maxTokens: 512,
  temperature: 0.5,
};

// ... model, context, and stream execution as shown above

```

This creates files like [`debug-payload-2024-01-15T10-30-00-000Z.json`](https://github.com/badlogic/pi-mono/blob/main/debug-payload-2024-01-15T10-30-00-000Z.json) containing the complete request payload for later inspection.

## Debugging Tips and Common Issues

When analyzing provider API payloads in pi-ai, look for these specific patterns:

| Situation | What to Inspect in the Payload |
|-----------|-------------------------------|
| **Missing field errors** | Compare the printed object against the provider's official API documentation. Check for required fields like `model`, `messages`, or `input`. |
| **Unexpected parameter values** | Verify that your `SimpleStreamOptions` (e.g., `temperature`, `maxTokens`) are correctly mapped to provider-specific field names (e.g., `max_output_tokens` for OpenAI). |
| **Provider-specific flags** | Ensure you pass the appropriate extended options type (e.g., `OpenAIResponsesOptions`) to access provider-specific parameters like `service_tier`. |
| **Authentication issues** | While the payload doesn't contain the API key, verify that the `model` object includes correct `baseUrl` and provider identifiers. |
| **Retry behavior** | If you see `maxRetryDelayMs` or similar retry configuration in the options, adjust these values and observe how the payload structure changes across retries. |

## Summary

- The **`onPayload`** callback in `StreamOptions` provides a unified debugging hook across all pi-ai providers.
- Located in [`packages/ai/src/types.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/types.ts) and implemented consistently across provider files like [`openai-responses.ts`](https://github.com/badlogic/pi-mono/blob/main/openai-responses.ts) and [`anthropic.ts`](https://github.com/badlogic/pi-mono/blob/main/anthropic.ts), this callback receives the complete request object before transmission.
- Pass a logging function to `onPayload` to inspect field mappings, verify parameter values, and troubleshoot provider-specific request formats.
- For persistent debugging, write payloads to timestamped JSON files using Node.js `fs` module within your callback function.

## Frequently Asked Questions

### How do I enable payload debugging for all providers in pi-ai?

Pass the `onPayload` callback to any streaming function's options object. Since all providers in the `badlogic/pi-mono` repository implement this hook consistently—whether using `streamOpenAIResponses`, `streamAnthropic`, or `streamAmazonBedrock`—the same debugging code works across every provider without modification.

### What information does the onPayload callback receive?

The callback receives the complete request payload as an `unknown` type, which typically contains the normalized request body ready for transmission. For OpenAI providers in [`packages/ai/src/providers/openai-responses.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/openai-responses.ts), this includes fields like `model`, `input`, `max_output_tokens`, and `temperature`. For Anthropic in [`packages/ai/src/providers/anthropic.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/providers/anthropic.ts), you'll see `model`, `messages`, `max_tokens`, and other provider-specific parameters.

### Can I use onPayload in production environments?

Yes, but implement it carefully. The callback executes synchronously before every provider request, so heavy operations like file I/O or network calls will block the stream initialization. For production debugging, consider conditional logging based on environment variables, or write payloads to an async queue rather than blocking the main thread. The `onPayload` hook itself adds negligible overhead if your callback simply pushes data to an in-memory array or performs quick console logging.

### Why don't I see authentication headers in the payload object?

The `onPayload` callback intentionally receives only the request body payload, not the HTTP headers or authentication configuration. This design prevents accidental logging of sensitive API keys while still allowing inspection of the data structure. To verify authentication issues, check that your `apiKey` is correctly passed in the options object and that the `model` configuration includes the correct `baseUrl` and provider identifiers as defined in [`packages/ai/src/types.ts`](https://github.com/badlogic/pi-mono/blob/main/packages/ai/src/types.ts).