What I Learned Building an AI Background Remover on Cloudflare A developer built CutoutKit, a web app on Cloudflare that removes image backgrounds and returns transparent PNGs. The project required authentication, usage limits, payments, and privacy boundaries, with a focus on atomic credit reservation to prevent race conditions and a proxy architecture to keep API keys secure. I recently shipped CutoutKit https://imagebackground.site/ , a small web app that removes image backgrounds and returns a transparent PNG. The interface is intentionally simple: upload an image, wait a few seconds, and download the result. The implementation behind that flow was less simple. Once I moved past the prototype, I needed authentication, usage limits, payments, webhook reliability, privacy boundaries, and an architecture that could run comfortably on Cloudflare. This post covers the decisions and mistakes that were most useful to me. It is not a tutorial for a specific starter repository; it is a set of patterns you can reuse in other API-backed SaaS products. I started with a few deliberate constraints: The resulting request flow looks like this: php Browser | +-- Next.js UI on Cloudflare | +-- Cloudflare server endpoint |-- verify session and request |-- reserve one usage credit in D1 |-- send the image to remove.bg |-- stream the processed PNG back -- release the reservation if processing fails Payment provider webhook -- verify signature -- record event -- grant credits in D1 One important privacy detail: “not stored” does not mean “never leaves the browser.” The uploaded file is sent through my server endpoint to the image-processing provider. My application does not write the original or processed image to a database, object store, or disk, but the processor still receives it to perform the requested operation. That distinction belongs in both the product copy and the privacy policy. The first production rule is obvious but easy to violate during a quick prototype: never call a paid processing API directly from browser code if it requires a secret key. The browser uploads a multipart/form-data request to a same-origin server endpoint. That endpoint reads the remove.bg key from a Cloudflare secret, adds it to the upstream request, and returns the image response. The key is never included in the JavaScript bundle or exposed through a public configuration endpoint. The proxy also became the right place to enforce the application’s rules: multipart/form-data .Client-side validation still improves the experience, but it is not a security boundary. Every meaningful rule is repeated on the server. Usage accounting looks trivial until two requests arrive at nearly the same time. A fragile implementation follows this sequence: read remaining credits call expensive API increment usage Two concurrent requests can both see the same remaining credit and both proceed. The better pattern is to reserve usage before calling the paid API: reserve credit atomically call upstream processor confirm reservation on success release reservation on failure This gives the request a small transaction-like lifecycle even though the database operation and external API call cannot share a real database transaction. The failure path matters as much as the success path. A timeout, rejected file, upstream outage, or exhausted provider balance should not charge the user. In my handler, upstream failures trigger a best-effort release of the reservation before returning a normalized error to the client. This pattern is useful for any metered API product: image generation, transcription, document conversion, email verification, or LLM calls. Google OAuth is conventional, but custom domains and preview domains create a sharp edge: the redirect URI must match exactly. My application builds the callback from a single canonical application origin: https://imagebackground.site/api/auth/google/callback That exact URI must appear in the Google OAuth client configuration. Differences in protocol, hostname, port, path, or trailing characters cause redirect uri mismatch . The login flow uses an OAuth state value stored in a short-lived, HTTP-only cookie. The callback compares that cookie with the returned state before exchanging the authorization code. After retrieving the Google profile, the app upserts the user in D1 and creates a server-side session. The browser receives a signed session identifier in a secure, HTTP-only cookie. It does not receive the Google access token, and the application does not need to keep that token after fetching the profile. D1 stores only the durable product data the app needs: the Google subject identifier, email, display information, sessions, usage events, and purchases. I chose 30-day credit packs rather than automatic subscriptions. The current model has a small free allowance and two paid packs. A purchase adds a fixed number of credits with an expiry date; it does not create recurring billing. Supporting both PayPal and Creem helped me separate the payment provider from the entitlement model. The provider collects money, but my own database decides whether a user may process an image. The common flow is: pending .The browser never submits an authoritative amount or number of credits. It submits only a plan identifier, and the server looks up the plan details. A payment success page is useful for the user experience, but it is not a reliable source of truth. The customer can close the tab, lose connectivity, or manipulate a browser request. Webhooks are the durable confirmation channel. My webhook handlers follow four rules: This is idempotency in practical form. Payment providers retry webhooks, and your endpoint must assume the same event can arrive more than once. I also store processing status for each webhook event. If an event fails halfway through, it is visible and can be retried without pretending it never arrived. The public pages are statically rendered, while the authenticated and image-processing routes run at the edge. Cloudflare D1 provides the relational state, and environment secrets keep provider credentials out of source control. The deployment also has one canonical host. Requests for the www hostname and the Pages preview hostname redirect to the apex domain. This fixed several subtle issues at once: I added a generated sitemap, robots.txt , canonical metadata, structured data, a favicon, and focused landing pages for real use cases such as product photos and transparent PNG creation. SEO did not change the application architecture, but the canonical-host decision definitely did. If I built another API-backed SaaS, I would make these decisions before polishing the interface: The background-removal model was not the hardest part of this product because I deliberately used a specialized API. The interesting engineering work was everything around it: protecting the API boundary, accounting for usage correctly, handling identity at the edge, and turning payment notifications into reliable entitlements. That is also encouraging. A small product does not need a huge infrastructure stack, but it does need clear trust boundaries and careful state transitions. You can try the finished implementation at CutoutKit https://imagebackground.site/ . If you are building a similar API-backed tool, I would be interested to hear which part caused the most trouble for you.