# Banana Peel > Smart routing for browser agents. One OpenAI-compatible API over 250+ runners; set `routing` and we pick the runner that wins. Base URL: https://bananapeel.com Env: BANANA_PEEL_BASE=https://bananapeel.com Auth env: BANANA_PEEL_API_KEY=bp_live_… ## No account yet — agent-native signup Your agent can start using Banana Peel before the human has an account. Golden path: https://bananapeel.com/docs/agents Signup: https://bananapeel.com/docs/agents/signup 1. Check for BANANA_PEEL_API_KEY / BANANAPEEL_API_KEY (whichever is set) or ~/.bananapeel/credentials. If a key exists, reuse it. Do not mint another account. 2. If absent, follow /docs/agents/signup: run `npx -y @banana-peel/cli init --agent --json`. Banana Peel detects your agent environment and configures itself. Stdout is only status, claim_url, has_existing, and configured — never the API key. Never paste the key into chat. If npx/the CLI is unavailable, use the supported HTTP fallback (challenge → signup) documented on /docs/agents/signup. Command: `npx -y @banana-peel/cli init --agent --json` - Stores the key in ~/.bananapeel/credentials (mode 0600). **Never paste the API key into chat.** - Stdout is only: {"status":"ready","claim_url":"https://…/claim/clm_…","has_existing":false,"configured":["cursor"]} - Banana Peel detects your agent environment (Claude Code, Cursor, Codex, VS Code, Windsurf, Claude Desktop) and configures MCP / skill automatically. 3. Never ask the human to create an account manually. Console /keys is an optional advanced path for humans who already have an account — not step 1 for agents. 4. Run the user's task with the stored key. Then tell the human: "I used Banana Peel. I also created your account automatically. Claim it here: [claim_url]" 5. Later: `npx -y @banana-peel/cli claim-url` (or whoami). `whoami` resolves account_id from the key (env or ~/.bananapeel/credentials) via GET /api/v1/me — never prints the key. Same $10 welcome credits as Console signup. No card at signup. Unclaimed + credits exhausted or 14d: error code account_unclaimed + claim_url. Claimed + out of credits: existing insufficient_credits (human adds a card in Billing). ### HTTP fallback (supported when npx/the CLI is unavailable) If `npx` cannot run in your environment (no Node, sandbox, registry unreachable), call the signup API directly — same account, key, and $10 welcome credits as the CLI: 1. `GET https://bananapeel.com/api/v1/agent-signup/challenge` → 200 {"challenge_id":"chal_…","expires_in_seconds":120,"instruction":"…","puzzle":{…}} Machine-solvable puzzle (not a human CAPTCHA). One-use, IP-bound, 2-minute TTL. Solve it per `instruction`. 2. `POST https://bananapeel.com/api/v1/agent-signup` with JSON {"challenge_id":"chal_…","challenge_answer":"…","client":"cli"} → 200 {"status":"ready","account_id":"acct_agent_…","api_key":"bp_live_…","claim_url":"https://…/claim/clm_…","free_credit_usd":10,"expires_at":"…"} Send a real User-Agent (bananapeel-cli/…, Cursor, Claude Code, Codex — generic UAs are rejected with code generic_user_agent). 3. The api_key is returned ONCE — and unlike the CLI (whose stdout never contains the key), this HTTP response necessarily places it in your context. Write it to ~/.bananapeel/credentials (mode 0600) and/or export BANANA_PEEL_API_KEY immediately, in the same step (e.g. pipe the response through jq straight to the file) — never echo it back, paste it into chat, or log it. Share only claim_url with the human. 4. Errors: invalid_challenge / wrong_challenge_answer (400, request a new challenge), rate_limited (429, honor Retry-After and reuse existing credentials). Full request/response reference: https://bananapeel.com/docs/agents/signup Do not clone the repo. Do not send the human to /keys. ## Read first (agents) 1. https://bananapeel.com/llms.txt (this file) 2. https://bananapeel.com/openapi.json — machine contract 3. https://bananapeel.com/docs/agents — human/agent integration guide 4. https://bananapeel.com/docs/mcp — official MCP server (stdio tools) 5. https://bananapeel.com/agent-skill — copy-paste system prompt / skill 6. https://bananapeel.com/llms-full.txt — full guide 7. https://bananapeel.com/security — security model (SOC 2 Type II, custody, encryption, trust tiers) ## MCP (preferred) Banana Peel detects your agent environment and configures itself (Claude Code, Cursor, Codex, VS Code, Windsurf, Claude Desktop). Official stdio MCP package: `@banana-peel/mcp`. Docs: https://bananapeel.com/docs/mcp Tools → API: - create_response → POST /api/v1/responses - get_response → GET /api/v1/responses/:id (Optional wait: true polls until completed, failed, or requires_action.) - submit_input → POST /api/v1/responses/:id/input - cancel_response → POST /api/v1/responses/:id/cancel - list_runs → GET /api/v1/runs - list_rankings → GET /api/v1/rankings - list_capabilities → GET /api/v1/capabilities Env: BANANA_PEEL_API_KEY=bp_live_… (alias BANANAPEEL_API_KEY) Run: `npx -y @banana-peel/mcp` (Cursor/Claude: command npx, args ["-y","@banana-peel/mcp"]) ## OpenAI SDK drop-in baseURL: https://bananapeel.com/api/v1 model: banana-peel Auth: Authorization: Bearer bp_live_… Docs: https://bananapeel.com/docs/api/openai Supports stream:true (SSE), background:true + poll, requires_action + POST …/input, Output normalization via normalize + output_schema (or text.format / response_format; +$0.10), GET /v1/responses list. ## Anthropic Messages adapter (thin) baseURL: https://bananapeel.com/api → POST /v1/messages Auth: x-api-key: bp_live_… (or Bearer) Docs: https://bananapeel.com/docs/api/anthropic Maps Messages → Responses pipeline; not a full Anthropic API. Pass normalize + output_schema on the body (+$0.10) the same as Responses. ## Primary API POST https://bananapeel.com/api/v1/responses Auth: Authorization: Bearer bp_live_… OR x-api-key: bp_live_… routing optional (defaults `smart` = production). Learning and learning-all exist to teach the router the best path: they calibrate the routing brain on a domain/task so later smart runs pick the winning runner. `learning` = live ~54-runner pool; `learning-all` = full catalog (250+). Run learning (or once, learning-all) on a new domain/task to teach the brain, then switch back to smart. Do not use learning-all for every production job. Body needs input and/or url (task text) Minimal: ``` export BANANA_PEEL_BASE=https://bananapeel.com export BANANA_PEEL_API_KEY=bp_live_… curl -s "$BANANA_PEEL_BASE/api/v1/responses" \ -H "Authorization: Bearer $BANANA_PEEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"banana-peel","input":"Extract the title from https://example.com","routing":"smart"}' ``` ## Create Response fields (from OpenAPI) Source of truth: https://bananapeel.com/openapi.json. Idempotency: send an `Idempotency-Key` header or the `idempotency_key` JSON body field (equivalent; if both, they must match). Honored on sync, background, and stream create; keys expire 24h after first use. Reuse with an identical body replays the original run (original created_at, `Idempotent-Replayed: true` header); reuse with a DIFFERENT body fails with 422 `idempotency_conflict`; a duplicate racing the original create gets 409 `idempotency_in_flight` + Retry-After. ### POST /v1/responses (`createResponse`) Create a browser-agent response Request body: - `model` (string, optional, default: "banana-peel") - `input` (string | array | object, optional) — Task text (string, OpenAI content array, or structured {url, goal, success_criteria, steps}). Required unless `url` alone is enough with goal/task. - `url` (string (uri), optional) - `instructions` (string, optional) - `goal` (string, optional) - `task` (string, optional) - `credential_id` (string, optional) — Optional vault credential id for the API key owner (create/list via /v1/credentials). After custody is satisfiable, secrets are decrypted and injected into the task for whichever runner routing selects (smart, steel, browserbase, …). Vault usage never pins routing to deck (deck_only is an opt-in custody policy; create default is trusted_runners). When routing does select Deck — pinned, smart, race, or learning — the credential is additionally attached through Deck’s native vault (registered per source, reused across runs), since Deck gates login-walled sources on an attached credential rather than the instruction text. - `stream` (boolean, optional) — OpenAI Responses SSE stream (text/event-stream). Takes precedence over background for this request. - `background` (boolean, optional) — Return 202 immediately and finish in the background. Preferred for human MFA/OTP relay (poll + POST …/input). - `metadata` (object, optional) — OpenAI metadata (string values); echoed on the response object. - `allow_interaction` (boolean, optional) — Human-in-the-loop. Default on (response id is the relay channel). false opts out; true refuses non-relay runners. - `interaction_timeout_ms` (integer, optional) — Max wait per MFA/OTP/text question (default 240000). - `totp_secret` (string, optional) — Base32 TOTP secret — OTP asks answered in-process (prefer vault credential_id). - `totp_identifier` (string, optional) — Skyvern-native TOTP inbox/phone identifier. - `totp_url` (string, optional) — Skyvern-native TOTP poll URL. - `normalize` (boolean, optional) — Paid output normalization (+$0.10 / run). Requires output_schema (or text.format / response_format schema). Build schemas in Console → Normalize. - `output_schema` (object, optional) — Desired output JSON Schema (or property map). Used when normalize is true or when a schema is supplied via text.format / response_format. HARD CONTRACT: a terminal success must populate exactly these keys (required keys present, non-empty, right type; no undeclared extra data keys unless additionalProperties: true — run metadata like url/final_url is tolerated). A run whose output cannot satisfy the schema terminates status:failed with error.code schema_violation and the nonconforming output kept under banana_peel.output for inspection; the normalization add-on is not billed on violation. Without a schema, read the stable default envelope instead: banana_peel.answer / answer_present / evidence. - `success_criteria` (string, optional) — Explicit success criterion for the quality grader (e.g. "an order number is present"). - `steps` (string[], optional) — Optional ordered steps for structured task input. - `credentials_ref` (string, optional) — Alias for credential_id (vault credential). - `custody` (string, optional) — Credential custody policy for THIS request: any | trusted_runners | deck_only | pinned:[,…]. Hard-filters the runner candidate set before any routing / scoring / fan-out; when no runner survives, the request fails fast with 409 `custody_unsatisfiable` and nothing is billed. Use with inline credentials in the task text; vault credentials carry their own stored policy (both compose as AND). Enforcement is echoed under `banana_peel.custody`. - `text` (object, optional) — OpenAI Responses text.format passthrough (json_schema → normalize). - `response_format` (object, optional) — OpenAI-style response_format (json_schema / json_object). - `idempotency_key` (string, optional) — Client retry key — equivalent to the `Idempotency-Key` HTTP header (send either; if both, they must match or the create fails with 400 `idempotency_key_mismatch`). Honored on sync, background (`background: true` / Prefer: respond-async), and streaming create; keys are per-workspace, shared across every run-creating endpoint, and expire 24h after first use. The key is reserved before dispatch: reuse with an identical body (`stream`/`background` excluded from the comparison) replays the existing run (original `created_at`, `Idempotent-Replayed: true` header) instead of starting a duplicate; reuse with a different body is refused with 422 `idempotency_conflict`; a duplicate racing the original create gets 409 `idempotency_in_flight`. - `routing` (string | string[], optional, default: "smart") — Optional (default smart). Learning and learning-all exist to teach the router the best path: they calibrate the routing brain on a domain/task so later smart runs pick the winning runner. smart = production (router uses the brain to pick/fallback) | learning = paid calibration: probe the curated live ~54-runner pool on this task/URL (not the default for production traffic) | learning-all = paid thorough calibration: full-catalog sweep (250+ options, NO early stop; most expensive; do not use for every production job) | named runner (browserbase, steel, browser-use, skyvern, hyperbrowser, playwright, deck, …) | category MVP pool (SOC2/HIPAA/EU — fixed runner lists, not attestation) | ordered runner list. Learning probes batches of 9 (concurrency 8) and stops after the first wave containing a success; learning-all keeps the same waves but sweeps the full catalog (250+ options): every dispatchable option attempts once; coming-soon catalog rows are skipped (never billed); $20 default ceiling (a catalog-wide sweep typically hits it). Learning-family runs are paid calibration sweeps (executed attempts bill); winners ranked fastest+cheapest feed future smart routing. - `sla_profile` (string, optional, enum: balanced | cheapest | fastest | most_reliable, default: "balanced") — Smart-routing preference: how the router weighs cost, speed, and block-avoidance against success probability when choosing runners. Only affects routing: "smart". - `max_fallbacks` (integer, optional, default: 5) — Smart routing: how many additional runners after the primary (1 + N). Default 5 (up to 6 attempts unless spend, time, or a breaker stops earlier). 0 pins to the first pick only. Invalid values fail free with 400 invalid_max_fallbacks. Only affects routing: "smart" (and category). Staggered race still starts the primary first. - `max_cost_usd` (number, optional) — Per-run spend ceiling (USD), honored by every routing mode. Once executed attempts’ spend reaches the ceiling, no NEW attempts / batches / waves are dispatched; in-flight attempts finish (and bill) and the run ends with error code `budget_exhausted` plus partial results under `banana_peel.routing_attempts`. Learning runs default to $20 when omitted (an explicit max_cost_usd overrides the default in either direction). The active ceiling is echoed under `banana_peel.budget` and reflected by POST /v1/estimate. ## Glossary - `routing` — what you request (`smart` | `learning` | `learning-all` | named runner | category | list) - `banana_peel.runner` — which runner executed (Browserbase, Steel, Skyvern, …) - run — one unit of work: a task routed to a runner, returning result + attempts, cost, duration, verdict. Billed per executed attempt; benchmarks are built from run outcomes. - session — a live browser you hold open and drive yourself over CDP (/api/v1/sessions, Steel proxy). No task attached, not smart-routed. Runs are what you asked for; sessions are what you're holding. Docs: https://bananapeel.com/docs/concepts/sessions ## Routing Learning and learning-all exist to teach the router the best path: they calibrate the routing brain on a domain/task so later smart runs pick the winning runner. API and MCP: routing: "learning" or routing: "learning-all". Console Compare: Learning / Learning All columns. Run learning (or once, learning-all) on a new domain/task to teach the brain, then switch back to smart. Do not use learning-all for every production job. - smart — Production: the router uses the brain to pick a runner and fall back. Rank runners by live domain success (+ Learning prefs when present), then up to `max_fallbacks` additional runners after the first pick (default 5; 0 pins to the first choice only) - learning — Paid calibration: probe the curated live pool (~54) on this task/URL so the brain learns which runners win. $20 default ceiling. Not the default for production traffic. Probe the live operational pool (~54 runners) in batches of 9 (concurrency 8); stops after the first wave with a success; rank successes by fastest+cheapest; persist for future smart. Paid calibration sweep ($20 default ceiling) - learning-all — Paid thorough calibration: sweep the full dispatchable catalog (250+), more expensive, $20 ceiling. Do not use for every production job. Full-catalog sweep (250+ options), NO early stop: every dispatchable option attempts once; catalog-only coming-soon options are skipped (never billed, see banana_peel.learning.skipped_no_adapter / pool.catalog vs pool.attempted); best success wins. Paid calibration sweep ($20 default ceiling — a catalog-wide sweep will typically hit it; bound with max_cost_usd) - named runner: browserbase, steel, browser-use, skyvern, hyperbrowser, anchor, deck, playwright, auto - chain: ["steel","skyvern","hyperbrowser"] - categories (MVP runner pools only, not attestation): SOC2, HIPAA, EU - default smart chain: browserbase → steel → hyperbrowser → browser-use → skyvern → anchor → deck → playwright ## Other endpoints - GET /api/v1/responses/:id - DELETE /api/v1/responses/:id — purge copied screenshots/artifacts immediately; later GET is 404 - POST /api/v1/responses/:id/cancel — cancel an in-flight run (returns the response with status: "cancelled") - GET /api/v1/runs?limit=50 - GET /api/v1/me — account identity for this API key (account_id, created_at, claim_url). CLI `whoami` calls this so env keys resolve. Never returns the secret. Unclaimed-freeze does not apply. - GET /api/v1/workspace — retention policy (default 7 days); PATCH retention 24h|7d|30d on Enterprise - GET /api/v1/rankings — leaderboard rows include `availability` (live | suspended | probation) - GET /api/v1/capabilities — capability matrix; each row carries `availability` (live | suspended | probation + machine-readable reason + until) from the runner-health circuit breaker, plus honest `runner_counts` (total vs live vs suspended vs probation). Suspended runners are auto-skipped by routing (probation = half-open: eligible, next attempt re-probes) — never attempted while suspended, never billed; no client-side filtering needed - GET /api/status — public platform status, no auth (ratios and timestamps only; human page at https://bananapeel.com/status) - Vault: POST /api/v1/credentials (create — body {domain, username, password, totp_secret?, custody?}; custody defaults to trusted_runners — deck_only is opt-in). GET /api/v1/credentials (list), GET/PATCH/DELETE /api/v1/credentials/:id. Create returns id + metadata; secrets are write-only (never returned by any endpoint). Use the id as credential_id on POST /api/v1/responses — secrets are injected at run time onto the runner routing selects (not registered as a Deck source, not pinned to deck). Docs: https://bananapeel.com/docs/api/credentials - Webhooks (production scale): POST /api/v1/webhooks {url, events?} — secret `whsec_…` returned once. Events: response.completed | response.failed | response.requires_action | response.cancelled. Signed with X-Banana-Peel-Signature (t=,v1= over `{t}.{raw_body}`; rotation grace includes two v1 values — verify ANY). At-least-once: dedupe on event.id. Envelope includes object banana_peel.event, api_version, sequence (monotonic per response; order not guaranteed — GET the response is authoritative). Prefer 202 + webhook over polling. Docs: https://bananapeel.com/docs/api/webhooks - Security model: https://bananapeel.com/security — Banana Peel is SOC 2 Type II; vault AES-256-GCM; custody hard-filters runners before decrypt; 7-day default artifact retention. Region pinning and contractual residency commitments are available on Enterprise (https://bananapeel.com/docs/security/regions). Canonical custody docs: https://bananapeel.com/docs/security/custody - Browserbase wrapper: /api/v1/sessions (NOT /api/wrappers/browserbase) — creates a live CDP browser you drive yourself, not a routed run - Wrappers: /api/wrappers/steel|browser-use|skyvern|hyperbrowser/… Copied screenshots, downloads, and artifact blobs are kept 7 days by default (Enterprise: 24h / 7d / 30d via PATCH /api/v1/workspace; zero-retention by contract). DELETE /api/v1/responses/:id purges copied blobs immediately. Benchmarks use success/cost/latency aggregates, not page content. https://bananapeel.com/docs/security/retention ## Long runs / dropped connections Runs often take 60–420 seconds. Prefer async create so the HTTP socket does not die mid-run: ```bash curl -s "$BANANA_PEEL_BASE/api/v1/responses" \ -H "Authorization: Bearer $BANANA_PEEL_API_KEY" \ -H "Content-Type: application/json" \ -H "Prefer: respond-async" \ -d '{ "model": "banana-peel", "input": "Extract the title from https://example.com", "routing": "smart", "background": true, "idempotency_key": "my-client-retry-key-1" }' ``` Response is HTTP 202 with `status: "in_progress"` and an `id`. Poll `GET /api/v1/responses/:id` until `status` is `completed`, `failed`, or `requires_action`. At production scale, register `POST /api/v1/webhooks` and receive signed events instead of spinning GET (polling still works). Webhooks are at-least-once — store `event.id` and ignore duplicates. Delivery order is not guaranteed; use `sequence` and treat GET as authoritative. Reuse the same `idempotency_key` (or `Idempotency-Key` header) with the identical body if the create request itself drops — Banana Peel returns the existing run instead of starting a duplicate. Do not reuse a key with a different body (422 `idempotency_conflict`). While in flight, each poll exposes live progress under `banana_peel.progress`: runner attempts so far (`runner`, `status`, `started_at`, `cost_usd`), `attempts_started` / `attempts_finished`, the current Learning `batch` ({current, total, size}), and `spend_usd` to date. On the terminal object this is superseded by `banana_peel.routing_attempts`. To stop a run that is spending: `POST /api/v1/responses/:id/cancel` — returns the response object with `status: "cancelled"`. Attempts that already executed still bill; no new attempts or Learning batches start after the cancel (in-flight upstream attempts are aborted best-effort where the adapter supports it, e.g. Skyvern). Runs also carry a hard TTL (default 20 min, `BP_RUN_HARD_TTL_MS`): a run still in progress past the TTL is marked `failed` with error code `run_timeout`, so nothing sits at `in_progress` forever. ## MFA / OTP / human input When a target site asks for MFA, 2FA, or an OTP, the run pauses immediately and asks the user for the code. It does not keep trying other runners. CAPTCHA and other bot-detection gates (reCAPTCHA, hCaptcha, Turnstile, DataDome, Cloudflare challenge, “prove you are human”) are solved, re-routed, or end blocked with reason bot_challenge. They never become a human text prompt. Poll GET /api/v1/responses/:id until status is requires_action (or handle the response.requires_action webhook). MCP create_response waits until that pause and returns human_action_required plus required_action. Submit the code with POST /api/v1/responses/{id}/input { "code": "123456" } (or { "answer": "…" } for text/confirm). MCP: submit_input({ id, code }). Then keep polling until completed or failed. If the vault credential (or request totp_secret) includes a base32 TOTP seed, OTP asks are answered in-process and the run does not pause. Username/password alone is not enough — the run still pauses for the code. Cursor / MCP: ASK THE HUMAN IN CHAT NOW for the site MFA/OTP code. Call submit_input with their code. Do not retry the task or pick another runner. Docs: https://bananapeel.com/docs/guides/mfa 1. Create with `background: true` (or MCP `create_response`, which waits until MFA or terminal by default). 2. Prefer a webhook on `response.requires_action` (time-sensitive); or poll `GET /api/v1/responses/:id` until `status: "requires_action"`. 3. Read `required_action.submit_input` (`kind`: otp|text|confirm, `channel`: totp|sms|email|unknown, `prompt`, `submit.method` + `submit.path`). 4. Submit: `POST /api/v1/responses/:id/input` with `{ "code": "123456" }` (or `answer`). MCP: `submit_input({ id, code })`. 5. Keep polling until `completed` / `failed` / `blocked`. Do not start a new run or pick another runner. ```bash # after create returns id=resp_… curl -s "$BANANA_PEEL_BASE/api/v1/responses/$ID" \ -H "Authorization: Bearer $BANANA_PEEL_API_KEY" # when requires_action: curl -s -X POST "$BANANA_PEEL_BASE/api/v1/responses/$ID/input" \ -H "Authorization: Bearer $BANANA_PEEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"code":"123456"}' ``` Shortcuts: - Vault `credential_id` with a TOTP secret → OTP answered in-process (no pause). - Body `totp_secret` (base32) → same in-process shortcut. - `allow_interaction: false` → opt out of mid-run questions. - `banana_peel.status` may show `needs_human` while the outer status is `requires_action`. - Submitted codes are not echoed and are cleared from memory after the agent consumes them. ## Errors Shape: { "error": { "message", "type", "code", "param", "doc_url", "hint?", "remediation?" } } Branch on error.code. Common: invalid_api_key, invalid_routing, missing_input, missing_output_schema, invalid_output_schema, insufficient_credits, account_unclaimed, response_not_found, invalid_custody, custody_unsatisfiable. Backpressure (429): rate_limited — the account already has its maximum concurrent Learning-family runs in flight ("learning" + "learning-all" share one cap, default 3); learning_capacity — instance fan-out ceiling saturated. EVERY 429 (including via POST /api/v1/messages and stream:true creates) carries a Retry-After header (seconds) — honor it, or poll GET /api/v1/responses/:id until an in-flight Learning run finishes (or cancel one), then retry. Nothing is created or billed on a 429. Submitting many Learning tasks? Keep at most 3 in flight and start the next as each finishes. Validation fails free: structurally unusable requests are rejected with a 400 BEFORE any runner attempt starts, so nothing bills — missing_input (no task text or url), missing_url (routing: "learning" / "learning-all" without a resolvable target URL — Learning modes need a domain to learn against), invalid_output_schema / missing_output_schema (normalization requested but unusable), invalid_custody (malformed custody policy), invalid_max_fallbacks (not an integer 0–12). custody_unsatisfiable (409) also fails free: the credential's custody policy excludes every runner in the requested routing — nothing dispatched, nothing billed. Terminal run errors carry machine-readable codes too: run_timeout (hard TTL watchdog) and run_cancelled (POST …/cancel). ## Output normalization (+$0.10) Optional reshape after the run into your JSON schema. `normalize: true` + `output_schema` (JSON Schema / property map), or OpenAI `text.format` / `response_format` with a schema. Build schemas in Console → Normalize (https://bananapeel.com/normalize). Flat +$0.10 on that run (every executed run is billed). ```bash curl -s "$BANANA_PEEL_BASE/api/v1/responses" \ -H "Authorization: Bearer $BANANA_PEEL_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "banana-peel", "input": "Extract the latest order from https://example.com/orders", "routing": "smart", "normalize": true, "output_schema": { "type": "object", "properties": { "order_id": { "type": "string" }, "total": { "type": "string" } }, "required": ["order_id", "total"] } }' ``` The response confirms what happened under `banana_peel.normalization`: `{ "requested": true, "applied": true, "reason": null, "addon_usd": 0.1 }`. `applied: false` carries a short `reason` (e.g. run failed before normalization) and `addon_usd: 0` — the +$0.10 is charged only when the pass actually runs. Absent when not requested. Structured input also accepted: `input: { url, credentials_ref, goal, success_criteria, steps[] }`. Read `confidence` / `banana_peel.confidence` on every response; `banana_peel.ran_in` when routing is SOC2|HIPAA|EU. Dry-run cost: `POST /api/v1/estimate` with the same body (no browser run) — returns `likely_runner` + catalog total including normalization. Docs: https://bananapeel.com/docs/api/responses#normalize ## Credits Runs debit runner listed price + 5% platform fee (0% until your first top-up). Top up in Console → Billing. Normalization add-on is +$0.10 flat when enabled. insufficient_credits → HTTP 402 + hint. ## Do not use - /api/v2/* — mock playground, not product API - POST /api/live/run — console Compare/playground dispatcher only ## Specs & docs - OpenAPI JSON: https://bananapeel.com/openapi.json - OpenAPI YAML: https://bananapeel.com/openapi.yaml - Docs home: https://bananapeel.com/docs - Quickstart: https://bananapeel.com/docs/quickstart - MCP server: https://bananapeel.com/docs/mcp - Agent-native signup: https://bananapeel.com/docs/agents/signup - API overview: https://bananapeel.com/docs/api - Wrappers: https://bananapeel.com/docs/wrappers - Keys console (humans with an existing account, optional): https://bananapeel.com/keys