OpenAI Responses compatibility

Banana Peel's primary HTTP API is an OpenAI Responses drop-in for browser-agent runs. Your existing OpenAI SDK code works after changing two values: baseURL and the API key. The model id is banana-peel.

This is the product API, not a provider wrapper — if you're migrating from a Browserbase/Steel-style SDK instead, see Compatibility wrappers.

Base URL & auth

  • OpenAI SDK baseURL: https://bananapeel.com/api/v1
  • Auth: Authorization: Bearer bp_live_… (also x-api-key)
  • Model: banana-peel
  • Create keys via agent-native signup (npx -y @banana-peel/cli init --agent --json) or, for humans who already have an account, Console → API Keys
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.BANANA_PEEL_API_KEY, // bp_live_…
  baseURL: process.env.BANANA_PEEL_BASE + "/api/v1",
  // https://bananapeel.com/api/v1
});

const response = await client.responses.create({
  model: "banana-peel",
  input: "Extract the title from https://example.com",
  // Banana Peel extension (defaults to smart if omitted). The TS SDK has no
  // extra_body — this is OpenAI's documented pattern for undocumented params:
  // @ts-expect-error routing is a Banana Peel field
  routing: "smart",
});

console.log(response.status, response.output_text);

Supported OpenAI-shaped surface

  • POST /v1/responses — create (sync, background: true, or stream: true SSE)
  • GET /v1/responses — list; GET /v1/responses/:id — retrieve / poll
  • Response fields: id, object: "response", created_at, status, error, model, output, output_text, usage, metadata, required_action, incomplete_details
  • Statuses: completed | failed | in_progress | requires_action | cancelled (returned by POST /v1/responses/:id/cancel and on later reads of a cancelled run)
  • Streaming SSE events: response.created, response.in_progress, response.output_text.delta (+ the output assembly events), then one terminal event — full matrix below
  • Output normalization (+$0.10 / run): OpenAI text.format / response_format, or Banana Peel normalize + output_schema. Build schemas in Console → Normalize. Example: Create a response
  • HITL: requires_action + POST /v1/responses/:id/input with { "code" } / { "answer" }
const stream = await client.responses.create({
  model: "banana-peel",
  input: "Extract the title from https://example.com",
  stream: true,
  // @ts-expect-error
  routing: "smart",
});

for await (const event of stream) {
  if (event.type === "response.output_text.delta") {
    process.stdout.write(event.delta);
  }
}

SSE event matrix (stream: true)

Every stream opens with response.created response.in_progress (repeated as a heartbeat every ~4s while the run executes). When output exists, it is assembled with response.output_item.added response.content_part.added response.output_text.delta × N → response.output_text.done response.content_part.done response.output_item.done. The terminal event depends on how the run ended:

  • Completed → output assembly, then response.completed.
  • Failed (including blocked / empty verdicts) → response.failed with the error on the response object; no output assembly.
  • MFA/OTP pauseresponse.incomplete with incomplete_details.reason: "requires_action". On a live create stream the socket stays open — submit POST /v1/responses/:id/input and the stream continues to its real terminal event. (A replayed stream of an already-paused run closes after this event.)
  • Cancelled (POST /v1/responses/:id/cancel while streaming) → the partial output is assembled, then response.completed — the inline response object on that event reports status: "completed". There is no response.cancelled SSE event in the OpenAI vocabulary. The persisted response keeps status: "cancelled" GET /v1/responses/:id and the response.cancelled webhook event are the authoritative cancellation signals.
  • Execution errors after admission stream as an error event inside the open stream. Admission failures (401, 400 validation, 402 credits, 429 backpressure) fail the HTTP request itself before any SSE bytes — with a Retry-After header on 429s.

Banana Peel extensions (banana_peel)

  • routingsmart (default, production) | learning / learning-all (teach the router the best path — calibrate the brain so later smart runs pick the winner) | named runner | SOC2/HIPAA/EU | runner list.
  • Passing extensions per SDK — Python: extra_body={"routing": "smart"} (the SDK's official mechanism for undocumented params). TypeScript: the SDK has no extra_body — pass the field directly with // @ts-expect-errorabove it (OpenAI's documented pattern; unknown params are sent as-is in the request body). Raw fetch: just include the field in the JSON body.
  • Envelope under banana_peel: runner, session, artifacts, cost, confidence, ran_in, billing, learning fields
  • Structured task input: { url, goal, success_criteria, steps }
  • Dry-run cost: POST /api/v1/estimate (same body)
  • Long runs: prefer background: true + poll (60–420s typical)

Intentional differences

  • No multi-turn conversation store / previous_response_id chaining
  • tools / function-calling are accepted but not executed as OpenAI tools — browser work runs on a runner
  • HITL uses required_action.type = submit_input (not submit_tool_outputs)
  • usage tokens are approximate (char/4); billing is USD credits under banana_peel.billing
  • Model id is always the product model banana-peel, not gpt-*

Full create docs: Create a response. Contract: OpenAPI.

Command Palette

Search for a command to run...