Recipes
1. A folder of photos, from the command line
You have a folder of product shots and want a ZIP sized for Amazon, plus a list of the photos to double-check. Needs curl and jq.
#!/usr/bin/env bash
# usage: CUTSTACK_API_KEY=csk_live_… ./cutstack-folder.sh ./photos amazon-main out.zip
set -euo pipefail
DIR=${1:?folder}; PRESET=${2:-amazon-main}; OUT=${3:-results.zip}
BASE=https://trycutstack.com; AUTH="Authorization: Bearer $CUTSTACK_API_KEY"
JOB=$(curl -sf -X POST $BASE/v1/jobs -H "$AUTH" -H "Content-Type: application/json" \
-d "{\"name\":\"$(basename "$DIR")\",\"defaults\":{\"presetId\":\"$PRESET\"}}" | jq -r .id)
echo "batch $JOB"
# upload in chunks of 20 files so one bad request does not cost the whole folder
find "$DIR" -maxdepth 1 -type f \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.webp' \) -print0 \
| xargs -0 -n 20 sh -c 'args=""; for f in "$@"; do args="$args -F files=@$f"; done;
curl -sf -X POST '"$BASE"'/v1/jobs/'"$JOB"'/items -H "'"$AUTH"'" $args | jq -r "\" added \(.added), total \(.counts.total), rejected \(.rejected|length)\""' sh
curl -sf -X POST $BASE/v1/jobs/$JOB/start -H "$AUTH" -H "Idempotency-Key: folder-$JOB" \
-H "Content-Type: application/json" -d '{}' > /dev/null
SINCE=0
while :; do
J=$(curl -sf "$BASE/v1/jobs/$JOB?wait=60&since=$SINCE&fields=summary" -H "$AUTH")
S=$(echo "$J" | jq -r .status); echo " $S: $(echo "$J" | jq -r '"\(.counts.done) done, \(.counts.failed) failed, \(.counts.refunded) refunded"')"
case "$S" in queued|running) SINCE=$(echo "$J" | jq .updatedAt) ;; *) break ;; esac
done
curl -sf "$BASE/v1/jobs/$JOB/download?pattern={name}-{preset}" -H "$AUTH" -o "$OUT"
echo "wrote $OUT"
curl -sf $BASE/v1/jobs/$JOB/review -H "$AUTH" | jq -r '.items[] | " check: \(.name) (\(.reason))"'2. A shop backend that processes new products
Product images already live on your CDN. When a product is created, send its image links in one batch, get told when it is done, and store the results back. The webhook secret is stored with the product so the receiver can check signatures.
const BASE = "https://trycutstack.com";
const headers = { Authorization: `Bearer ${process.env.CUTSTACK_API_KEY}`, "Content-Type": "application/json" };
export async function processProduct(product) {
const job = await fetch(`${BASE}/v1/jobs`, {
method: "POST", headers,
body: JSON.stringify({
name: product.sku,
defaults: { presetId: "shopify", background: { mode: "white" } },
webhookUrl: "https://shop.example.com/hooks/cutstack",
webhookEvents: ["job.done"],
}),
}).then((r) => r.json());
await db.cutstackJobs.insert({ jobId: job.id, sku: product.sku, secret: job.webhookSecret });
await fetch(`${BASE}/v1/jobs/${job.id}/items`, { method: "POST", headers, body: JSON.stringify({ urls: product.imageUrls }) });
await fetch(`${BASE}/v1/jobs/${job.id}/start`, { method: "POST", headers: { ...headers, "Idempotency-Key": `product-${product.sku}-${job.id}` }, body: "{}" });
}app.post("/hooks/cutstack", express.raw({ type: "application/json" }), async (req, res) => {
const event = JSON.parse(req.body);
const row = await db.cutstackJobs.findOne({ jobId: event.jobId });
if (!row || !verifyCutstack(req.body, req.get("X-Cutstack-Signature"), row.secret)) return res.sendStatus(401);
res.sendStatus(204); // answer first, work after: we retry only on non-2xx
if (event.event !== "job.done") return;
const auth = { Authorization: `Bearer ${process.env.CUTSTACK_API_KEY}` };
const job = await fetch(`${BASE}/v1/jobs/${event.jobId}`, { headers: auth }).then((r) => r.json());
for (const item of job.items) {
if (item.status !== "done") continue;
const bytes = await fetch(`${BASE}${item.outputUrl}`, { headers: auth }).then((r) => r.arrayBuffer());
await cdn.put(`products/${row.sku}/${item.name}`, Buffer.from(bytes));
if (item.review === "red") await db.flags.insert({ sku: row.sku, image: item.name, reason: "needs a look" });
}
});verifyCutstack is on Events and webhooks. Photos with review: "red" were not charged; the recipe stores them anyway and flags them for a person.
3. A flow driven by the review queue
For a script, a person or an agent that should look only at what needs looking at: run a sample first, decide, run the rest, then act on the queue. Written in Python with requests.
import os, requests, time
BASE = "https://trycutstack.com"
S = requests.Session(); S.headers["Authorization"] = f"Bearer {os.environ['CUTSTACK_API_KEY']}"
def wait_done(job_id):
since = 0
while True:
j = S.get(f"{BASE}/v1/jobs/{job_id}", params={"wait": 60, "since": since, "fields": "summary"}).json()
if j["status"] not in ("queued", "running"): return j
since = j["updatedAt"]
job = S.post(f"{BASE}/v1/jobs", json={"defaults": {"presetId": "etsy"}}).json()
S.post(f"{BASE}/v1/jobs/{job['id']}/items", json={"zipUrl": "https://cdn.example.com/drops/spring.zip"})
# 1. a sample of ten, then decide
S.post(f"{BASE}/v1/jobs/{job['id']}/start", json={"sample": 10}, headers={"Idempotency-Key": f"sample-{job['id']}"})
j = wait_done(job["id"])
queue = S.get(f"{BASE}/v1/jobs/{job['id']}/review").json()
if queue["total"] > 3:
print("the sample looks off; stopping here:", [i["reason"] for i in queue["items"]])
S.delete(f"{BASE}/v1/jobs/{job['id']}")
raise SystemExit(1)
# 2. the rest
S.post(f"{BASE}/v1/jobs/{job['id']}/continue")
j = wait_done(job["id"])
# 3. act only on the queue: re-run flagged ones with the strongest model, drop failures
queue = S.get(f"{BASE}/v1/jobs/{job['id']}/review").json()
for item in queue["items"]:
if item["kind"] == "review" and item["canRerun"]:
S.post(f"{BASE}/v1/jobs/{job['id']}/items/{item['id']}/retry", json={"tier": "heavy"}) # 2 credits
elif item["kind"] == "failed":
S.delete(f"{BASE}/v1/jobs/{job['id']}/items/{item['id']}")
j = wait_done(job["id"])
with open("spring.zip", "wb") as f:
f.write(S.get(f"{BASE}/v1/jobs/{job['id']}/download", params={"pattern": "{name}-{preset}"}).content)
print(j["counts"])The queue is the loop’s whole input. On a 200-photo batch it is usually a handful of entries, each with a reason and a thumbnail, which is what makes this pattern cheap for an agent: it never reads 200 items or looks at 200 images.
Next: Reference. The OpenAPI spec, every endpoint, MCP.