A free image-generation API with no key and no account — and the three failures that look like success Every "free AI image API" tutorial starts the same way: create an account, verify a phone number, copy a key, put it in .env. This one skips all of that. There is nothing to apply for — you make up an identifier string yourself and send it as a header, and that string is your account. The honest limits, up front: output is 1K and watermarked, and you get a wallet of 15 credits. The three things you can spend them on cost 5, 15 and 40 — so one of them can never run at all, and when it refuses, it answers HTTP 200. That last part is why this post exists. Every failure mode on this endpoint is shaped like a success. Here's the whole client: pip install kavel from kavel import generate url = generate("a paper crane on a windowsill at sunrise, soft light, minimal") print(url) No config file, no environment variable, no third-party dependency — standard library only. It returns a CDN URL. The package is on PyPI; it wraps the anonymous tier of Kavel, an online image and video studio. Both images in this post came out of it while writing. The first is text-to-image. The second is the first one fed back in, with a prompt that only asks for an overcast sky: The folds of the paper, the highlight on the frame, the scratches on the glass — all still there. Only the light changed. That turns out to be a useful thing to know about, and I'll come back to it. Without the package: two endpoints The package is a shell around two HTTP calls, so any language can do this directly. Submit curl -X POST https://www.kavel.ai/api/ai/generate \ -H 'content-type: application/json' \ -H 'x-anon-id: any-unique-string-you-generate' \ -d '{ "provider": "kie", "mediaType": "image", "model": "kavel-image-v1", "scene": "text-to-image", "prompt": "a paper crane on a windowsill at sunrise", "options": {"aspect_ratio": "1:1"} }' Real response, measured 2026-09-06: {"code":0,"message":"ok","data":{ "id":"2a04f52a-0da3-4629-8a3f-ecc5a507f34f", "status":"pending","anon":true, "charged":5,"remaining":10,"grant":15}} You invent the x-anon-id yourself. Nothing issues it, nothing validates it, it maps to no account — it is just the key your wallet is stored under. Poll curl "https://www.kavel.ai/api/ai/anon-query?taskId=<id>&provider=kie&mediaType=image" \ -H 'x-anon-id: the same string' {"code":0,"message":"ok","data":{ "status":"success", "images":["https://cdn.kavel.ai/uploads/kie/image/af6c7cb1-....webp"], "watermarked":[true],"videos":[]}} Those two runs took 60s and 75s respectively — one sample each, not an average. Poll every 10–15 seconds; a one-second loop buys nothing. What the free tier actually covers Every row below is read out of a response like the ones above, not copied from a doc: Action Model Credits Where the number comes from Text-to-image kavel-image-v1 5 {"charged":5,"remaining":10,"grant":15} Photo edit (image-to-image) nano-banana-2-lite 15 {"charged":15,"remaining":0,"grant":15} Video, 480p / 6s kavel-video-v1 40 {"wall":true,"cost":40,"grant":15} Anonymous wallet — 15 the grant field above Per-IP daily ceiling — 30 {"wall":true,"reason":"anon_ip_daily"} Three consequences worth stating plainly: One edit empties the wallet. A 15-credit grant against a 15-credit edit is not a coincidence — it is sized so a signed-out visitor gets exactly one complete edit. Budget accordingly. Video cannot run, and it is not flaky. The cheapest configuration I could request — 480p, 6 seconds — costs 40 credits against a 15-credit wallet, and every other setting is priced above it, so there is nothing cheaper to fall back to. Without knowing that, you will spend an hour assuming you got the request shape wrong. Rotating the ID does not get you more. I burned through three x-anon-id values from one IP; after the 30th credit the first call on a fresh ID was refused, with reason switching from anon_credits to anon_ip_daily. Handle those two separately — the first one means this wallet is done, the second means this IP is done for today. Three failures that look like success 1. Running out of credit is an HTTP 200 This is the expensive one: {"code":0,"message":"ok","data":{ "wall":true,"reason":"anon_credits","cost":5,"remaining":0,"grant":15}} Status 200. code is 0. message is ok. Code that branches on the status code walks straight past this, reaches for data.id, and gets undefined one line later. On my first integration it surfaced as No task id — an error message pointing at entirely the wrong thing. Check the field explicitly, before anything else: data = resp.json()["data"] if data.get("wall"): raise RuntimeError(f"out of credit: {data['reason']}, this call needed {data['cost']}") task_id = data["id"] 2. The edit field is image_input, not image_urls I guessed options.image_urls and got: {"code":-1,"message":"a reference image is required for this edit"} It says the reference image is missing, and I had passed one — under a name the server never looked at. The correct shape: "options": {"image_input": ["https://.../photo.jpg"]} The pattern here is worth more than the fix: the error describes the outcome, not the cause, so following it leads away from the problem. The Python package wraps this as image_url and you never see the layer. 3. Task status is not monotonic The sequence I observed was processing → pending → success — it went backwards once. So a poll loop written as "anything other than processing means we're done" will fire early. Treat only success and failed as terminal; keep waiting on everything else. Wiring it into agents and workflows Because there is no key to distribute, this fits the places where you want other people to run something without sending them to a signup form first: MCP server — ask Claude or Cursor for an image and it runs; nothing to configure. It's listed on LobeHub. n8n workflow — a form trigger, two HTTP Request nodes for submit and poll, and a branch on wall: true that returns a readable message instead of failing silently. The template imports and runs with no credentials. Apify actor — batch a list of prompts. One gotcha: credits are metered per x-anon-id, so generate a fresh ID per prompt or everything after the first one hits the wall. None of the three asks the person using it to register anywhere. Back to those two images The prompt for the edit only said: change the sky to overcast, keep the crane and the windowsill exactly as they are. The frame highlight, the scratches on the glass, the folds — all preserved. Here's the rule that saves re-runs: a model preserves what it can still see, and invents what it can't. So the leverage in an edit prompt is not describing the change in more detail, it's naming what must not change. Same model, two outcomes: tell it to change the sky and it changes only the sky; hand it a blurred face and ask it to sharpen, and you get a sharp face that belongs to someone else — because the face was never in the input to begin with. I've only verified that on this one model, but the cause (a model can only preserve information that survived into the input) isn't specific to it. If you'd rather click than write code, the nano-banana-2-lite page takes an upload directly. Limits, stated up front Signed out Signed in Text-to-image ✅ 1K, watermarked ✅ no watermark, larger models Photo edit ✅ one run on the free grant ✅ Video ❌ 40 credits vs a 15-credit wallet ✅ Refill per-browser, roughly 30 days per plan As said at the top: anonymous output is 1K and watermarked. That's the shape of the free tier, not a defect. Signing up is free and starts you at 40 credits — eight text-to-image runs, or two edits, at the prices in the table above. There's also a daily check-in that adds more. I'm listing the unit costs so you can work out whether that's enough before you register rather than after. Removing the watermark and reaching the larger models starts from the same place; pricing is here. Signed-out video on the site runs on a separate browser-side engine rather than this endpoint — the video tools work without an account too. FAQ Is this endpoint going to disappear? It's the public shape of the anonymous trial path on the site — the same one a signed-out visitor hits when they press Generate. It lives and dies with the free tier rather than being a temporary promo endpoint. The credit numbers will move with costs, but every response carries grant and charged, so read them instead of hardcoding them. Can I rotate x-anon-id on every call? Yes, nothing validates it. But rotating only escapes the per-browser wallet, not the 30-credit per-IP daily ceiling. Rotation is required for batch work (without it everything after the first prompt walls), it just isn't a way to get unlimited credit. Why isn't video exposed in the Python package? Because signed out it fails 100% of the time. A function that can never succeed is worse than a missing one — people assume they're holding it wrong. It's left out, and the README says why. Do failed generations still cost credits? In my testing the charge lands at submission — it's in the charged field of the very first response. So get the prompt right the first time, particularly on the edit path, where one attempt is the entire wallet. Can I use the output commercially? Anonymous output is watermarked, so not directly. Watermark removal and licensing follow the signed-in plans. One thing to take away If you keep a single line from this: the failures are shaped like successes. Running out of credit returns HTTP 200 with "code":0 and "message":"ok", and a single boolean buried in data.wall is the only thing telling you nothing happened. Write that check first, then write the rest. If you'd rather not write any of it, kavel.ai/image runs in the browser — no account there either. All numbers measured 2026-09-06.