MiaoCut

MiaoCut API

Remove image backgrounds from your own apps. The API runs on the same pipeline as the website's batch tool, so credits, result retention and refunds work the same way.

Base URL: https://pro-api.miaocut.app. All requests and responses are JSON, except the image upload and download links.

Quick start

  1. Create an API key on the Developer API page (sign-in required).
  2. Create a job with the file names, and get an upload link for each image.
  3. Upload each image to its link, then start the job.
  4. Poll the job until it is done, then download the transparent PNG results.

Authentication

Send your key in the Authorization header: Authorization: Bearer mc_live_YOUR_API_KEY. Keys start with mc_live_. The full key is shown only once when you create it. Keep it secret: anyone with the key can spend your credits. If a key leaks, revoke it on the Developer API page and it stops working immediately.

Credits

API calls use the same credits as the website. Credits are reserved for the whole job when you call start. Images that fail to process are refunded automatically.

profileModeCredits per image
fastStandard mode1
furFine edges mode (hair, fur, plush, plants)2

Endpoints

1. Create a job

POST /api/v1/jobs with a JSON body. Nothing is charged yet.

{
  "profile": "fast",
  "files": [{ "name": "photo-1.jpg" }, { "name": "photo-2.png" }]
}

Response:

{
  "job_id": "3f0c9a52-…",
  "profile": "fast",
  "cost_per_image": 1,
  "total_cost": 2,
  "expires_in": 3600,
  "uploads": [
    { "image_id": "…", "orig_filename": "photo-1.jpg", "put_url": "https://…" },
    { "image_id": "…", "orig_filename": "photo-2.png", "put_url": "https://…" }
  ]
}

The number of images per job depends on your plan (up to 200 on the free tier and Basic, up to 500 on Pro and Business). If your balance is lower than total_cost you get 402 insufficient_credits.

2. Upload the images

PUT the raw image bytes to each put_url, in the same order as files. No Authorization header is needed (and do not add one): the link carries its own signature and is valid for expires_in seconds (1 hour). Supported formats: PNG, JPEG and WebP. Images larger than 60 megapixels fail.

3. Start the job

POST /api/v1/jobs/{{job_id}}/start reserves the credits and queues the images. Response: {{ "status": "processing", "total": 2, "enqueued": 2 }}. A job can be started only once (409 already_started).

4. Check progress and download

GET /api/v1/jobs/{{job_id}}. Poll every 2–5 seconds. The first images can take up to a minute while a GPU starts. When done is true, every image has finished.

{
  "job_id": "3f0c9a52-…",
  "profile": "fast",
  "status": "processing",
  "total": 2,
  "succeeded": 1,
  "failed": 0,
  "done": false,
  "images": [
    { "image_id": "…", "idx": 0, "filename": "photo-1.jpg", "status": "done", "download_url": "https://…" },
    { "image_id": "…", "idx": 1, "filename": "photo-2.png", "status": "queued" }
  ],
  "retention_days": 1,
  "retention_ends_at": "2026-10-21T08:00:00.000Z"
}

Image status is queued, processing, done or failed. A done image has a download_url (a transparent PNG, valid for 1 hour; call this endpoint again for a fresh link). A failed image has an error and its credits are refunded. Results are kept for 1 day (7 days with a subscription), then deleted.

Limits

Errors

Errors are JSON like {{ "error": "insufficient_credits", "balance": 3, "needed": 10 }}.

StatuserrorMeaning
400no_files, too_many_imagesNo files in the request, or more images than your plan allows (max is returned).
401invalid_api_keyThe key is missing, malformed, unknown or revoked.
402insufficient_creditsNot enough credits (balance and needed are returned when creating a job).
404not_foundThe job does not exist, or was not created through the API.
409already_startedThe job was already started.
429rate_limited, too_many_active_jobsSlow down and retry after the Retry-After seconds.
503api_disabled, api_unavailable, enqueue_failedTemporarily unavailable, retry later. enqueue_failed refunds the reserved credits.

Examples

curl

KEY=mc_live_YOUR_API_KEY
API=https://pro-api.miaocut.app

# 1) create the job
curl -s "$API/api/v1/jobs" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" \
  -d '{"profile":"fast","files":[{"name":"photo.jpg"}]}'

# 2) upload the image to the put_url from the response (no Authorization header)
curl -s -X PUT --upload-file photo.jpg "PUT_URL_FROM_RESPONSE"

# 3) start, then 4) poll
curl -s -X POST "$API/api/v1/jobs/JOB_ID/start" -H "Authorization: Bearer $KEY"
curl -s "$API/api/v1/jobs/JOB_ID" -H "Authorization: Bearer $KEY"

Python

import time, requests

API = "https://pro-api.miaocut.app"
HEADERS = {"Authorization": "Bearer mc_live_YOUR_API_KEY"}

def remove_backgrounds(paths, profile="fast"):
    job = requests.post(f"{API}/api/v1/jobs", headers=HEADERS, json={
        "profile": profile, "files": [{"name": p} for p in paths]}).json()
    for path, up in zip(paths, job["uploads"]):
        with open(path, "rb") as f:
            requests.put(up["put_url"], data=f).raise_for_status()   # no Authorization header here
    requests.post(f"{API}/api/v1/jobs/{job['job_id']}/start", headers=HEADERS).raise_for_status()
    while True:
        st = requests.get(f"{API}/api/v1/jobs/{job['job_id']}", headers=HEADERS).json()
        if st["done"]:
            return st["images"]          # each "done" image has a download_url
        time.sleep(3)

Node.js (18+)

import { readFile } from "node:fs/promises";

const API = "https://pro-api.miaocut.app";
const headers = { Authorization: "Bearer mc_live_YOUR_API_KEY" };
const call = (path, init = {}) => fetch(API + path, { ...init, headers: { ...headers, ...init.headers } }).then((r) => r.json());

const job = await call("/api/v1/jobs", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ profile: "fast", files: [{ name: "photo.jpg" }] }),
});
await fetch(job.uploads[0].put_url, { method: "PUT", body: await readFile("photo.jpg") }); // no Authorization header
await call(`/api/v1/jobs/${job.job_id}/start`, { method: "POST" });
let status;
do {
  await new Promise((r) => setTimeout(r, 3000));
  status = await call(`/api/v1/jobs/${job.job_id}`);
} while (!status.done);
console.log(status.images);

Manage your API keys →