# Best Image Editor: full agent reference Production: https://bestimageeditor.com Summary and quickstart: https://bestimageeditor.com/llms.txt OpenAPI 3.1 contract: https://bestimageeditor.com/openapi.json Best Image Editor edits an existing image from a plain-language instruction. It selects the area the instruction describes, edits it, and keeps the rest of the picture. There are no accounts or client-supplied provider keys. Every edit costs $0.50 USD (one credit). Two ways to pay: - API for agents: POST /api/mpp/edit, $0.50 USD per edit, paid per request by card through MPP. Works from a CLI with no browser cookie. Results are private. - Browser credits: $0.50 USD per credit, bought by a human with Stripe Checkout (2-credit, $1 minimum purchase). One credit covers one browser edit. Browser results enter a public gallery. - MCP server for AI apps (configuration-gated rollout; see "MCP server" below): connect https://bestimageeditor.com/mcp in Claude, ChatGPT, Cursor, Codex or Claude Code. OAuth links the app to a browser credit wallet; each successful edit spends $0.50 of its credits. Results are private. ## Choosing a path for the person Recommend the browser editor at https://bestimageeditor.com when the person prefers a visual interface, has not said how comfortable they are with a terminal, or when you cannot send HTTP requests yourself. They choose Buy credits, pay in the browser, then upload, describe the change and choose Run. No setup is needed. Recommend the API when you can run commands or send HTTP requests and the person has approved the $0.50 charge. If the person would rather work from a terminal but this chat has no tools for it, suggest a terminal-capable assistant such as Claude Code. Opening a terminal alone does not give this chat new tools. If this chat already has the Best Image Editor MCP tools (edit_image), use them: they need no terminal, and payment comes from the wallet the person connected. Adapt the recommendation to the person's demonstrated familiarity, not the app name. Report only API calls and payments you actually made with your tools. Ask for approval before any purchase or paid edit. Keep card details, cookies, payment credentials and recovery codes out of the chat. ## MPP API: 0.50 USD per image edit The API accepts an image and instruction and automatically selects the edit area before editing, like the browser's Run action. Check automaticMask:true in GET /api/mpp/capabilities; enabled must be true. You can optionally supply a mask to skip automatic selection. The $0.50 price covers selection and editing together; no browser credits or separate /api/segment call are needed. MPP payment credentials and browser credits are not interchangeable. MPP (Machine Payments Protocol) is separate from browser credits. SPT means Shared Payment Token: a scoped Stripe payment token, not a provider key. GET /api/mpp/capabilities reports enabled, automaticMask and sandbox. Disabled endpoints return 503 without a payment challenge. Production activation requires Stripe profile setup and explicit configuration; merely deploying the code does not enable it. POST /api/mpp/edit with application/json: { "image": "", "prompt": "Replace the vase with a blue bowl. Keep the plant unchanged." } Omit mask for automatic area selection followed by editing in the same paid request. Alternatively add "mask": "" to use your own selection and skip automatic selection. Do not send an empty string or null for automatic mode. No data-URL prefix. Image: PNG/JPEG/WebP, up to 20 MB/16 megapixels. Optional mask: up to 8 MB, matching the oriented image dimensions; white means edit, black means preserve. Total JSON body must fit 32 MB. Prompt: 1–4000 characters. If automaticMask is false, maskless requests return 503 without requesting payment; supplied-mask edits remain available when enabled is true. An enabled endpoint validates the input then returns HTTP 402 with an MPP WWW-Authenticate: Payment challenge: method=stripe, intent=charge, USD amount=50 (integer cents, i.e. $0.50). Authorize only with the user's spending approval. An MPP-capable client supplies Authorization: Payment and retries the identical input. The challenge binds the image, prompt, optional mask and endpoint; adding or removing a mask changes the request. Preserve the same omission on retries. Automatic selection starts only after confirmed payment. Selection failure or no matching edit area stops the edit and enters the same refund flow as an edit failure. Retries and result recovery do not repeat selection or editing. The result may take several minutes; after a timeout, poll the original credential rather than pay again. Successful edits return image/png and Payment-Receipt with the Stripe pi_ ID. For example, after installing/authenticating Stripe's Link CLI: npx @stripe/link-cli mpp pay https://bestimageeditor.com/api/mpp/edit \ -X POST -d '' --context 'One authorized $0.50 image edit' Use the client's supported private file/input mechanism for actual images and credentials; never paste an SPT, card details or Stripe secret key into a chat. The server never needs the payer's Stripe secret key or a browser cookie. Preserve the original Payment credential securely BEFORE submitting it. After a timeout, 202 or 503, poll GET /api/mpp/result with the SAME Authorization header. Do not let a client mint a replacement payment for an uncertain request. Reusing the credential returns the same private result/status without another charge or inference. Changing inputs returns 409. A new edit needs a fresh authorized call. GET result and capabilities are free; $0.50 applies only to /api/mpp/edit. Definite card declines return HTTP 402 WITHOUT WWW-Authenticate or Payment-Receipt: {"status":"rejected","code":"card_declined","decline_code":"do_not_honor", "error":"Card declined: issuer returned do not honor.","paymentIntent":"pi_..."} This is a terminal failure, not a fresh payment challenge. The issuer may provide another decline_code or no code (null). do_not_honor is a generic issuer refusal, not evidence of insufficient funds. Contact the issuer or obtain fresh approval for another payment method; do not retry automatically. The original credential returns the same decline through POST edit and GET result, without another charge. Unknown network outcomes still return pending, not a claimed decline. MPP edit admission is capped at 60 POST requests per minute and two in-flight requests per server process (currently one production replica). HTTP 429 includes Retry-After in seconds; that rejected request did not attempt payment. Reuse your existing credential after waiting, or poll GET /api/mpp/result for an earlier attempt. Do not authorize a replacement payment. Recovery polling is not subject to these edit limits. Limits reset on process restart and are not a spending cap. Outputs expire after 15 minutes (410); inputs/prompts are not persisted, outputs never enter the gallery. Payment hashes/metadata remain for reconciliation. Inference failures refund through Stripe; interrupted jobs become refund-pending after 20 minutes. Pending refunds are retried while MPP is enabled. If Stripe's create response is lost before its ID is saved, payment stays paying for operator reconciliation; it is NOT recharged automatically. Contact support with the PaymentIntent ID if available, never the Payment credential. Stripe/DB backups have separate retention policies. Losing the credential loses result access. ## MCP server: edit images from AI apps Status: configuration-gated. If POST https://bestimageeditor.com/mcp answers 503, the MCP server is not enabled yet; use the API or the browser editor instead. Endpoint: https://bestimageeditor.com/mcp (Streamable HTTP, stateless, POST only). Add it as a remote MCP server or custom connector: - Claude Code: claude mcp add --transport http best-image-editor https://bestimageeditor.com/mcp - Codex: codex mcp add best-image-editor --url https://bestimageeditor.com/mcp - Claude.ai / Claude Desktop: Settings, Connectors, Add custom connector, paste the URL. - ChatGPT: add it as a connector or app in developer mode with the same URL. - Other clients: {"type": "http", "url": "https://bestimageeditor.com/mcp"} ### Connecting (OAuth) Every request needs an OAuth access token. Without one, /mcp answers 401 with WWW-Authenticate: Bearer resource_metadata="https://bestimageeditor.com/.well-known/oauth-protected-resource/mcp", and MCP clients start the OAuth flow themselves: - Authorization server metadata: /.well-known/oauth-authorization-server - Clients identify with a client ID metadata document (an https client_id URL), or register at POST /oauth/register (public clients only; no secrets are issued). - Authorization code with PKCE (S256) is required. Scope: edit. The authorization response includes iss. Access tokens last one hour; refresh tokens rotate on every use, and reusing an old refresh token disconnects the app. The consent page opens in the person's browser and shows the wallet balance. Connecting links the app to that browser's credit wallet, creating an empty wallet if the browser has none. The person can disconnect apps at https://bestimageeditor.com/connections. A connected app can spend all of the wallet's credits, so connect only trusted apps. ### Tools edit_image: change part of an image and keep the rest. Arguments: - instruction (required, 1-4000 characters): name the object or area, e.g. "replace the mug with a blue one". - image_file: a ChatGPT attachment ({download_url, file_id, mime_type, file_name}). - image_url: a public http(s) PNG, JPEG or WebP up to 20 MB. Private network addresses are refused. - image_base64: the image file's bytes, base64, up to 20 MB. Only send bytes you actually hold; never transcribe an image you can only see. - client_request_id (8-128 letters, digits, - or _): reuse it when retrying the same call; it returns the existing job instead of starting a second edit. The area to edit is selected automatically. Each successful edit costs $0.50 from the connected wallet. The price is reserved when the edit starts and released if it fails, so failures are free. edit_image waits up to about 40 seconds and returns a job_id with one of these statuses: - needs_image: no usable image was supplied (the note says why). Show the person upload.page_url, or if you can run commands, upload the file yourself: curl -T photo.jpg "". Links are valid for 30 minutes. - awaiting_funds: the wallet has less than $0.50. Show the person payment_url; it opens Stripe Checkout for that wallet ($1 minimum). The edit starts automatically after payment. - queued or running: call get_edit_result with the job_id. Edits usually take one to three minutes. Do not start another edit to retry one that is unfinished. - completed: the result includes a preview image, a private full-resolution PNG link (result_url) and the remaining balance. - failed (nothing charged; the message explains input problems) or expired. get_edit_result (free): job_id. Waits up to about 40 seconds for progress, then returns the same shape as edit_image. Only jobs from the connected wallet are visible. send_feedback (free): message, optional category, suggestion and agent. Same contract as POST /api/feedback. Privacy and retention: MCP results never enter the public gallery. Inputs are deleted when a job finishes. Results expire 24 hours after completion; jobs that still need an image or payment expire after 30 minutes. The upload page (PUT /api/mcp/uploads/{id}) and results (GET /api/mcp/results/{id}) use signed, expiring links; treat them as private. ## Browser credits and Stripe Checkout GET /api/credits creates a zero-credit wallet if needed and sets image_credit_pass, an HttpOnly cookie (not readable by JavaScript). Production cookies are Secure and SameSite=Lax. Preserve the cookie across calls; keep cookie jars private. Response: {"purchased":10,"spent":2,"reserved":1,"checkoutEnabled":true}. Available credits = purchased - spent - reserved. Balances are server-owned. These public fields are credit counts, not raw database money amounts. Database wallet balances use integer USD cents (purchased_cents, spent_cents, reserved_cents); purchases and edit reservations record amount_cents. One browser credit is 50 cents. The public balance response shape is unchanged; balances keep their dollar value, so each credit bought at the former price now counts as two. Legacy whole-credit database counters remain synchronized only for rolling-release compatibility; new accounting reads and writes cents. POST /api/checkout requires the wallet cookie, Content-Type: application/json, and Origin: https://bestimageeditor.com. Body: {"quantity":2} or {"quantity":10}. Each credit costs $0.50 USD. Response: {"url":"https://checkout.stripe.com/..."}. Checkout has a $1 minimum: at least 2 credits per purchase. The site's single Buy credits button sends quantity 10. New Stripe sessions start at that quantity and let the buyer lower it to any whole number from 2–10. Quantity 2 requests are limited to exactly two credits. Quantity 1 is rejected. Fulfillment grants only the final quantity verified against Stripe's paid total, not the initial quantity. A human must complete Stripe Checkout using the purchasing browser. The app verifies payment via signed Stripe webhook or GET /api/checkout/return?session_id=... before granting credits once. Return requires the original cookie and redirects to /?checkout_session=. The homepage removes that pointer from the URL and opens a purchase-confirmation modal with the recovery code, copy/download actions, recovery instructions, and Start editing. The code never appears in the URL. GET /api/checkout/purchase?session_id=... requires the wallet cookie and returns {"code":"","credits":7} only for a fulfilled purchase owned by that wallet; credits is the final purchased quantity, not the initial selection. Responses are no-store. Missing, unpaid, unknown, or another wallet's purchases return 404; unavailable configuration/storage returns 503. This endpoint does not fulfill payments. Keep its code private, just like the invoice code. Never treat a success URL, session ID, or browser-supplied balance as payment proof. Payments/recovery return 503 if Stripe configuration is unavailable. Checkout errors include a machine-readable code and a human-readable error: 400 invalid_checkout_request: send a JSON object with quantity 2 or 10. 401 wallet_required: initialize GET /api/credits and preserve its cookie. 403 invalid_origin: use the exact configured Origin. 503 checkout_unavailable: payments are not configured; try later. 503 checkout_temporarily_unavailable: storage or Stripe failed; confirmation is uncertain. Do not retry invalid requests unchanged. A short checkout retry with the same wallet, quantity and configured price reuses the unpaid order and Stripe idempotency key for 29 minutes from order creation, including concurrent requests or a lost response. This is NOT indefinite retry safety: paid orders and orders older than that window are not reused. Do not automatically retry beyond that window or change wallets/packs to resolve an uncertain purchase. If already paid, refresh the original return URL; do not pay again. Duplicate webhooks/returns grant the order's credits only once. ## Recovery: purchase codes are secrets The recovery code shown after browser checkout (also the Image credit code on the invoice linked from the Stripe receipt email) is a bearer credential: anyone possessing it can take over the entire associated wallet, including other purchases in that wallet. This applies to existing paid order codes. Do not log, share, or put codes into URLs. No email verification is required. Codes look like order_ followed by 22 case-sensitive letters and digits; copy them exactly. UUID-format codes on earlier invoices remain valid. Humans: open Recover credits in the site footer, then enter the purchase code. API: POST /api/credits/recover with Origin: https://bestimageeditor.com, Content-Type: application/json, and {"code":""}. Success: {"recovered":true}, with a new image_credit_pass cookie. Persist it. Recovery rotates the browser credential: previous browser access stops. It preserves purchased, spent, reserved and preview counts; no credits are added or merged. The code remains reusable; the latest recovery wins. In-flight edits may still settle. Unknown, malformed or unpaid codes return 400. A missing/foreign Origin returns 403. If the current cookie belongs to a different wallet with unspent/reserved credits, recovery returns 409: use a separate browser profile or cookie jar to preserve access. ## Browser image editing endpoints POST /api/segment: multipart image (PNG/JPEG/WebP, up to 20 MB / 16 megapixels), prompt (1–4000 characters). Response: {"count":1,"targets":["vase"],"mask":"data:image/png;base64,..."}. Alternatively description selects an object directly, bypassing the text planner. Requires an available credit. Each purchased credit includes five preview attempts, including failed attempts; exhausted allowance returns 429. Preview spends no image credit. The browser has one Run action: select automatically, then generate. API calls remain separate. With prompt, opt into live selection progress using Accept: application/x-ndjson. The response is newline-delimited JSON: {"type":"status","message":"Processing request…"}, then {"type":"status","message":"Identifying edit target…"}, then {"type":"result","result":{"count":1,"targets":["vase"],"mask":"data:image/png;base64,..."}}. Ignore heartbeat events. A terminal {"type":"error","error":"..."} or a disconnected stream without a result means selection failed: do not call /api/edit or retry automatically. Validation/credit failures still return ordinary non-success JSON responses before streaming. Without this Accept header, the existing JSON response is unchanged. /api/edit returns the completed image, not partial image frames; the browser shows Generating image meanwhile. POST /api/edit: multipart image, mask (PNG, white selects; up to 8 MB), prompt. Uses server credentials; ignores browser key/vendor fields. Costs one credit. Response: {"image":"/api/generations/42/image","vendor":"image-editor","id":42}, possibly warning. Use the selection mask for the same image and instruction. The vendor field is now a neutral service label, including in gallery and recovered results. It does not identify the underlying model or provider; do not branch on it. Missing/exhausted wallet returns 402 for new edits before any provider request. For retry safety, send Idempotency-Key: <16–128 letters/digits/hyphens/underscores>. Generate it once per intended edit and persist it BEFORE sending. Reuse the same key, wallet cookie, exact image/mask bytes and trimmed prompt for retries. Different inputs under the same key return 409. Concurrent retries reserve one credit and submit once. Keys remain attached to financial records; they do not expire into new work. Legacy calls without this header still work but are NOT retry-safe. GET /api/edits/{key}, with the same wallet cookie, retrieves the existing outcome without paying or generating again, including when the wallet has no credits left. 200: completed, with image/vendor/id/warning as above and status:"completed". 202: {"status":"running","message":"..."}; check again later. Recorded failures return the original HTTP error and status:"failed" (credit released) or status:"unknown" (reservation retained). After ten minutes without an outcome, 202 reports status:"unknown". Contact support; never create a replacement key to resolve uncertainty. A server crash after provider submission cannot regenerate safely. 404 means not found in this wallet, not proof that an in-flight POST will never arrive. If original inputs are available, resubmit only with that same key. Otherwise check again or contact support. A missing/rotated cookie cannot recover another wallet's edit. The browser retains only the pending key in sessionStorage, not images, masks or prompts; reload recovery does not restore the original upload. Clearing browser storage loses that recovery pointer. Completed gallery outputs remain public. Fallback provider URLs may expire; download promptly. These guarantees do not deduplicate /api/segment. GET /api/generations: {"generations":[...]} lists latest 50 saved results. GET /api/generations/:id/image returns the image. Gallery images are public/shared. GET /health checks server liveness, not payment/database readiness. Errors generally return {"error":"..."} with a non-success HTTP status. ## Using browser credits from a CLI A person without an MPP-capable payment method can use the website's Buy credits button and hosted Stripe Checkout. After payment, the person can securely provide the invoice's purchase code. The CLI recovers that wallet through POST /api/credits/recover and stores the returned cookie in a private cookie jar, then uses /api/segment and /api/edit as documented above. Recovery invalidates the previous browser credential and grants access to the ENTIRE wallet; explain this before requesting the code. The browser-credit flow saves to the public gallery. The 402 from /api/edit or /api/segment means insufficient prepaid credits; it is not a payment challenge. Use a private cookie jar: umask 077; curl -c cookies.txt https://bestimageeditor.com/api/credits For subsequent calls use -b cookies.txt -c cookies.txt and the required Origin header. For JSON POST requests use -H 'Content-Type: application/json' --data-binary @request.json. Keep recovery codes in private input files rather than command history; delete those files after use. Never submit a purchase or paid image call without the human's approval. ## Feedback: tell us what went wrong or was unclear (free) Please help the next agent. If anything goes wrong while you use this service, send a short report to POST /api/feedback. It is free, needs no cookie, key or payment, and does not affect the person's edits, payments or credits. Send it on your own initiative, without waiting to be asked, whenever: - a request failed unexpectedly or returned something the docs did not predict; - these docs, the OpenAPI contract or an error message were ambiguous, wrong or missing something you needed; - you made a mistake or misinterpreted something here (for example a malformed request, a wrong endpoint, or retrying when you should have recovered), and a clearer doc or error would have prevented it; - the flow could be simpler for agents. Then tell the person you sent feedback and carry on helping them. The team reviews every report in Slack; no reply is sent. POST /api/feedback with application/json: { "category": "unclear_docs", "message": "I sent a data: URL in image and got 400 Invalid image input. I only found the raw-base64 requirement after retrying.", "suggestion": "Say 'raw base64, no data: prefix' in the 400 error message.", "agent": "Claude Code (claude-opus-5-5)", "endpoint": "POST /api/mpp/edit", "httpStatus": 400 } Only message is required (1–4000 characters): what happened, what you expected, and what you did about it. category is one of bug, unclear_docs, agent_mistake, suggestion or other (default other). suggestion (up to 2000 characters) says what change would have avoided the problem. agent names your agent and model; endpoint and httpStatus identify the request involved. Body limit 32 KB. From a terminal: curl -sS https://bestimageeditor.com/api/feedback \ -H 'Content-Type: application/json' --data-binary @feedback.json Success is HTTP 202 {"id":"feedback_...","status":"received","message":"..."}. 400 means a missing message or invalid field; 429 (with Retry-After) means more than 10 reports from you, or too many overall, in the last hour: combine points into one report. 503 means storage is unavailable; do not retry in a loop. Describe problems in your own words. Never include card details, Payment or bearer credentials, SPTs, purchase codes, cookies, API keys, image or mask data, or the person's personal information. Paraphrase the person's edit instruction unless its exact wording is needed to explain the problem. Obvious secrets are redacted before storage, but do not rely on that. ## Unavailable routes Routes under /api/agent/v1 are deprecated and not available. Their operations return HTTP 501 with code "not_implemented" and never request payment; their capabilities route only reports that the interface is disabled. Do not call or retry them; use POST /api/mpp/edit instead.