WayShot logoWayShot API

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.

Integration prompt — paste into your AI
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.

Create API keySigned-in users go straight to API Keys.
Environment
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.

Direct upload
# 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.

Create a digi 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.

Poll a job
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 }

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).

image.completed delivery
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

FieldRequiredMeaning
capabilityYesThe model capability to use. Available value: digi.
imageYesA publicly reachable source image URL, or an image_ref (upload://…) from POST /v1/uploads.
params.resolutionNo (default 1K)Output resolution. Available values: 1K ($0.10) or 2K ($0.15).
webhook_urlNoYour server endpoint for receiving the final result without polling.
Idempotency-Key (header)NoSame key + same body returns the original job without double billing; same key + different body returns 409.

Job statuses

StatusMeaningBilled
queuedThe job is accepted and waiting.No
processingThe output is being generated.No
succeededThe output URL is ready.Yes
failedThe job failed because of image or system issues.No
rejectedThe 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.

HTTPCodeRetryableMeaning
401invalid_api_keyNoThe API key is missing, malformed, or revoked.
402insufficient_balanceNoThe account balance cannot cover the job reserve.
404not_foundNoThe job does not exist or belongs to another account.
409idempotency_conflictNoSame Idempotency-Key was reused with a different body.
422image_unavailableYesThe image URL cannot be fetched (timeout or unreachable).
422image_too_largeNoThe input image exceeds the size limit.
422invalid_paramsNoInvalid capability, resolution, or request format.
422content_rejectedNoThe image or request violates policy checks.
429rate_limitedYesRequest 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.
500internal_errorNoUnexpected server error inside WayShot.
503model_unavailableYesThe model service is temporarily unavailable.
503service_overloadedYesThe 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.

Rulev1 behavior
Concurrent processing2 jobs per developer account (all API keys combined); further jobs stay queued.
In-flight jobsUp to 10 active (queued or processing) jobs per account; beyond that job creation returns 429 rate_limited.
Request rate — writes10 requests/second per API key for job creation and uploads; beyond that requests return 429 rate_limited — respect Retry-After.
Request rate — reads30 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 volumeHandled 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.