paybondpaybond
Sign in

OpenAI Responses API spend controls

Guard OpenAI Responses API function tools with Paybond agent-agnostic middleware — authorize at the tool execute boundary before side effects.

The OpenAI Responses API exposes function tools on a streaming response object — similar to Chat Completions tool calls, but with a different client surface. Paybond does not ship a dedicated Responses adapter; use the agent-agnostic path: wrap { name, execute } handlers and map Responses tool calls to Paybond envelopes at execute time.

Tool boundary, not model middleware

Paybond guards tool execution — not responses.create inference. Keep your OpenAI client for the model; intercept before your handler performs side effects.

Try it

Terminal
Terminal commandSwipe to inspect long lines
paybond login
paybond agent demo generic smoke \
  --operation paid-tool \
  --requested-spend-cents 100 \
  --evidence-preset cost_and_completion \
  --format table

Policy scaffold

Terminal
Terminal commandSwipe to inspect long lines
paybond policy init --preset saas --out paybond.policy.yaml
paybond policy validate-tools --file paybond.policy.yaml --local-only

Wire handlers

Define side-effecting tools once; Paybond wraps them for authorization and evidence:

import OpenAI from "openai";
import { Paybond } from "@paybond/kit";

const openai = new OpenAI();
const paybond = await Paybond.open({ apiKey: process.env.PAYBOND_API_KEY! });

const { tools } = await paybond.instrument({
  policy: "./paybond.policy.yaml",
  tools: {
    "saas.provision_seat": provisionSeat,
    "saas.list_plans": listPlans,
  },
});

const toolByName = new Map(tools.map((t) => [t.name, t]));

const response = await openai.responses.create({
  model: "gpt-4.1",
  tools: [
    {
      type: "function",
      name: "saas.provision_seat",
      description: "Provision a SaaS seat",
      parameters: {
        type: "object",
        properties: {
          planId: { type: "string" },
          priceCents: { type: "integer" },
        },
        required: ["planId", "priceCents"],
      },
    },
  ],
  input: "Provision a starter seat for acme-corp.",
});

// When the response includes a function_call output item:
for (const item of response.output) {
  if (item.type === "function_call" && item.name === "saas.provision_seat") {
    const wrapped = toolByName.get(item.name)!;
    const args = JSON.parse(item.arguments);
    const result = await wrapped.execute({
      toolName: item.name,
      toolCallId: item.call_id ?? item.id,
      arguments: args,
    });
    // Feed result back into a follow-up responses.create call
  }
}

Mapping rules

  1. Register every side-effecting function name in paybond.policy.yaml with matching tools.* entries.
  2. Use the Responses call_id (or stable id) as toolCallId in the Paybond envelope.
  3. Read-only tools can omit Paybond wrapping or stay unregistered with default_deny: false only when you accept the risk.

OpenAI Agents SDK vs Responses API

SurfacePaybond path
OpenAI Agents SDK (@openai/agents)Native adapter — OpenAI Agents spend controls
Responses API (openai.responses.create)Agent-agnostic instrument() — this guide

Choose the Agents SDK adapter when you use Runner.run and FunctionTool; use this guide when you integrate directly against Responses function tools.

Developer reference: /docs/kit/agent-agnostic.