{"openapi":"3.1.0","info":{"title":"Best Image Editor API","version":"1.0.0","description":"Edit an existing image from a text instruction; only the described region is meant to change. Agents send an image and instruction to POST /api/mpp/edit and pay 0.50 USD per edit by card through MPP (Machine Payments Protocol); the service selects the area to edit automatically and returns the edited PNG. Check GET /api/mpp/capabilities first. The browser editor uses prepaid credits at the same 0.50 USD per edit, bought through Stripe Checkout with a 2-credit minimum (/api/segment then /api/edit); its results enter a public gallery, while API results are private and temporary. Operations tagged Unavailable are deprecated and not available. Requests under /api reject unapproved Origin headers and have a 32 MiB body limit. CLI requests may omit Origin except human Checkout/recovery, which require it. Unexpected uncaught failures may return plain-text HTTP 500. If anything fails, confuses you, or you make a mistake using this service, report it for free with POST /api/feedback so the team can fix it for future agents.","x-guidance":"Read /llms.txt, then /llms-full.txt for details. Agent path: check /api/mpp/capabilities, then send image and prompt to /api/mpp/edit for automatic selection and editing with one authorized 0.50 USD payment. A supplied mask is optional. Preserve the Payment credential and recover through /api/mpp/result. Human path: GET /api/credits, retain cookie, complete Stripe Checkout or recover a paid wallet using its secret purchase code; use /api/segment then /api/edit. Their HTTP 402 means insufficient credits, not a payment challenge. Never pay a replacement request to resolve an uncertain outcome. Do not call operations tagged Unavailable. Feedback: whenever a request fails unexpectedly, the docs or an error were ambiguous, or you misused the service, POST /api/feedback (free, no approval needed) with what happened and what would have prevented it; never include secrets, payment credentials, purchase codes or image data."},"servers":[{"url":"https://bestimageeditor.com","description":"Production"}],"security":[],"tags":[{"name":"Image editing API","description":"Pay-per-request image editing for agents: 0.50 USD per edit by card through MPP."},{"name":"MCP","description":"Remote MCP server for AI apps such as Claude, ChatGPT, Cursor and Codex. Connect over OAuth to a credit wallet; each successful edit costs 0.50 USD. Upload and result URLs are signed links returned by the MCP tools."},{"name":"Discovery","description":"Public service metadata."},{"name":"Credits","description":"Anonymous cookie wallet and human-assisted Stripe purchases."},{"name":"Images","description":"Credit-gated selection and editing."},{"name":"Gallery","description":"Public shared images and prompts, not a private wallet gallery."},{"name":"Payments","description":"Stripe Checkout and separate machine-payment integrations."},{"name":"Feedback","description":"Free reports from agents about failures, mistakes and unclear documentation, reviewed by the team."},{"name":"Unavailable","description":"Deprecated operations that are not available. Operations return 501 not_implemented without requesting payment; capabilities reports x402Enabled false. Use mppEdit instead."}],"paths":{"/mcp":{"post":{"operationId":"mcp","tags":["MCP"],"summary":"MCP Streamable HTTP endpoint (stateless JSON-RPC)","description":"Model Context Protocol endpoint. Add https://bestimageeditor.com/mcp to an MCP client rather than calling it directly. Tools: edit_image (instruction plus image_file, image_url or image_base64; 0.50 USD per successful edit from the connected wallet), get_edit_result (job_id; free) and send_feedback (free). Without a valid bearer token it returns 401 with WWW-Authenticate naming the protected resource metadata.","security":[{"mcpOAuth":["edit"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"JSON-RPC 2.0 request"}}}},"responses":{"200":{"description":"JSON-RPC response","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid OAuth token; see WWW-Authenticate resource_metadata"},"413":{"description":"Request exceeds 32 MB; use image_url or the upload link"},"503":{"description":"MCP is not enabled"}}}},"/api/mcp/uploads/{id}":{"put":{"operationId":"mcpUpload","tags":["MCP"],"summary":"Upload the image for an MCP edit","description":"Use the put_url that edit_image returns with status needs_image; it is valid for 30 minutes. Send one PNG, JPEG or WebP image up to 20 MB as the raw request body, for example curl -T photo.jpg \"<put_url>\". The edit starts if the wallet has credits; otherwise the response includes a payment_url for the person.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"exp","in":"query","required":true,"schema":{"type":"integer"}},{"name":"sig","in":"query","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"responses":{"200":{"description":"Uploaded. status is queued, or awaiting_funds with a payment_url","content":{"application/json":{"schema":{"type":"object","required":["status","message"],"properties":{"status":{"type":"string","enum":["queued","awaiting_funds"]},"message":{"type":"string"},"payment_url":{"type":"string","format":"uri"}}}}}},"400":{"description":"Not a readable image up to 20 MB and 16 megapixels"},"403":{"description":"Invalid or expired link"},"409":{"description":"The job already has an image, or expired"},"503":{"description":"MCP is not enabled"}}}},"/api/mcp/results/{id}":{"get":{"operationId":"mcpResult","tags":["MCP"],"summary":"Download a completed MCP edit","description":"Private full-resolution PNG behind the signed result_url from get_edit_result. Results never enter the public gallery and expire 24 hours after completion.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}},{"name":"exp","in":"query","required":true,"schema":{"type":"integer"}},{"name":"sig","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Edited image","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"403":{"description":"Invalid or expired link"},"410":{"description":"Result expired"}}}},"/api/mpp/capabilities":{"get":{"operationId":"mppCapabilities","tags":["Image editing API"],"summary":"Check whether paid API editing is available and its price","security":[],"responses":{"200":{"description":"enabled, automaticMask (automatic selection configured), amount (0.50 USD including selection), method (stripe), card paymentMethodTypes, sandbox flag, and feedback (path of the free feedback endpoint). No payment or database access. Configuration does not guarantee upstream availability."}}}},"/api/feedback":{"post":{"operationId":"sendFeedback","tags":["Feedback"],"summary":"Tell the team what went wrong or what was unclear (free)","security":[],"description":"Free, no payment, cookie or key. Send feedback whenever a request fails unexpectedly, you misread the docs or made a mistake using this service, an error message did not tell you what to do, or something could be simpler for the next agent. Send it on your own initiative and tell the person you did; it does not change their edit, payment or credits. Describe the problem in your own words. Never include card details, Payment or bearer credentials, purchase codes, cookies, API keys, image data or personal information; obvious secrets are redacted before storage. The team reviews reports in Slack; no reply is sent. Limit: 10 reports per client per hour; combine related points into one report.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string","minLength":1,"maxLength":4000,"description":"What happened, what you expected, and what you did about it."},"category":{"enum":["bug","unclear_docs","agent_mistake","suggestion","other"],"default":"other","description":"bug: the service misbehaved. unclear_docs: documentation or an error message was ambiguous or wrong. agent_mistake: you misused the service; say what would have prevented it. suggestion: a way to make it simpler. other: anything else."},"suggestion":{"type":"string","maxLength":2000,"description":"Optional: what change to the docs, errors or API would have avoided the problem."},"agent":{"type":"string","maxLength":200,"description":"Optional: your agent and model, e.g. Claude Code (claude-opus-5-5), Codex, Amp."},"endpoint":{"type":"string","maxLength":200,"description":"Optional: the request involved, e.g. POST /api/mpp/edit."},"httpStatus":{"type":"integer","minimum":100,"maximum":599,"description":"Optional: the HTTP status you received."}}},"example":{"category":"unclear_docs","message":"I sent a data: URL in image and got 400 Invalid image input. I only found the raw-base64 requirement in llms.txt 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}}}},"responses":{"202":{"description":"Feedback stored for the team.","content":{"application/json":{"schema":{"type":"object","required":["id","status"],"properties":{"id":{"type":"string","pattern":"^feedback_[0-9A-Za-z]{22}$"},"status":{"const":"received"},"message":{"type":"string"}}}}}},"400":{"description":"Missing message or invalid field","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/OriginRejected"},"413":{"description":"Body exceeds 32 KB. Summarize; leave out images and logs."},"429":{"description":"More than 10 reports per client, or the service-wide cap, in the last hour. Combine points into one report later.","headers":{"Retry-After":{"description":"Seconds to wait","schema":{"type":"string"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/mpp/result":{"get":{"operationId":"mppResult","tags":["Image editing API"],"summary":"Get a paid edit's result again without paying twice","description":"Send the exact original Authorization: Payment credential. Never charges or starts inference. Treat that credential as a secret. Results expire after 15 minutes.","parameters":[{"name":"Authorization","in":"header","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Private image result and Payment-Receipt","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"202":{"description":"Payment/inference/refund pending. Do not pay again."},"401":{"description":"Missing/malformed credential"},"402":{"description":"Stored card decline: status rejected, code card_declined, decline_code (e.g. do_not_honor, or null), error and paymentIntent. No WWW-Authenticate or Payment-Receipt. Do not retry payment automatically."},"404":{"description":"Unknown payment"},"409":{"description":"Rejected payment"},"410":{"description":"Result expired"},"502":{"description":"Edit failed and payment refunded"},"503":{"description":"Disabled or unavailable"}}}},"/api/mpp/edit":{"post":{"operationId":"mppEdit","tags":["Image editing API"],"summary":"Edit an image from a text instruction for 0.50 USD","description":"Send a base64 image and an instruction. The service selects the area the instruction describes, edits it, and returns the edited PNG, all under one 0.50 USD payment. The first request returns 402 with an MPP Stripe card challenge; retry the identical request with Authorization: Payment <credential>. Optionally supply a matching mask (white=edit, black=preserve) to skip automatic selection; check automaticMask in capabilities. The challenge binds exact inputs, including whether mask is omitted. Selection starts only after confirmed payment and a durable execution claim. Selection failure or no matching area stops editing and triggers a refund; retries cannot repeat either step. Retry only with the same Payment credential and inputs; poll /api/mpp/result after a timeout. Results are private and never saved to the public gallery. Separate from browser credits.","x-payment-info":{"method":"stripe","intent":"charge","amount":"50","currency":"usd"},"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["image","prompt"],"properties":{"image":{"type":"string","contentEncoding":"base64","description":"PNG/JPEG/WebP, decoded size up to 20 MB, 16 megapixels"},"mask":{"type":"string","minLength":1,"contentEncoding":"base64","description":"Optional. Omit for automatic selection; null and empty strings are invalid. Up to 8 MB; matching oriented image dimensions, white selected pixels."},"prompt":{"type":"string","minLength":1,"maxLength":4000}}},"example":{"image":"iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAIAAAASFvFNAAAACXBIWXMAAAPoAAAD6AG1e1JrAAAAHElEQVR4nGOQk5OzsbGJiopiqKiomDZt2pYtWwA13gdjaRhyPAAAAABJRU5ErkJggg==","prompt":"Make the center region yellow"}}}},"responses":{"200":{"description":"Private PNG with Payment-Receipt","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"202":{"description":"Already admitted; poll /api/mpp/result without paying again"},"400":{"description":"Invalid input, no charge"},"402":{"description":"MPP challenge when WWW-Authenticate is present. Otherwise a terminal card decline: status rejected, code card_declined, decline_code (e.g. do_not_honor, or null), error and paymentIntent. No receipt on decline. Contact issuer or obtain fresh spending approval; never retry automatically."},"409":{"description":"Payment/input conflict or rejected payment"},"410":{"description":"Result expired"},"413":{"description":"Body exceeds 32 MB"},"429":{"description":"60 POSTs/minute or two in-flight edits per process exceeded. No payment attempted by this request; preserve the credential and use free GET result recovery.","headers":{"Retry-After":{"description":"Seconds to wait before retrying","schema":{"type":"string"}}}},"502":{"description":"Selection or editing failed, refunded"},"503":{"description":"MPP disabled, automatic selection unconfigured, or service unavailable. Preserve any existing Payment credential and poll result; do not pay again."}}}},"/api/agent/v1/capabilities":{"get":{"operationId":"agentCapabilities","tags":["Unavailable"],"deprecated":true,"summary":"Unavailable: reports that the deprecated x402 interface is disabled","security":[],"responses":{"200":{"description":"x402Enabled, protocol/version, network, previewCents/editCents when configured, authentication and documentation. No database/payment side effects; enabled means configured, not upstream readiness.","content":{"application/json":{"schema":{"type":"object","required":["x402Enabled"],"properties":{"x402Enabled":{"type":"boolean"},"previewCents":{"type":"integer"},"editCents":{"type":"integer"},"network":{"type":"string"}}}}}}}}},"/api/agent/v1/operations":{"post":{"operationId":"createAgentOperation","tags":["Unavailable"],"deprecated":true,"summary":"Unavailable (returns 501): freeze private preview or child-edit inputs without charging","security":[{"agentBearer":[]}],"description":"Requires enabled x402. Preview accepts image/prompt; edit accepts only previewId from the same bearer. Same idempotency key and inputs returns the same operation; changed inputs return 409. No inference or payment occurs at creation. At most 20 live working inputs and 100 creations per ten minutes; unavailable capacity returns 503.","parameters":[{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]{16,128}$"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["kind","image","prompt"],"properties":{"kind":{"const":"preview"},"image":{"type":"string","format":"binary","description":"PNG/JPEG/WebP up to 20 MB and 16 megapixels"},"prompt":{"type":"string","minLength":1,"maxLength":4000}}},{"type":"object","additionalProperties":false,"required":["kind","previewId"],"properties":{"kind":{"const":"edit"},"previewId":{"type":"string","pattern":"^op_[0-9A-Za-z]{22}$"}}}]}}}},"responses":{"200":{"description":"Existing child operation for this key."},"201":{"description":"Operation: id, kind, status, price {cents,currency,network}, expiresAt, statusUrl, executeUrl; resultUrl only when completed.","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/InvalidInput"},"401":{"description":"Invalid/missing bearer."},"403":{"$ref":"#/components/responses/OriginRejected"},"404":{"description":"Preview not found or belongs to another bearer."},"409":{"description":"Different inputs for key or preview incomplete."},"410":{"description":"Expired operation/preview."},"413":{"$ref":"#/components/responses/TooLarge"},"501":{"description":"x402 is not available at launch. JSON code: not_implemented. No payment requested. Use MPP with image and prompt for automatic selection and editing, or human Checkout. MPP has no separate paid preview/child-edit equivalent."},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/agent/v1/operations/{id}/execute":{"post":{"operationId":"executeAgentOperation","tags":["Unavailable"],"deprecated":true,"summary":"Unavailable (returns 501): pay one immutable operation through x402 v2","security":[{"agentBearer":[]}],"description":"EMPTY body. Initial call provisions a unique Stripe deposit address and returns standard 402 PAYMENT-REQUIRED. Retry same URL with PAYMENT-SIGNATURE. Accepts USDC exact/EIP-3009 only; signatures must expire within five minutes. Persisted payment prevents a second charge; poll status on 202, network failure or 503. No inference until chain confirmation and Stripe succeeded. Not a browser-credit endpoint.","parameters":[{"$ref":"#/components/parameters/AgentId"},{"name":"PAYMENT-SIGNATURE","in":"header","schema":{"type":"string","maxLength":16384},"description":"Base64-encoded x402 v2 PaymentPayload from a standard EVM client."}],"responses":{"200":{"description":"Already completed; no additional charge. PAYMENT-RESPONSE carries the confirmed settlement receipt."},"202":{"description":"Payment accepted, pending, or failed/refunding job. Poll statusUrl; never pay a replacement to resolve uncertainty."},"400":{"$ref":"#/components/responses/InvalidInput"},"401":{"description":"Invalid/missing bearer."},"402":{"description":"Standard x402 v2 challenge; NOT a prepaid-credit error.","headers":{"PAYMENT-REQUIRED":{"schema":{"type":"string"},"description":"Base64 JSON x402Version=2, accepts (one exact USDC requirement), resource."}},"content":{"application/json":{"schema":{"type":"object","required":["x402Version","accepts","resource"]}}}},"403":{"$ref":"#/components/responses/OriginRejected"},"404":{"description":"Operation not found."},"409":{"description":"Payment already used, quote expired in race, or configured network changed."},"410":{"description":"Operation expired."},"413":{"$ref":"#/components/responses/TooLarge"},"501":{"description":"x402 is unavailable. JSON code: not_implemented. No payment requested; do not retry or send USDC."},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/agent/v1/operations/{id}":{"get":{"operationId":"getAgentOperation","tags":["Unavailable"],"deprecated":true,"summary":"Unavailable (returns 501): poll private job/payment/refund status without charging","security":[{"agentBearer":[]}],"parameters":[{"$ref":"#/components/parameters/AgentId"}],"responses":{"200":{"description":"Operation summary, paymentStatus, transaction, paymentIntent, refund. PAYMENT-RESPONSE present after confirmed settlement. Payment states: unpaid, settling, settled, recorded, refund_pending, refunded, not_settled. Job states: awaiting_payment, paid, running, completed, failed, expired.","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Invalid/missing bearer."},"403":{"$ref":"#/components/responses/OriginRejected"},"404":{"description":"Operation not found."},"410":{"description":"Expired/deleted result or unpaid operation."},"501":{"description":"x402 is unavailable. JSON code: not_implemented. MPP recovery uses /api/mpp/result and its own Payment credential."},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/agent/v1/operations/{id}/result":{"get":{"operationId":"getAgentResult","tags":["Unavailable"],"deprecated":true,"summary":"Unavailable (returns 501): download private PNG mask or edit result","security":[{"agentBearer":[]}],"parameters":[{"$ref":"#/components/parameters/AgentId"}],"description":"No-store response; expires 15 minutes after completion or on acknowledgment. Never added to the public gallery. HEAD returns headers without a body.","responses":{"200":{"description":"PNG mask for preview, PNG output for edit.","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Invalid/missing bearer."},"403":{"$ref":"#/components/responses/OriginRejected"},"404":{"description":"Operation not found."},"409":{"description":"Result not ready."},"410":{"description":"Result expired. Never regenerate on download."},"501":{"description":"x402 is unavailable. JSON code: not_implemented. MPP recovery uses /api/mpp/result and its own Payment credential."},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/agent/v1/operations/{id}/acknowledge":{"post":{"operationId":"acknowledgeAgentResult","tags":["Unavailable"],"deprecated":true,"summary":"Unavailable (returns 501): delete completed working data after download","security":[{"agentBearer":[]}],"parameters":[{"$ref":"#/components/parameters/AgentId"}],"description":"Create a child edit before acknowledging its preview. Existing children retain independent input copies. Keeps financial metadata and permanently reserved recipient bindings.","responses":{"200":{"description":"Deleted, including repeated acknowledgment.","content":{"application/json":{"schema":{"type":"object","properties":{"deleted":{"const":true}}}}}},"401":{"description":"Invalid/missing bearer."},"403":{"$ref":"#/components/responses/OriginRejected"},"404":{"description":"Operation not found."},"409":{"description":"Operation has not completed."},"413":{"$ref":"#/components/responses/TooLarge"},"501":{"description":"x402 is unavailable. JSON code: not_implemented. MPP has no acknowledgment equivalent; its results expire automatically."},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/openapi.json":{"get":{"operationId":"getOpenApi","tags":["Discovery"],"summary":"Read this API contract","security":[],"responses":{"200":{"description":"OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/health":{"get":{"operationId":"getHealth","tags":["Discovery"],"summary":"Check process liveness","security":[],"description":"Does not check the database, payment configuration, or upstream providers.","responses":{"200":{"description":"Process is responding.","content":{"application/json":{"schema":{"type":"object","required":["status"],"properties":{"status":{"const":"ok"}}},"example":{"status":"ok"}}}}}}},"/api/credits":{"get":{"operationId":"getCredits","tags":["Credits"],"summary":"Read or initialize an anonymous credit wallet","security":[{},{"creditWallet":[]}],"description":"An absent or invalid cookie creates a new empty wallet. Retain Set-Cookie for future requests. Available credits = purchased - spent - reserved. Each credit includes five preview attempts; remaining attempts are not exposed here. Paid purchase codes are secret bearer credentials accepted by POST /api/credits/recover to restore access.","responses":{"200":{"description":"Wallet balance. Responses are not cached.","headers":{"Set-Cookie":{"description":"When initializing a wallet: image_credit_pass; HttpOnly; SameSite=Lax; Path=/; Secure in production; one-year lifetime.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credits"},"example":{"purchased":10,"spent":3,"reserved":1,"checkoutEnabled":true}}}},"403":{"$ref":"#/components/responses/OriginRejected"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/credits/recover":{"post":{"operationId":"recoverCredits","tags":["Credits"],"summary":"Recover a wallet using a secret paid purchase code","security":[],"description":"Anyone holding a fulfilled purchase code can take over its entire wallet. No email verification. Codes remain reusable: each recovery rotates the cookie and disconnects previous browsers. Balances and reservations are preserved, never added or merged. Requires configured Stripe payments. An optional current wallet cookie is checked to prevent replacing another wallet with unspent credits. Keep codes out of URLs and logs.","parameters":[{"in":"header","name":"Origin","required":true,"schema":{"type":"string","const":"https://bestimageeditor.com"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"Secret purchase code from the paid invoice: order_ followed by 22 case-sensitive letters and digits. UUID codes from earlier invoices remain valid. Surrounding whitespace is trimmed."}}}}}},"responses":{"200":{"description":"Existing wallet recovered. Save the new cookie; the previous token is invalid.","headers":{"Set-Cookie":{"description":"image_credit_pass; HttpOnly; Secure in production; SameSite=Lax; Path=/; one-year lifetime.","schema":{"type":"string"}}},"content":{"application/json":{"schema":{"type":"object","required":["recovered"],"properties":{"recovered":{"const":true}}}}}},"400":{"description":"Malformed, unknown, or unpaid purchase code."},"403":{"$ref":"#/components/responses/OriginRejected"},"409":{"description":"Current cookie belongs to another wallet with unspent or reserved credits. Use a separate browser profile or cookie jar."},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/checkout":{"post":{"operationId":"createCheckout","tags":["Credits"],"summary":"Create a hosted Stripe Checkout link","security":[{"creditWallet":[]}],"description":"Requires configured Stripe payments and the exact configured checkout Origin. A human must open the returned URL and pay. One credit costs $0.50, the same as one API edit. Checkout has a $1 minimum (2 credits). The site starts checkout at 10 credits; the buyer can adjust to 2–10 in Stripe. Explicit two-credit requests are also supported; quantity 1 is rejected. Fulfillment verifies and grants the final paid quantity. Repeated calls with the same wallet, price and initial quantity reuse an unpaid order for 29 minutes from creation. Bound Stripe sessions are retrieved without altering their original controls; session creation retries use the same idempotency key. After payment or that window, or with a different wallet, quantity or configured price, a call can create a new order. A checkout URL does not prove payment. If payment confirmation is uncertain, refresh the original return URL; do not pay again. Malformed JSON, non-object bodies and invalid quantities return 400 with code invalid_checkout_request. Other checkout codes: wallet_required (401), invalid_origin (403), checkout_unavailable (503, payments not configured), checkout_temporarily_unavailable (503, storage or Stripe failure). The error field is human-readable. Retry temporary failures shortly with the same wallet and quantity; correct 400/401/403 requests before resubmitting.","parameters":[{"in":"header","name":"Origin","required":true,"schema":{"type":"string","format":"uri"},"example":"https://bestimageeditor.com","description":"Must equal the configured CHECKOUT_ORIGIN."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quantity"],"properties":{"quantity":{"type":"integer","enum":[2,10],"description":"Initial and maximum quantity. The Buy credits button sends 10; new Stripe sessions allow any whole quantity from 2 up to this value at $0.50 each ($1 minimum). Fulfillment grants the final paid quantity, not this initial value."}}},"example":{"quantity":10}}}},"responses":{"200":{"description":"Hosted checkout URL.","content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","description":"HTTPS URL on checkout.stripe.com."}}}}}},"400":{"$ref":"#/components/responses/InvalidInput"},"401":{"description":"No valid wallet; initialize it with GET /api/credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"$ref":"#/components/responses/OriginRejected"},"413":{"$ref":"#/components/responses/TooLarge"},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/checkout/purchase":{"get":{"operationId":"getCheckoutPurchase","tags":["Credits"],"summary":"Read a paid purchase's private recovery code","security":[{"creditWallet":[]}],"description":"Configuration-gated browser confirmation endpoint. Returns only a fulfilled purchase owned by the current wallet cookie. A session ID alone grants no access. Does not fulfill payments. The code is a bearer secret for the entire wallet; never log it or put it in URLs. Responses use Cache-Control: no-store.","parameters":[{"name":"session_id","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Owned paid purchase; credits is the final quantity purchased.","content":{"application/json":{"schema":{"type":"object","required":["code","credits"],"properties":{"code":{"type":"string","description":"Private recovery code, also on the invoice."},"credits":{"type":"integer","minimum":1}}}}}},"403":{"$ref":"#/components/responses/OriginRejected"},"404":{"description":"Missing wallet, unknown/unpaid purchase, or purchase belongs to another wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"$ref":"#/components/responses/Unavailable"}}}},"/api/checkout/return":{"get":{"operationId":"confirmCheckoutReturn","tags":["Credits"],"summary":"Confirm a Stripe purchase and return to the app","security":[{"creditWallet":[]}],"description":"Browser return route. Requires the original purchasing wallet cookie. Re-fetches and verifies payment with Stripe before crediting the wallet; repeated fulfillment does not add credits twice. A session ID alone grants no wallet access.","parameters":[{"name":"session_id","in":"query","required":true,"schema":{"type":"string"},"description":"Stripe Checkout session ID supplied by the checkout redirect."}],"responses":{"202":{"description":"Payment not yet confirmed; refresh later without paying again.","content":{"text/plain":{"schema":{"type":"string"}}}},"303":{"description":"Payment confirmed; redirect to /?checkout_session=<session ID>. The homepage removes the pointer from the URL and fetches the wallet-authorized recovery code to show the confirmation modal.","headers":{"Location":{"schema":{"type":"string","format":"uri"}}}},"401":{"description":"Original wallet cookie is missing or invalid.","content":{"text/plain":{"schema":{"type":"string"}}}},"403":{"$ref":"#/components/responses/OriginRejected"},"503":{"description":"Checkout unavailable or purchase cannot yet be confirmed. Do not pay again.","content":{"text/plain":{"schema":{"type":"string"}}}}}}},"/api/segment":{"post":{"operationId":"previewSelection","tags":["Images"],"summary":"Select existing objects and return a mask","security":[{"creditWallet":[]}],"description":"Requires at least one available credit. Send prompt for automatic target planning, or description to directly name an existing object. If both are present, prompt takes precedence (even if empty). Each accepted attempt consumes one of five preview attempts per purchased credit, including provider failures. Does not spend an image credit. White mask pixels mean edit; black means preserve. Decode the returned PNG data URL into a file before calling /api/edit. Provider calls may be slow; there are no automatic retries.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image"],"anyOf":[{"required":["prompt"]},{"required":["description"]}],"properties":{"image":{"$ref":"#/components/schemas/ImageUpload"},"prompt":{"type":"string","minLength":1,"maxLength":4000,"pattern":"\\S","description":"Full edit instruction; the planner extracts up to five existing targets.","example":"Replace the vase with a blue bowl"},"description":{"type":"string","minLength":1,"maxLength":1000,"pattern":"\\S","description":"Existing object to select when prompt is omitted.","example":"vase"}}}}}},"responses":{"200":{"description":"Selection mask; targets is included only for prompt-based planning.","content":{"application/json":{"schema":{"type":"object","required":["mask","count"],"properties":{"mask":{"type":"string","pattern":"^data:image/png;base64,","description":"Base64 PNG data URL with the source image dimensions."},"count":{"type":"integer","minimum":0,"description":"Number of nonempty instance masks combined, not number of requested targets. Direct description mode can return zero with an all-black mask; prompt mode rejects missing targets."},"targets":{"type":"array","maxItems":5,"items":{"type":"string"}}}}}}},"400":{"$ref":"#/components/responses/InvalidInput"},"402":{"$ref":"#/components/responses/CreditsRequired"},"403":{"$ref":"#/components/responses/OriginRejected"},"413":{"$ref":"#/components/responses/TooLarge"},"429":{"description":"Preview attempt allowance exhausted; run an existing selection or purchase credits.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"$ref":"#/components/responses/ProviderFailure"},"503":{"$ref":"#/components/responses/Unavailable"},"504":{"$ref":"#/components/responses/ProviderTimeout"}}}},"/api/edits/{key}":{"get":{"operationId":"recoverBrowserEdit","tags":["Images"],"summary":"Recover a browser edit without submitting or charging again","security":[{"creditWallet":[]}],"description":"Uses the same wallet and Idempotency-Key as POST /api/edit. Works with zero available credits. Returns the recorded result or failure, or 202 while pending. After ten minutes without a recorded outcome, status is unknown; do not create a replacement. A 404 is not proof that an in-flight POST will never arrive. No provider calls occur here.","parameters":[{"name":"key","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]{16,128}$"}}],"responses":{"200":{"$ref":"#/paths/~1api~1edit/post/responses/200"},"202":{"$ref":"#/components/responses/EditPending"},"400":{"$ref":"#/components/responses/InvalidInput"},"403":{"$ref":"#/components/responses/OriginRejected"},"404":{"description":"No request found in this wallet.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"502":{"$ref":"#/components/responses/ProviderFailure"},"503":{"$ref":"#/components/responses/Unavailable"},"504":{"$ref":"#/components/responses/ProviderTimeout"}}}},"/api/edit":{"post":{"operationId":"editImage","tags":["Images"],"summary":"Edit an image using an approved mask","security":[{"creditWallet":[]}],"description":"Reserves one credit and performs one image edit using server credentials. No segmentation runs here. Send Idempotency-Key for wallet-scoped deduplication: retries with identical image/mask bytes and trimmed prompt return the original outcome; changed inputs return 409. Keyed requests return status completed, failed or unknown; 202 means running or uncertain. Request keys do not expire into new work. Recover using GET /api/edits/{key}, even after spending the last credit. Unknown outcomes retain the reservation and never resubmit inference. Legacy callers without a key are NOT retry-safe. Client vendor/key fields are ignored. Successful edits cost one credit even if gallery saving fails; a fallback provider URL may expire. Definite provider failures release the reservation. Masks guide the edit, but unselected pixels are not guaranteed identical. Saved images AND prompts remain publicly visible in the shared gallery.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Required for retry safety. Generate once per intended edit and retain across retries. Optional only for legacy compatibility.","schema":{"type":"string","pattern":"^[a-zA-Z0-9_-]{16,128}$"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["image","mask","prompt"],"properties":{"image":{"$ref":"#/components/schemas/ImageUpload"},"mask":{"type":"string","format":"binary","description":"PNG mask file, at most 8 MiB. Must match the auto-oriented source dimensions. Grayscale values >=128 select pixels; lower values preserve them. Transparent pixels flatten to black. At least one selected pixel required."},"prompt":{"type":"string","minLength":1,"maxLength":4000,"pattern":"\\S","example":"Replace the vase with a blue bowl"}}}}}},"responses":{"200":{"description":"Edit completed. A warning is still a successful, chargeable edit; download the image rather than rerunning.","content":{"application/json":{"schema":{"type":"object","required":["image","vendor"],"properties":{"status":{"const":"completed","description":"Included for keyed requests."},"image":{"type":"string","format":"uri-reference","description":"Usually a relative /api/generations/{id}/image URL. If persistence fails, a provider URL or PNG data URL."},"vendor":{"const":"image-editor","description":"Service label, not an underlying model or provider identifier."},"id":{"type":"integer","minimum":1,"description":"Present only when saved."},"warning":{"type":"string","description":"Persistence or credit reconciliation warning. Do not rerun."}}},"example":{"image":"/api/generations/42/image","vendor":"image-editor","id":42}}}},"202":{"$ref":"#/components/responses/EditPending"},"400":{"$ref":"#/components/responses/InvalidInput"},"402":{"$ref":"#/components/responses/CreditsRequired"},"403":{"$ref":"#/components/responses/OriginRejected"},"409":{"description":"Key already belongs to different inputs.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"$ref":"#/components/responses/TooLarge"},"502":{"$ref":"#/components/responses/ProviderFailure"},"503":{"$ref":"#/components/responses/Unavailable"},"504":{"$ref":"#/components/responses/ProviderTimeout"}}}},"/api/generations":{"get":{"operationId":"listGenerations","tags":["Gallery"],"summary":"List the latest 50 public generations","security":[],"description":"Shared across all visitors, newest ID first. No pagination or wallet filtering. Includes prompts and a neutral service label.","responses":{"200":{"description":"Public gallery metadata.","content":{"application/json":{"schema":{"type":"object","required":["generations"],"properties":{"generations":{"type":"array","maxItems":50,"items":{"type":"object","required":["id","created_at","vendor","prompt"],"properties":{"id":{"type":"integer","minimum":1},"created_at":{"type":"string","format":"date-time"},"vendor":{"type":"string","const":"image-editor","description":"Service label, including for historical results."},"prompt":{"type":"string"}}}}}}}}},"403":{"$ref":"#/components/responses/OriginRejected"},"500":{"$ref":"#/components/responses/InternalFailure"}}}},"/api/generations/{id}/image":{"get":{"operationId":"getGenerationImage","tags":["Gallery"],"summary":"Download a public saved PNG","security":[],"parameters":[{"in":"path","name":"id","required":true,"schema":{"type":"integer","minimum":1},"description":"Generation ID from the gallery or edit response."}],"responses":{"200":{"description":"PNG bytes.","content":{"image/png":{"schema":{"type":"string","format":"binary"}}}},"403":{"$ref":"#/components/responses/OriginRejected"},"404":{"description":"Unknown or invalid generation ID.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"$ref":"#/components/responses/InternalFailure"}}}},"/api/stripe/webhook":{"post":{"operationId":"receiveStripeEvent","tags":["Payments"],"summary":"Receive signed Stripe payment events","security":[{"stripeSignature":[]}],"description":"Stripe-only integration. Verifies the signature on the original body with a five-minute timestamp tolerance. Handles checkout.session.completed and checkout.session.async_payment_succeeded, re-fetches the session, and fulfills paid orders once. Other valid signed events are acknowledged and ignored. Not a credit-purchase endpoint for agents.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["type","data"],"properties":{"type":{"type":"string"},"data":{"type":"object","required":["object"],"properties":{"object":{"type":"object","description":"Stripe Checkout session with id for handled event types."}}}}}}}},"responses":{"200":{"description":"Event acknowledged; not proof that an arbitrary session is paid.","content":{"application/json":{"schema":{"type":"object","required":["received"],"properties":{"received":{"const":true}}}}}},"400":{"$ref":"#/components/responses/InvalidInput"},"403":{"$ref":"#/components/responses/OriginRejected"},"413":{"$ref":"#/components/responses/TooLarge"},"503":{"$ref":"#/components/responses/Unavailable"}}}}},"components":{"parameters":{"AgentId":{"name":"id","in":"path","required":true,"schema":{"type":"string","pattern":"^op_[0-9A-Za-z]{22}$"}}},"securitySchemes":{"agentBearer":{"type":"http","scheme":"bearer","description":"Client-generated 32 random bytes encoded as 64 lowercase hex characters. Keep for private job access. Not a crypto private key; no cookie or account required."},"creditWallet":{"type":"apiKey","in":"cookie","name":"image_credit_pass","description":"Opaque anonymous wallet credential created by GET /api/credits. Not an on-chain wallet. Keep it private and retain it through checkout and editing."},"mcpOAuth":{"type":"oauth2","description":"OAuth 2.1 authorization code with PKCE (S256) for public clients. Discover through /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-authorization-server. Clients identify with a client ID metadata document URL or register at /oauth/register. Consent links the client to the browser's credit wallet.","flows":{"authorizationCode":{"authorizationUrl":"https://bestimageeditor.com/oauth/authorize","tokenUrl":"https://bestimageeditor.com/oauth/token","refreshUrl":"https://bestimageeditor.com/oauth/token","scopes":{"edit":"Spend the connected wallet's credits on image edits"}}}},"stripeSignature":{"type":"apiKey","in":"header","name":"stripe-signature","description":"Stripe-generated timestamp and HMAC signature over the original request body; not a static API key."}},"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"},"status":{"enum":["failed","unknown"],"description":"Browser keyed edit: failed is terminal with credit released; unknown requires recovery/manual reconciliation, never a new request."},"code":{"type":"string","description":"Machine-readable error code when supplied. Do not parse the human-readable error message for retry decisions."}}},"ImageUpload":{"type":"string","format":"binary","description":"PNG, JPEG, or WebP file, at most 20 MiB and 16 megapixels. The source is auto-oriented. Send actual file bytes, not a URL or base64 text. Limits may also apply after normalization."},"Credits":{"type":"object","required":["purchased","spent","reserved","checkoutEnabled"],"properties":{"purchased":{"type":"integer","minimum":0},"spent":{"type":"integer","minimum":0},"reserved":{"type":"integer","minimum":0},"checkoutEnabled":{"type":"boolean","description":"Whether Stripe configuration is present, not a live Stripe health check."}}}},"responses":{"EditPending":{"description":"No new provider call. Check the same request again; unknown may require manual reconciliation.","content":{"application/json":{"schema":{"type":"object","required":["status","message"],"properties":{"status":{"enum":["running","unknown"]},"message":{"type":"string"}}}}}},"InvalidInput":{"description":"Invalid input or invalid Stripe signature for the webhook.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"CreditsRequired":{"description":"Missing/invalid wallet cookie or no available credits. This is NOT an x402 payment challenge; no PAYMENT-REQUIRED header is supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"You need at least 1 available credit to preview or generate an image."}}}},"OriginRejected":{"description":"Origin header not allowed, or missing/incorrect required checkout Origin.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"TooLarge":{"description":"Entire request body exceeds 32 MiB. Individual file limits instead return 400 after credit authorization.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ProviderFailure":{"description":"Upstream or request-processing failure. An edit may already be running; do not automatically retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unavailable":{"description":"Required service/configuration unavailable or credit/payment reconciliation pending. Read the error before retrying; an edit may already have completed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"ProviderTimeout":{"description":"Provider timed out. An edit may still be running and its credit remains reserved; do not automatically retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"InternalFailure":{"description":"Unhandled storage/server failure.","content":{"text/plain":{"schema":{"type":"string"},"example":"Internal Server Error"}}}}}}