API reference

Endpoint reference

Method-by-method contracts for account checks, file uploads, swap creation, task retrieval, and result retrieval. Each endpoint pairs its parameters with live request and response examples.

RESTBearer authJSON responses

Endpoints

Complete request fields, response shapes, ownership rules, and error contracts for every endpoint.

All endpoints sit under https://aifacesswap.com/api/v1 and authenticate via Authorization: Bearer $AIFACESWAP_API_KEY. Errors share the documented envelope.

GET/api/v1/me

Return the authenticated user, credit balances, and current rate-limit window.

Auth
Bearer $AIFS_API_KEY. Counts toward the per-minute rate limit only; no credits consumed.
Credits
0

Errors

CodeStatusWhen
unauthorized401Missing or malformed Authorization header.
invalid_api_key401Bearer token does not match a live API key.
rate_limited429Per-minute request budget exceeded.
method_not_allowed405Any method other than GET.
api_temporarily_disabled503Public API feature flag is off.
Request
curl -X GET https://aifacesswap.com/api/v1/me \
  -H 'Authorization: Bearer $AIFACESWAP_API_KEY'
200 response
user_id: string
email_verified: boolean
subscription_tier: string
credits: { permanent: number, monthly: number, subscription: number, total: number }
rate_limit: { limit: number, window_seconds: number, remaining: number }

Identity + credit snapshot for the calling key.

GET/api/v1/swaps

List swaps owned by the authenticated API key, newest first.

Auth
Bearer $AIFS_API_KEY. Filtered to apiKeyId = auth.apiKey.id — two keys on the same user see independent lists.
Credits
0

Query parameters

limit
integer
Page size. Defaults to 25; must be between 1 and 100 inclusive. Out-of-range values return validation_error.
cursor
string
Opaque cursor returned as next_cursor from the previous page. Pass back verbatim to fetch the next page; absent on the first request.

Errors

CodeStatusWhen
unauthorized401Missing or malformed Authorization header.
invalid_api_key401Bearer token does not match a live API key.
validation_error400limit out of range or cursor not a task-id string.
rate_limited429Per-minute request budget exceeded.
api_temporarily_disabled503Public API feature flag is off.
Request
curl -X GET https://aifacesswap.com/api/v1/swaps \
  -H 'Authorization: Bearer $AIFACESWAP_API_KEY'
200 response
data: PublicSwap[]
next_cursor: string | null

Page of swaps. Composite (createdAt DESC, taskId DESC) ordering — taskId acts as a tie-breaker so siblings sharing a createdAt second never get skipped across pages.

POST/api/v1/swaps

Create a face-swap task from publicly reachable image, GIF, or video URLs.

Auth
Bearer $AIFS_API_KEY. Concurrent-create cap is enforced — over-cap requests return rate_limited_concurrent before any side effects.
Credits
Charged on create via the shared pricing helper used by the web app; refunded automatically if the request fails after deduction. GIFs under the free threshold remain free; video costs match the web video swap formula.

Query parameters

wait
"true"
Optional. When true, the request long-polls until the swap reaches a terminal state (done / failed / canceled) or the poll budget expires. The response body always reflects the latest known row.

Headers

Content-Typerequired
string
Must be application/json. Multipart and form bodies return unsupported_media_type.
Idempotency-Key
string
Optional 1–64 character key. Scoped per API key. Replays a previously finalised create within the retention window; concurrent retries wait for the in-flight call to finalise instead of double-charging.

Request body

typerequired
"image" | "multi_face_image" | "gif" | "video"
Discriminator. Accepts image, multi_face_image, gif, and video. multi_face_image uses per-face source mappings to mirror the web multi-face image flow.
source_url
string (URL, https only)
Required for image, gif, and single-source video; omitted when face_swaps provides per-face source_url mappings. Public HTTPS URL of the face image to swap in. Fetched server-side through the SSRF guard (DoH-resolved, blocks private/loopback/link-local/cloud-metadata ranges).
target_urlrequired
string (URL, https only)
Public HTTPS URL of the target image, GIF, or video. MIME and size enforced server-side: ≤ image cap for type=image, ≤ GIF cap for type=gif, ≤ video cap for type=video.
target_face_index
integer ≥ 0
Optional. Index of the target face to replace when the target contains multiple faces.
enable_face_enhancer
boolean
Optional. Run the face-enhancer post-processor on the result.
reference_frame_time_seconds
number ≥ 0
type=gif or type=video. Seconds into the moving target to source the reference face match from.
duration_seconds
number > 0
type=gif or type=video. Caller-declared duration used by the pricing helper.
file_size_bytes
integer > 0
type=gif or type=video. Caller-declared byte size used by the pricing helper.
keep_audio
boolean
type=video only. Defaults to true, matching the web video swap flow that preserves the original audio track.
face_swaps
array
type=multi_face_image: required array of { face_index, source_url, enable_face_enhancer? }. type=video: optional array of { frame_time_seconds, face_index, source_url } for web-style multi-face video swaps. When omitted for video, source_url drives a single-face swap.
webhook_url
string (URL, https only)
Optional. HTTPS URL the platform POSTs the terminal-state webhook to. Validated with the same SSRF guard as media URLs at create time.
metadata
object
Optional opaque key/value bag echoed back on the swap object. Not interpreted by the platform.

Content type: application/json

The create endpoint is URL-only. Multipart uploads and base64 bodies are deliberately rejected; callers should host media at public HTTPS URLs before creating a swap. Multi-face image and video swaps are represented with face_swaps arrays so API callers can reproduce the web taskData shape.

Both source_url and target_url are fetched, MIME-sniffed, size-checked, and copied into R2 before the swap is queued. The original URLs do not need to remain live after the call returns.

NSFW pre-check: if either input is FLAGGED, the request is rejected with nsfw_content_detected. If either is still being scanned, the request returns api_temporarily_disabled with Retry-After so the caller can retry shortly.

Errors

CodeStatusWhen
unauthorized401Missing or malformed Authorization header.
invalid_api_key401Bearer token does not match a live API key.
validation_error400Body fails schema validation: missing field, wrong type, malformed URL, non-HTTPS webhook_url, or out-of-range numeric/boolean field.
unsupported_media_type415Content-Type is not application/json, or the fetched media type is outside the allowlist (image, GIF, or video).
payload_too_large413Fetched source_url or target_url exceeds the per-type byte cap.
source_unreachable400source_url or target_url resolves to a non-public address, fails DNS, or cannot be fetched.
nsfw_content_detected422One or more inputs were flagged by the content analyser.
insufficient_credits402Credit balance below the computed cost for this swap.
rate_limited429Per-minute request budget exceeded.
rate_limited_concurrent429Concurrent in-flight create cap exceeded for this key.
api_temporarily_disabled503Public API flag is off, or input NSFW checks have not completed yet.
internal_error500Queue/DB/Redis side-effect failed; credits and idempotency claim are rolled back before the response.
Request
curl -X POST https://aifacesswap.com/api/v1/swaps \
  -H 'Authorization: Bearer $AIFACESWAP_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "type": "example",
  "source_url": "https://example.com/source_url.jpg",
  "target_url": "https://example.com/target_url.jpg",
  "target_face_index": 1,
  "enable_face_enhancer": true
}'
201 response
id: string
type: "image" | "multi_face_image" | "gif" | "video"
status: "queued" | "preparing" | "processing" | "done" | "failed" | "canceled"
credit_cost: number
progress: number
created_at: string (ISO 8601 UTC)
result_url: string | null
error: string | null
metadata: object | null
webhook_url: string | null

PublicSwap for the newly created task. result_url is null until the swap reaches done. When set, it points at the public R2 URL derived from R2_PUBLIC_URL — v1 deliberately does not issue signed URLs.

GET/api/v1/swaps/{taskId}

Fetch a single swap by id. Per-key ownership enforced.

Auth
Bearer $AIFS_API_KEY. 404 with not_found if the task does not exist OR is owned by another key — the envelope is identical so callers cannot probe foreign task ids.
Credits
0

Path parameters

taskIdrequired
string
Task id returned by POST /api/v1/swaps. Must match [A-Za-z0-9_-]{1,64}.

Errors

CodeStatusWhen
unauthorized401Missing or malformed Authorization header.
invalid_api_key401Bearer token does not match a live API key.
not_found404Task does not exist, was cleaned up, or is owned by a different key.
rate_limited429Per-minute request budget exceeded.
api_temporarily_disabled503Public API feature flag is off.
Request
curl -X GET https://aifacesswap.com/api/v1/swaps/{taskId} \
  -H 'Authorization: Bearer $AIFACESWAP_API_KEY'
200 response
id: string
type: "image" | "multi_face_image" | "gif" | "video"
status: "queued" | "preparing" | "processing" | "done" | "failed" | "canceled"
credit_cost: number
progress: number
created_at: string (ISO 8601 UTC)
result_url: string | null
error: string | null
metadata: object | null
webhook_url: string | null

PublicSwap for the requested task id.

DELETE/api/v1/swaps/{taskId}

Cancel a queued or preparing swap. Always returns 200 with the current swap body — there is no 409.

Auth
Bearer $AIFS_API_KEY.
Credits
0 to call. Phase 4 refund hook is wired but the refund itself ships in the queue-completion path.

Path parameters

taskIdrequired
string
Task id returned by POST /api/v1/swaps. Must match [A-Za-z0-9_-]{1,64}.

Errors

CodeStatusWhen
unauthorized401Missing or malformed Authorization header.
invalid_api_key401Bearer token does not match a live API key.
not_found404Task does not exist or is owned by a different key.
rate_limited429Per-minute request budget exceeded.
api_temporarily_disabled503Public API feature flag is off.

The cancel uses a status-filtered conditional UPDATE so a worker pick-up cannot race the cancel into a PROCESSING row.

Request
curl -X DELETE https://aifacesswap.com/api/v1/swaps/{taskId} \
  -H 'Authorization: Bearer $AIFACESWAP_API_KEY'
200 response
id: string
status: "queued" | "preparing" | "processing" | "done" | "failed" | "canceled"
...other PublicSwap fields

PublicSwap reflecting state AFTER the conditional cancel. QUEUED / PREPARING flip to canceled; PROCESSING / DONE / FAILED / already-canceled are returned unchanged (cancel is a no-op past PREPARING).

POST/api/v1/face-detect

Enqueue a face-detection task for a publicly reachable image. Async; returns a task id to poll.

Auth
Bearer $AIFS_API_KEY. 0 credits — counts toward the per-minute rate limit only. Synchronous detection is deferred to a later phase.
Credits
0

Headers

Content-Typerequired
string
Must be application/json.

Request body

target_urlrequired
string (URL, https only)
Public HTTPS URL of the image to scan. SSRF-guarded with the same DoH-backed validator used by POST /api/v1/swaps.

Content type: application/json

Errors

CodeStatusWhen
unauthorized401Missing or malformed Authorization header.
invalid_api_key401Bearer token does not match a live API key.
validation_error400target_url missing, malformed, non-HTTPS, or a non-canonical IP literal.
source_unreachable400target_url resolves to a non-public address or DNS fails.
unsupported_media_type415Content-Type is not application/json.
rate_limited429Per-minute request budget exceeded.
internal_error500Failed to initialise the result hash in Redis (queue insert is rolled back).
api_temporarily_disabled503Public API feature flag is off.
Request
curl -X POST https://aifacesswap.com/api/v1/face-detect \
  -H 'Authorization: Bearer $AIFACESWAP_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
  "target_url": "https://example.com/target_url.jpg"
}'
202 response
task_id: string
status: "queued"
poll_url: string (e.g. "/api/v1/face-detect/{taskId}")
note: string

Detection accepted. Poll the returned poll_url for the result snapshot.

GET/api/v1/face-detect/{taskId}

Poll a face-detection task enqueued by POST /api/v1/face-detect. Per-key ownership enforced.

Auth
Bearer $AIFS_API_KEY. 404 with not_found covers both missing tasks and tasks owned by another key.
Credits
0

Path parameters

taskIdrequired
string
Task id returned by the create endpoint. Must match [A-Za-z0-9_-]{1,64}.

Errors

CodeStatusWhen
unauthorized401Missing or malformed Authorization header.
invalid_api_key401Bearer token does not match a live API key.
not_found404Task does not exist or is owned by a different key.
rate_limited429Per-minute request budget exceeded.
api_temporarily_disabled503Public API feature flag is off.
Request
curl -X GET https://aifacesswap.com/api/v1/face-detect/{taskId} \
  -H 'Authorization: Bearer $AIFACESWAP_API_KEY'
200 response
task_id: string
status: "queued" | "processing" | "done" | "failed"
faces_detected: number | null
faces: Array<{ index: number, bounding_box: { x: number, y: number, width: number, height: number } }> | null
error: string | null
created_at: string | null
completed_at: string | null

Current detection snapshot. faces is populated when status is done; error populated when failed.