---
title: Quickstart
url: https://base_url.placeholder/docs/quickstart
group: Start
---

# Quickstart

From a key to a background removed and a public URL, in whichever of the SDKs, the CLI, curl or an MCP client you use. Then the same image through a saved preset of four steps, and the one-call road for when you want nothing stored.

## 1. Get a key

[Sign in and create an API key](https://base_url.placeholder/keys) — it is shown once. The Free plan needs no card and comes with a $3 welcome credit for AI ops. Every call sends the key as `Authorization: ApiKey is_sk_…`; every sample below reads it from `IMAGESTEP_API_KEY`. Install what you will call from:

```sh
pnpm add imagestep           # JavaScript / TypeScript
pip install imagestep        # Python
pnpm add -g imagestep-cli    # the CLI
export IMAGESTEP_API_KEY=is_sk_…
```

## 2. Upload, then run an op

Over REST an upload is one call — `POST /api/v1/assets/upload`, the image as the body with its own `Content-Type`, up to 25 MB — and the answer is the asset. The SDKs, the CLI and MCP take a file of any size in one line ([assets](https://base_url.placeholder/docs/assets#in) has the three-step route they use underneath). An op on that asset is a job, `{"op": "remove_bg", "assetIds": [...]}`, and the same request with `?dryRun=true` prices it first and creates nothing.

#### JavaScript

```js
import { ImageStep } from "imagestep";
const client = new ImageStep({ apiKey: process.env.IMAGESTEP_API_KEY });

const asset = await client.assets.upload("./product.jpg");
const price = await client.ops.estimate("remove_bg", { assetIds: asset.id }); // nothing is created
const job = await client.ops.removeBg(asset.id, { wait: true });
const [cutout] = await client.jobs.outputs(job);
const [published] = await client.assets.publish(cutout.id);
console.log(published.publicUrl);
```

#### Python

```python
from imagestep import ImageStep
client = ImageStep()  # reads IMAGESTEP_API_KEY

asset = client.assets.upload("./product.jpg")
price = client.ops.estimate("remove_bg", asset_ids=asset["id"])  # nothing is created
job = client.ops.remove_bg(asset["id"], wait=True)
cutout = client.jobs.outputs(job)[0]
published = client.assets.publish(cutout["id"])[0]
print(published["publicUrl"])
```

#### CLI

```sh
# pnpm add -g imagestep-cli, then imagestep login — or export IMAGESTEP_API_KEY
imagestep asset upload ./product.jpg -o json

# price it (nothing is created), then run it and block until it is done
imagestep jobs estimate --op remove_bg --asset-ids <asset-id>
imagestep jobs submit --op remove_bg --asset-ids <asset-id> --wait -o json

# the bytes, and a permanent URL
imagestep jobs outputs <job-id> --download ./out
imagestep asset publish <result-asset-id>
```

#### curl

```sh
# 0. upload — the bytes are the body, the answer is the asset id
curl -s "https://api.imagestep.dev/api/v1/assets/upload?name=product.jpg" \
  -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: image/jpeg" \
  --data-binary @product.jpg

# 1. price it (nothing is created)
curl -s https://api.imagestep.dev/api/v1/jobs?dryRun=true \
  -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" \
  -d '{"op":"remove_bg","assetIds":["<asset-id>"]}'

# 2. run it, and wait for it in the same call: "wait" holds the response until the job is done
#    (60 s at most) — 200 with the finished job and its resultAssetId, or 202 if it is still running
curl -s https://api.imagestep.dev/api/v1/jobs \
  -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"op":"remove_bg","assetIds":["<asset-id>"],"wait":30}'

# 3. only after a 202: wait again (or subscribe to the job.completed webhook and do not wait at all)
curl -s "https://api.imagestep.dev/api/v1/jobs/<job-id>?wait=30" -H "Authorization: ApiKey $IMAGESTEP_API_KEY"

# 4. a permanent URL: publish the result — its id is items[0].resultAssetId of the finished job
curl -s https://api.imagestep.dev/api/v1/assets/update \
  -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" \
  -d '{"ids":["<result-asset-id>"],"published":true}'
```

#### MCP

```text
# tool call — upload, run, wait and publish are one call; the answer carries publicUrl
transform  {"op": "remove_bg", "file_paths": ["./product.jpg"]}

# the same call with "dry_run": true prices it and creates nothing
# the hosted server cannot read your disk: give it "urls" or "asset_ids" instead of "file_paths"
```

Uploading is for work you want kept. If you are holding an image and only want the result back, the shorter road below is one call.

## 3. Get the result

`wait` holds the response until a one-item job is done, 60 s at most: `200` with the finished job, or `202` while it is still running — `GET /api/v1/jobs/{id}?wait=` then waits again. A batch answers at once and ignores `wait`: read it by id the same way, or register a webhook for `job.completed` and `job.failed` and never wait at all ([webhooks](https://base_url.placeholder/docs/webhooks)).

Each output is a new asset — `items[i].resultAssetId` of the finished job — and the input is left as it was. Publish an output and it gets a `publicUrl` on `cdn.imagestep.dev` that does not change; or download its bytes. What each of these things is — [assets](https://base_url.placeholder/docs/assets), [ops](https://base_url.placeholder/docs/ops), [jobs](https://base_url.placeholder/docs/jobs) — has its own page; every error you can meet is on [errors, retries & limits](https://base_url.placeholder/docs/errors).

## Next: several steps, one job

A [preset](https://base_url.placeholder/docs/presets) is a list of steps you save once — versioned, under your account — and then run by name. This one is the preset the home page runs: cut the product out, trim it, put it on white, get it under 50 KB.

```json
{
  "name": "Marketplace cut-out",
  "slug": "marketplace-cutout",
  "description": "Background removed, trimmed to the product, on white, a WebP under 50 KB.",
  "steps": [
    {
      "op": "remove_bg"
    },
    {
      "op": "trim"
    },
    {
      "op": "flatten",
      "parameters": {
        "background": "#ffffff"
      }
    },
    {
      "op": "compress",
      "parameters": {
        "format": "webp",
        "maxBytes": 50000
      }
    }
  ]
}
```

#### JavaScript

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

const preset = await client.presets.create(JSON.parse(await readFile("marketplace-cutout.json", "utf8")));
console.log(preset.slug, preset.version); // "marketplace-cutout" 1
```

#### Python

```python
import json

preset = client.presets.create(json.load(open("marketplace-cutout.json")))
print(preset["slug"], preset["version"])  # "marketplace-cutout" 1
```

#### CLI

```sh
imagestep preset create -f marketplace-cutout.json -o json
```

#### curl

```sh
curl -s https://api.imagestep.dev/api/v1/presets -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" -d @marketplace-cutout.json
```

#### MCP

```text
# tool call — an agent has no disk to read, so the document travels in the call
save_preset  {"name":"Marketplace cut-out","slug":"marketplace-cutout","description":"Background removed, trimmed to the product, on white, a WebP under 50 KB.","steps":[{"op":"remove_bg"},{"op":"trim"},{"op":"flatten","parameters":{"background":"#ffffff"}},{"op":"compress","parameters":{"format":"webp","maxBytes":50000}}]}
```

Then price it and run it over the asset you uploaded above. One of its steps is a model, so it runs as a single `chain` job: the intermediate images are not yours to manage, and the bill is per step.

#### JavaScript

```js
const price = await client.presets.run("marketplace-cutout", ["<asset-id>"], { dryRun: true }); // nothing is created
console.log(price.estimatedCredits, price.steps);

const job = await client.presets.run("marketplace-cutout", ["<asset-id>"], { wait: true });
const outputs = await client.jobs.outputs(job);
```

#### Python

```python
price = client.presets.run("marketplace-cutout", ["<asset-id>"], dry_run=True)  # nothing is created
print(price["estimatedCredits"], price.get("steps"))

job = client.presets.run("marketplace-cutout", ["<asset-id>"], wait=True)
outputs = client.jobs.outputs(job)
```

#### CLI

```sh
imagestep jobs estimate --preset-id marketplace-cutout --asset-ids <asset-id>
imagestep jobs submit --preset-id marketplace-cutout --asset-ids <asset-id> --wait -o json
```

#### curl

```sh
# price it — the same body, nothing is created
curl -s "https://api.imagestep.dev/api/v1/jobs?dryRun=true" -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" \
  -d '{"presetId":"marketplace-cutout","assetIds":["<asset-id>"]}'

# run it; "wait" holds the response until the job is done (60 s at most, then 202 and you wait again)
curl -s https://api.imagestep.dev/api/v1/jobs -H "Authorization: ApiKey $IMAGESTEP_API_KEY" -H "Content-Type: application/json" \
  -d '{"presetId":"marketplace-cutout","assetIds":["<asset-id>"],"wait":30}'
```

#### MCP

```text
# tool calls — the first prices it and creates nothing
run_preset  {"preset":"marketplace-cutout","asset_ids":["<asset-id>"],"dry_run":true}
run_preset  {"preset":"marketplace-cutout","asset_ids":["<asset-id>"]}
```

The dry run lists the steps the way they are billed — the model at its price (227 credits is the $0.0227 the catalogue lists for `remove_bg`), the three deterministic steps as one `process` segment at 0:

```json
{
  "type": "chain",
  "presetId": "pre_66a38dbbde6b43659060c8fa7c492173",
  "presetName": "Marketplace cut-out",
  "presetVersion": 1,
  "totalItems": 1,
  "costPerItem": 227,
  "estimatedCredits": 227,
  "creditBalance": 30000,
  "sufficientCredit": true,
  "assetCountLeft": 199,
  "processCountLeft": 200,
  "overageRuns": 0,
  "overageCredits": 0,
  "processPerItem": 1,
  "steps": [
    { "index": 0, "op": "remove_bg", "model": "fal-ai/bria/background/remove", "costPerItem": 227, "maxInputEdge": 2048 },
    { "index": 1, "op": "process", "costPerItem": 0 }
  ]
}
```

What happens when a step fails, what you pay for then, and how `resume` picks up from that step: [chains](https://base_url.placeholder/docs/presets#chains). More presets to copy, this one included: [recipes](https://base_url.placeholder/docs/recipes#marketplace-cutout).

## The shorter road: one call, nothing stored

Everything above is a job: this service has promised to finish the work, which is what the asset, the price and the webhook are for. When you are holding the image and only want the result back, a deterministic op — `resize`, `convert`, `compress`, `crop` and the rest of what `GET /api/v1/ops` marks with a `syncEndpoint` — runs while you wait: bytes in, bytes out, no upload, no asset, no publish. It is free on a paid plan; on Free it counts against the monthly allowance of deterministic ops and is paid from your balance past it. If it fails you send it again, so there is no `Idempotency-Key` either. AI ops never go this way; a provider call needs an owner for its retry and refund.

#### JavaScript

```js
import { writeFile } from "node:fs/promises";

const small = await client.images.transform("resize", { file: "./product.jpg", parameters: { width: 1200 } });
await writeFile("out.jpg", small);
```

#### Python

```python
small = client.images.transform("resize", file="./product.jpg", parameters={"width": 1200})
open("out.jpg", "wb").write(small)
```

#### CLI

```sh
imagestep image resize ./product.jpg --width 1200 --out out.jpg
```

#### curl

```sh
curl --data-binary @product.jpg -H "Content-Type: image/jpeg" \
  -H "Authorization: ApiKey $IMAGESTEP_API_KEY" \
  "https://api.imagestep.dev/api/v1/images/transform?op=resize&width=1200" -o out.jpg
```

#### curl -F

```sh
curl -F file=@product.jpg -H "Authorization: ApiKey $IMAGESTEP_API_KEY" \
  "https://api.imagestep.dev/api/v1/images/transform?op=resize&width=1200" -o out.jpg
```

A saved preset runs several steps in the same one call — if every step is deterministic: `marketplace-cutout` above starts with a model, so it is a job and only a job. A template renders to a PNG, and reading metadata is free — limits and the line between the two roads are on [synchronous ops](https://base_url.placeholder/docs/sync).

## Not writing code?

The same two roads exist where there is no code to paste. Each page opens with its own install and first call.

- [n8n](https://base_url.placeholder/docs/n8n#install) — install the community node, add the ImageStep credential, pick an op from the **Op** dropdown. **Store Result** off is the one-call road; on is a job with an asset id and a permanent URL.
- [Claude, Cursor or any MCP client](https://base_url.placeholder/docs/mcp#connect) — one config block, hosted or local; the tab above is what the agent then calls.
- [A coding agent with a shell](https://base_url.placeholder/docs/skill#install) — `imagestep skill --install claude-code` teaches it to drive the CLI.

An agent choosing between these starts on [the agents page](https://base_url.placeholder/docs/agents).
