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_…(alsox-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, orstream: trueSSE)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 byPOST /v1/responses/:id/canceland 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 Peelnormalize+output_schema. Build schemas in Console → Normalize. Example: Create a response - HITL:
requires_action+POST /v1/responses/:id/inputwith{ "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.failedwith the error on the response object; no output assembly. - MFA/OTP pause →
response.incompletewithincomplete_details.reason: "requires_action". On a live create stream the socket stays open — submitPOST /v1/responses/:id/inputand 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/cancelwhile streaming) → the partial output is assembled, thenresponse.completed— the inline response object on that event reportsstatus: "completed". There is noresponse.cancelledSSE event in the OpenAI vocabulary. The persisted response keepsstatus: "cancelled"—GET /v1/responses/:idand theresponse.cancelledwebhook event are the authoritative cancellation signals. - Execution errors after admission stream as an
errorevent inside the open stream. Admission failures (401, 400 validation, 402 credits, 429 backpressure) fail the HTTP request itself before any SSE bytes — with aRetry-Afterheader on 429s.
Banana Peel extensions (banana_peel)
routing—smart(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 noextra_body— pass the field directly with// @ts-expect-errorabove it (OpenAI's documented pattern; unknown params are sent as-is in the request body). Rawfetch: 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_idchaining tools/ function-calling are accepted but not executed as OpenAI tools — browser work runs on a runner- HITL uses
required_action.type = submit_input(notsubmit_tool_outputs) usagetokens are approximate (char/4); billing is USD credits underbanana_peel.billing- Model id is always the product model
banana-peel, notgpt-*
Full create docs: Create a response. Contract: OpenAPI.