Guide9 min read

How to Add an Image Enhancement API to Your App (2026)

A developer guide to automating image upscaling, cleanup, and background removal over REST — the async job model, chaining, scopes, rate limits, and real costs.

ML

Munib Ali Laghari

Founder & Lead Developer

Quick answer

How do you add AI image enhancement to your app?

Call an image enhancement API instead of hosting the models. The flow is three requests: upload the image to get a storage key, create a job with that key and an operations array, then poll the job until it completes. EnhanceCraft exposes this over REST, and one job can chain several enhancements.

🔌

Somewhere in your product there is a feature request that reads like a small thing and is not: "can we clean up the images users upload?" Marketplace listings arrive as 800-pixel phone photos. Profile pictures come in dark and noisy. Supplier catalogs land with cluttered backgrounds and no consistency at all.

You have two ways to solve it. Run the models yourself, which means GPU infrastructure, model weights, a queue, and someone who owns all of it at 2 a.m. Or call an API and treat image quality as a service. This guide covers the second path end to end: when it is genuinely the right call, the three-call request flow, why the job model is asynchronous and how to poll it properly, how to chain several enhancements into one request, and what it actually costs per image.

When an Image API Beats Building It Yourself

Use an API when image processing is a feature of your product rather than the product itself. Running enhancement models in-house means provisioning GPUs, keeping model weights and dependencies current, building a job queue that survives restarts, and absorbing the cold-start latency — real engineering that competes with your roadmap for exactly the same people.

The honest inversion point is volume plus specificity. Self-hosting starts to pay when you are processing enough images continuously to keep a GPU saturated, and when you need a model tuned to your own data in a way no general service offers. Below that, a per-image API is usually cheaper once you count engineering time rather than just compute, because an idle GPU bills exactly the same as a busy one while a per-image call bills nothing.

The middle path is worth knowing about: call the API first, ship the feature, and measure real volume for a quarter before deciding whether any of it deserves dedicated infrastructure. Most teams discover their actual throughput is a fraction of what they projected.

How the API Works: Three Calls

The EnhanceCraft API exposes a small surface on purpose — you upload an image, create a job describing what to do with it, then poll that job until it finishes. Everything runs against the base URL https://api.enhancecraft.com/api/v1, and every request carries your key in a standard bearer Authorization header.

CallEndpointWhat it does
UploadPOST /upload/apiSends the image file, returns a storage key
Create jobPOST /jobs/apiTakes that storage key plus an operations array, returns a job ID
Check statusGET /jobs/api/{job_id}Returns the job's status and, once complete, the result
List jobsGET /jobs/apiReturns your recent jobs, for reconciliation and dashboards

The full request and response bodies, with copy-paste cURL, Python, and JavaScript for each call, live in the API documentation — this guide covers the decisions around them rather than repeating the samples.

How to Integrate It: Step by Step

Getting from zero to a processed image is four steps, and the first one is the only one that happens outside your code:

  1. 1.Create an API key in your dashboard. Keys are issued with the prefix sk_live_ and shown exactly once, because only a hash is stored server-side — there is no way to retrieve the plaintext later. Put it straight into your secret manager or environment config, never into your repository.
  2. 2.Upload the image and keep the storage key. POST the file to the upload endpoint and hold onto the storage key it returns. That key, not the raw file, is what every subsequent call refers to, so one upload can feed several different jobs.
  3. 3.Create a job describing the work. POST the storage key together with an operations array — one entry per enhancement, each with its own parameters. The job is accepted immediately and starts life queued.
  4. 4.Poll until the job completes, then fetch the result. Check the job endpoint on an interval until the status stops being queued or in-progress, then read the output. Handle the failure case explicitly rather than assuming completion.

Put together, a complete integration — upload, chained job, backoff polling, and both terminal states handled — is about forty lines:

python
import time
import requests

API_KEY = "sk_live_YOUR_API_KEY"
API_URL = "https://api.enhancecraft.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}


def enhance(file_path, operations, timeout=300):
    # 1. Upload the file and keep the storage key it returns
    with open(file_path, "rb") as f:
        upload = requests.post(
            f"{API_URL}/upload/api", headers=HEADERS, files={"file": f}
        )
    upload.raise_for_status()
    input_key = upload.json()["storage_key"]

    # 2. Create ONE job describing the whole chain
    created = requests.post(
        f"{API_URL}/jobs/api",
        headers={**HEADERS, "Content-Type": "application/json"},
        json={"input_key": input_key, "operations": operations},
    )
    created.raise_for_status()
    job_id = created.json()["id"]

    # 3. Poll with exponential backoff until the job reaches a terminal state
    delay, waited = 2, 0
    while waited < timeout:
        time.sleep(delay)
        waited += delay
        job = requests.get(f"{API_URL}/jobs/api/{job_id}", headers=HEADERS)
        job.raise_for_status()
        payload = job.json()

        if payload["status"] == "completed":
            return payload["output_url"]
        if payload["status"] == "failed":
            raise RuntimeError(payload.get("error_message") or "job failed")

        delay = min(delay * 2, 30)

    raise TimeoutError(f"job {job_id} did not finish within {timeout}s")


# Remove the background, upscale 4x, export WebP — one job, one charge
url = enhance(
    "supplier-photo.jpg",
    [
        {"type": "remove-background", "params": {}},
        {"type": "enhance", "params": {"scale": 4}},
        {"type": "convert", "params": {"output_format": "webp", "quality": 85}},
    ],
)
print(url)

That is the entire surface area. The equivalent cURL and JavaScript, plus the full response bodies for each endpoint, are in the API documentation.

Why It Is Asynchronous, and How to Poll Properly

Enhancement runs on GPU hardware and takes seconds to minutes depending on the operation and the image size, which is far too long to hold an HTTP request open. So job creation returns immediately with an ID and a queued status, and you collect the result separately. Your integration has to be built around that from the start — retrofitting async handling into code that assumed a synchronous response is a genuinely unpleasant refactor.

Two practical notes. There is currently no completion webhook — you poll, rather than being called back, so budget a little request volume for status checks and use exponential backoff rather than a tight loop: a first check after a couple of seconds, then progressively wider intervals, with an overall timeout so a stuck job cannot hang a worker forever. And treat the job ID as the unit of work in your own system — persist it against whatever record triggered the upload, so a crashed process can resume by re-polling rather than by reprocessing and paying twice.

Chaining Operations in a Single Request

The operations array is the part that makes a general enhancement API different from a single-purpose one. A background-removal service removes backgrounds; here you describe a sequence, and the images move through it in one job without round-tripping to your servers between steps.

That matters because real image pipelines are almost never one operation. A marketplace listing image typically needs the background removed, the resolution raised, noise cleaned up, and the file converted to a web format — four operations, one job, one credit charge, one result to store. Building the same thing across four single-purpose vendors means four uploads, four sets of credentials, four failure modes, and an intermediate file to babysit at every hop.

The catalog spans the same operations as the web app: upscaling, face restoration, denoise, deblur, background removal and replacement, object and text removal, relighting, color grading, perspective correction, and format conversion, plus the packaged e-commerce and real-estate presets. Anything you can see on a tool page is addressable from the API by its operation type.

Authentication, Scopes, and Key Hygiene

Every key carries a scope list, which is what lets you hand a key to a service without handing it your whole account. The default scope set covers the common integration — creating and reading jobs and uploads — while the fuller list adds job cancellation, batch reads, and usage reporting.

ScopeGrants
jobs:createSubmit new jobs
jobs:readRead job status and results
jobs:cancelCancel a queued job
uploads:createUpload image files
uploads:readRead upload metadata
batches:create / batches:readBatch operations
usage:readRead usage and quota data

Three habits are worth adopting on day one. Scope down — a service that only needs to check results should hold a read-only key, so a leak from your reporting job cannot spend credits. Use separate keys per service rather than one shared key, because the moment you need to revoke something, a shared key means an outage everywhere. And rotate rather than delete when a key is exposed: rotation issues a replacement so you can cut over without a gap. Per-key usage figures make it obvious which integration is actually consuming your quota.

Rate Limits by Tier

API access is a paid-plan feature and is not available on the free or starter tiers. The limits scale with the plan, in both request rate and how many keys you can have live at once:

PlanAPI accessMax keysRequests / minuteRequests / month
Free
Starter
Pro105050,000
Business50200200,000
EnterpriseUnlimitedCustomCustom

Size your integration against the per-minute number rather than the monthly one, because the per-minute limit is what bites first. A nightly job that dumps 5,000 images at the API in a burst will hit the ceiling even though the monthly allowance is nowhere near spent — meter your own submissions with a queue and a small concurrency cap, and remember that status polls count as requests too.

What It Costs Per Image

Pricing is per image processed, in credits, at the same rates as the web app — a 2x upscale costs 1 credit, a 4x upscale 2, background removal 1, and the heavier generative operations more. There is no separate API subscription and no per-seat charge on top: your plan grants access and a monthly credit allowance, and pay-as-you-go credits top it up when you need them.

Two details matter when you model this. Packaged presets are cheaper than the same operations chained by hand, because the bundles carry a discount — the marketplace and real-estate presets cost less than the sum of their parts. And credits do not expire, which makes the arithmetic behave for uneven workloads: a seasonal catalog refresh that burns a quarter's worth of credits in a fortnight does not forfeit the rest of the year. Current per-operation costs are listed on the pricing page.

What the API Does Not Do (Yet)

Being straight about the edges is more useful than a feature list, so: three things developers reasonably expect that are not there today.

  • No completion webhooks. Job results are collected by polling. If your architecture assumes a callback, you will need a poller or a scheduled reconciliation pass over the job list endpoint.
  • No batch endpoint on the API. Bulk processing is available in the dashboard, but over the API you submit one job per image and manage concurrency yourself, inside the rate limits above. In practice this is a short loop with a semaphore, and it gives you per-image error handling that a batch call would not.
  • No synchronous mode. Even fast operations return a job rather than an image, so there is no shortcut for a "just give me the result now" path in a request handler.

None of these block a solid integration, but all three change your design, which is why they belong before you write code rather than after.

Where to Go From Here

Before writing any integration code, run a handful of your genuinely worst real images through the equivalent free tool in the browser. Output quality on your actual data is the only thing that decides whether an image API is worth wiring in, and it takes two minutes to answer — long before you have committed to polling logic, key management, or a credit budget.

If the results hold up, the API documentation has the request and response shapes with working cURL, Python, and JavaScript for every endpoint, and the wider EnhanceCraft AI image toolkit shows the full operation catalog you can address programmatically. For workflow patterns rather than plumbing, our guide to batch image processing covers the pipeline thinking behind chained operations, and the supplier-photo-to-marketplace pipeline shows one running end to end.

Tags:APIDevelopersAutomationIntegrationBatch ProcessingImage Enhancement

Found this helpful?

Share it with your network

Ready to try it yourself?

25 free credits every month. No credit card. Process your first image in under 15 seconds.

Read the API documentation
ML

Munib Ali Laghari

Founder & Lead Developer · EnhanceCraft

Munib Ali Laghari is the founder and lead developer of EnhanceCraft, an AI image toolkit. He writes about AI upscaling, photo restoration, and background removal. Connect on LinkedIn

Recent Posts

Stay Updated

Get the latest guides and tutorials delivered to your inbox.

We respect your privacy. Unsubscribe at any time.