Quickstart
WayShot API uses an async job flow: create a job, receive a job_id, then poll or use a webhook to get the result. The machine-readable contract lives at /v1/openapi.json (runtime source of truth).
Fastest: integrate with AI
Copy this prompt and paste it into your AI coding tool (Cursor, Claude, Copilot…). It contains everything the AI needs to write your integration — just tell it your language.
I want to integrate the WayShot digi API into my app. digi turns a portrait photo into a realistic digicam-style image. Please write the integration code for me.API reference:- Base URL: https://api.wayshot.ai- Auth: send the header Authorization: Bearer <WAYSHOT_API_KEY>- Create a job: POST /v1/jobs JSON body: { "capability": "digi", "image": "<public URL of the input portrait>", "params": { "resolution": "1K" }, // "1K" or "2K" "webhook_url": "<optional: your endpoint for the result>" } Optional header: Idempotency-Key: <unique key> — retries with the same key return the original job instead of creating a duplicate. Keys are deduplicated indefinitely — always generate a fresh key for each new job. Response: { "job_id": "...", "status": "queued" }- Get the result: GET /v1/jobs/{job_id} Response: { "status": "queued|processing|succeeded|failed|rejected", "input": { "url": "...", "expires_at": "..." }, "output": { "url": "<image URL when succeeded>" }, "billed": true }- Async: the output is usually ready in about 30 seconds. Poll with backoff (2s -> 5s -> 10s) plus random jitter when polling several jobs, or use the webhook. Reads and writes are rate-limited separately; on 429/503 wait for Retry-After.- Billing: you are charged only when status = succeeded. failed / rejected cost $0.- Errors: 401 invalid_api_key, 402 insufficient_balance, 422 image_unavailable, 422 content_rejected, 429 rate_limited (respect Retry-After), 503 model_unavailable.Task: write <LANGUAGE> code that takes a portrait image URL, creates a digi job at 1K, polls until it finishes, returns the output image URL, and handles the errors above. Read the API key from the WAYSHOT_API_KEY environment variable.
1. Create an API key
Open the Developer Console, create a secret key, and save it securely. The full secret is shown once.
export WAYSHOT_API_KEY="wsk_live_xxx"2. Authentication
Every request to /v1/* carries your key in the Authorization: Bearer header. Keys look like wsk_live_…; revoking a key takes effect immediately.
3. Upload your image (optional)
If the source image is not on a public URL, use POST /v1/uploads to get a direct-upload ticket: PUT the file to put_url within 15 minutes, then pass the returned image_ref as the image field when creating the job. Accepted types: image/jpeg, image/png, image/webp, up to 10MB. Images on a public URL can skip this step.
# 1) Request a direct-upload ticketcurl -X POST https://api.wayshot.ai/v1/uploads \ -H "Authorization: Bearer $WAYSHOT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "content_type": "image/jpeg" }'# -> 201 { "upload_id": "fA9x2kQ7pLm4vRt8yBw3c",# "put_url": "https://storage.googleapis.com/...",# "expires_in": 900,# "image_ref": "upload://fA9x2kQ7pLm4vRt8yBw3c" }# 2) PUT the file to put_url within 15 minutes (same Content-Type)curl -X PUT "$PUT_URL" \ -H "Content-Type: image/jpeg" \ --data-binary @portrait.jpg# 3) Create the job with image_ref as the "image" field# { "capability": "digi", "image": "upload://fA9x2kQ7pLm4vRt8yBw3c", ... }
4. Create an image job
Use POST /v1/jobs to create a new output job. The available capability today is digi. Optional Idempotency-Key header makes retries safe — keys dedupe indefinitely, use a fresh key per job.
curl -X POST https://api.wayshot.ai/v1/jobs \ -H "Authorization: Bearer $WAYSHOT_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: order-42-digi" \ -d '{ "capability": "digi", "image": "https://your.cdn/portrait.jpg", "params": { "resolution": "1K" }, "webhook_url": "https://your.app/wayshot-webhook" }'# -> 201 { "job_id": "job_8f2a91", "status": "queued",# "capability": "digi", "resolution": "1K",# "cost_cents": 10, "created_at": "2026-07-15T09:30:00Z" }# Idempotency-Key (optional): retries with the same key return the# original job instead of creating a duplicate. Use a fresh key per job.
5. Fetch the result
Use GET /v1/jobs/{job_id} to poll the job — back off 2s → 5s → 10s and stop on a terminal status. If webhook_url is provided, WayShot also POSTs the final result to your server.
Polling many jobs at once? Don't fire all requests in the same instant — spread them out with random jitter and reuse one poll loop for all pending jobs. If you receive 429 or 503, wait for the Retry-After value before retrying; your job keeps running server-side either way. For high-volume integrations we recommend webhook_url — results are pushed to you, no polling needed.
curl https://api.wayshot.ai/v1/jobs/job_8f2a91 \ -H "Authorization: Bearer $WAYSHOT_API_KEY"# -> { "job_id": "job_8f2a91", "status": "succeeded",# "capability": "digi", "resolution": "1K",# "input": { "url": "https://storage.googleapis.com/...",# "expires_at": "2026-07-16T09:30:00Z" },# "output": { "url": "https://storage.googleapis.com/...",# "expires_at": "2026-07-16T09:30:00Z" },# "billed": true, "cost_cents": 10, "error": null }
output.url: a fresh 1-hour download link signed per request — just GET again after it expires; outputs are JPEG (image/jpeg) and kept for 30 daysbilled= (status === "succeeded"); failed/rejected net $0, the reserve is released automaticallyerror: { code, message } on non-success terminal states
6. Webhook
When a job reaches a terminal status, an image.completed event is delivered to your webhook_url (sent for success/failure/rejection alike — check the status in the body).
- Verify signatures via the
svix-id / svix-timestamp / svix-signatureheaders (standard Svix HMAC, SDKs at docs.svix.com/receiving); de-duplicate bysvix-id - Retries: failed deliveries are retried with exponential backoff — respond 2xx to acknowledge
- Debugging: point webhook_url at a tool like webhook.site to inspect requests
POST https://your.app/wayshot-webhooksvix-id: msg_2f9a... # Svix standard headerssvix-timestamp: 1752570600svix-signature: v1,K5oZfzN95Z...{ "job_id": "job_8f2a91", "status": "succeeded", "capability": "digi", "resolution": "1K", "output": { "url": "https://...", "expires_at": "..." }, "cost_cents": 10, "billed": true, "occurred_at": "2026-07-15T09:30:31Z"}
Request body
| Field | Required | Meaning |
|---|---|---|
capability | Yes | The model capability to use. Available value: digi. |
image | Yes | A publicly reachable source image URL, or an image_ref (upload://…) from POST /v1/uploads. |
params.resolution | No (default 1K) | Output resolution. Available values: 1K ($0.10) or 2K ($0.15). |
webhook_url | No | Your server endpoint for receiving the final result without polling. |
Idempotency-Key (header) | No | Same key + same body returns the original job without double billing; same key + different body returns 409. |
Job statuses
| Status | Meaning | Billed |
|---|---|---|
queued | The job is accepted and waiting. | No |
processing | The output is being generated. | No |
succeeded | The output URL is ready. | Yes |
failed | The job failed because of image or system issues. | No |
rejected | The job was rejected by policy checks. | No |
Errors
All non-2xx responses share one error body: { "error": { "code", "message", "request_id", "retryable" } }. 429 responses also carry a Retry-After header.
| HTTP | Code | Retryable | Meaning |
|---|---|---|---|
| 401 | invalid_api_key | No | The API key is missing, malformed, or revoked. |
| 402 | insufficient_balance | No | The account balance cannot cover the job reserve. |
| 404 | not_found | No | The job does not exist or belongs to another account. |
| 409 | idempotency_conflict | No | Same Idempotency-Key was reused with a different body. |
| 422 | image_unavailable | Yes | The image URL cannot be fetched (timeout or unreachable). |
| 422 | image_too_large | No | The input image exceeds the size limit. |
| 422 | invalid_params | No | Invalid capability, resolution, or request format. |
| 422 | content_rejected | No | The image or request violates policy checks. |
| 429 | rate_limited | Yes | Request rate exceeded (reads and writes are limited separately) or too many in-flight jobs (queued + processing ≥ 10) — the message field tells you which; respect Retry-After. |
| 500 | internal_error | No | Unexpected server error inside WayShot. |
| 503 | model_unavailable | Yes | The model service is temporarily unavailable. |
| 503 | service_overloaded | Yes | The service is shedding load under pressure — retry after the Retry-After delay. |
Processing limits
For v1, each developer account processes up to 2 jobs concurrently — extra jobs simply wait in the queue, no error. Concurrency is counted per account: multiple API keys share the same 2 slots. Request-rate protection is per key, with reads and writes limited separately. There is no separate limits dashboard or automatic tier upgrade in the first release.
| Rule | v1 behavior |
|---|---|
| Concurrent processing | 2 jobs per developer account (all API keys combined); further jobs stay queued. |
| In-flight jobs | Up to 10 active (queued or processing) jobs per account; beyond that job creation returns 429 rate_limited. |
| Request rate — writes | 10 requests/second per API key for job creation and uploads; beyond that requests return 429 rate_limited — respect Retry-After. |
| Request rate — reads | 30 requests/second per API key for job status polling (GET /v1/jobs/{job_id}), limited separately from writes — creating jobs never eats into your polling budget. |
| Higher volume | Handled manually by the team if needed. |
Defaults are initial values and may be tuned after load testing.
Billing rules
Prepaid balance, success-only billing: the job cost ($0.10 at 1K, $0.15 at 2K) is reserved when a job is created, captured only when it succeeds, and fully released when it fails or is rejected — failed jobs net $0. See Pricing for details, or view your bills on Billing.