API architecture guide
How to implement an asynchronous image-editing API
Submit an image once, keep the returned job ID, wait for a terminal state by polling or signed webhook, then retrieve the result. Use the same idempotency key when retrying a submission so a transport failure does not create a second charged job. See the live OpenAPI 3.1 contract and the shipped integration skill.
Published and last source-verified August 11, 2026 · Peelaway Engineering
The four-step contract
1. Submit
POST /api/process with a base64 JPEG, PNG, or WebP and a prompt. The response returns a job_id immediately.
2. Persist
Store the job ID with your own work item. For submission retries, reuse the original Idempotency-Key.
3. Wait
Poll /api/process/status every 2–5 seconds, or provide a public HTTPS webhook URL. The terminal states are done and error.
4. Retrieve
After done, GET /api/process/result. Its output URL is signed and remains valid for one hour, so copy the bytes into storage you control.
Sources: request and response schemas and polling and result guidance.
Minimal curl workflow
Authentication uses a bearer API key. The submit endpoint accepts an optional client-supplied idempotency value; retain it until the submission outcome is known.
# Generate once, persist it, and reuse it for any submit retry.
IDEMPOTENCY_KEY="$(uuidgen)"
# Submit one image.
curl -sS -X POST https://api.peelaway.io/api/process \
-H "Authorization: Bearer $PEELAWAY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"image": "<base64-encoded JPEG, PNG, or WebP>",
"prompt": "remove the temporary sign",
"format": "jpeg"
}'
# -> {"job_id":"...","credits_remaining":24}
# Poll until status is done or error.
curl -sS \
-H "Authorization: Bearer $PEELAWAY_API_KEY" \
"https://api.peelaway.io/api/process/status?job_id=<job_id>"
# -> {"status":"pending"}
# After done, fetch the signed result URL.
curl -sS \
-H "Authorization: Bearer $PEELAWAY_API_KEY" \
"https://api.peelaway.io/api/process/result?job_id=<job_id>"Source: Peelaway OpenAPI.
TypeScript polling loop
This Node example polls every three seconds, which stays within the documented 2–5 second interval. Production code should also persist the job ID before entering the loop and apply its own overall timeout.
import { readFile } from "node:fs/promises";
const apiKey = process.env.PEELAWAY_API_KEY;
if (!apiKey) throw new Error("PEELAWAY_API_KEY is required");
const headers = {
Authorization: "Bearer " + apiKey,
"Content-Type": "application/json",
};
const image = (await readFile("input.jpg")).toString("base64");
const submission = await fetch("https://api.peelaway.io/api/process", {
method: "POST",
headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
body: JSON.stringify({ image, prompt: "remove the temporary sign" }),
});
if (!submission.ok) throw new Error("submit failed: " + submission.status);
const { job_id: jobId } = await submission.json() as { job_id: string };
for (;;) {
await new Promise((resolve) => setTimeout(resolve, 3_000));
const statusResponse = await fetch(
"https://api.peelaway.io/api/process/status?job_id=" + jobId,
{ headers },
);
if (!statusResponse.ok) throw new Error("status failed: " + statusResponse.status);
const { status } = await statusResponse.json() as {
status: "pending" | "done" | "error";
};
if (status === "pending") continue;
if (status === "error") throw new Error("image edit failed");
break;
}
const resultResponse = await fetch(
"https://api.peelaway.io/api/process/result?job_id=" + jobId,
{ headers },
);
if (!resultResponse.ok) throw new Error("result failed: " + resultResponse.status);
const result = await resultResponse.json() as { url: string };
console.log(result.url);Source: Peelaway integration skill.
Webhook verification and delivery
A webhook URL must be public HTTPS. Settlement callbacks carrydoneorerror, plus X-Peelaway-Timestamp and X-Peelaway-Signature headers. Fetch the per-user secret from GET /api/webhooks/secret, compute HMAC-SHA256 over the exact string ${timestamp}.${rawBody}, compare signatures, reject stale timestamps, and deduplicate on job ID.
Non-2xx and 3xx delivery responses are retried after 30 seconds, 2 minutes, 10 minutes, and 1 hour. A successful callback includes the signed result URL directly; an error callback contains a generic failure message instead.
Sources: callback schema and signature contract and delivery retry schedule.
Retry boundaries
| Response | Meaning | Client action |
|---|---|---|
| 400 | Invalid request or batch above the plan limit | Correct the input or reduce the batch; do not retry unchanged. |
| 402 | Insufficient credits | Add credits before retrying. |
| 403 | Email is not verified | Complete verification before submitting. |
| 429 | Rate-limited or too many jobs in flight | Honor Retry-After or wait for in-flight jobs to settle. |
| 502 | Upstream provider failure | Retry with the same Idempotency-Key. |
Source: documented error handling. Status lookup can also return 400 or 404; result lookup returns 202 while processing, 404 for an unknown job, and 500 for a failed job, as listed in the OpenAPI responses.
Limits to design around
- A request may contain up to 50 images, but the account plan can impose a lower concurrent-job and batch limit.
- Peelaway edits an input image; it does not generate an image from scratch. Use images the caller owns or is authorized to edit.
- Dimension behavior depends on the selected workflow. Validate representative outputs before removing human review.
- Signed result links expire after one hour. Download or copy the finished bytes rather than treating the URL as permanent storage.
Sources: batch and result schemas, usage boundaries, and workflow limitations.
Choose the next integration step
Create a verified account and key for REST, review the developer workflow, or use the connector guide for an interactive MCP client.
Primary sources
- Peelaway OpenAPI 3.1 specification — endpoint, request, response, callback, and schema contract.
- Peelaway SKILL.md — shipped integration, polling, retry, and usage instructions.
- Peelaway API llms.txt — concise public API and webhook overview.