ID PhotoREST · JSONOpenAPI 3.1

SVOYAGER ID Photo API

Turn any photo into a compliant ID / passport / visa photo.

Overview

What it does

The SVOYAGER ID Photo API turns an ordinary portrait into a compliant document photo. ID Photo is a single product: passport, visa, residence-permit and national-ID are represented as document specifications, not separate products. You upload a photo, generate a watermarked preview against a specification, then finalize to get the no-watermark result.

Preview → final workflow

  1. 1Upload a photo. Create an upload target and PUT the image bytes to it.
  2. 2Create a session. Render a watermarked preview against a spec. Review the returned issues.
  3. 3Finalize. Turn the reviewed session into an order and receive the no-watermark final.

Quickstart

Every request is authenticated with a Bearer secret key. Start in test mode:

curl
# List specifications to find a spec_id
curl -G "https://partner.svoyager.com/v1/photo-specifications" \
  -H "Authorization: Bearer svoy_sk_test_YOUR_KEY" \
  --data-urlencode "country=US"

Base URL & environments

All endpoints are served under a single base URL:

Base URL
https://partner.svoyager.com/v1

There is no environment path or parameter. Whether a request runs in test or live is determined entirely by your API key prefix (svoy_sk_test_… vs svoy_sk_live_…). Responses echo this as livemode.

Authentication

API keys

Authenticate every request with an Authorization header carrying your secret key. Keys are created and rotated in the Partner Center.

Header
Authorization: Bearer svoy_sk_test_R2p9Kx7YOURKEYHERExxxxxxx

The prefix selects the environment: svoy_sk_test_… for test, svoy_sk_live_… for live. A test key can never act on live data (and vice versa) — mismatches return 401 wrong_environment.

Scopes

Keys carry explicit least-privilege scopes. A request missing the required scope returns 403 insufficient_scope.

ScopeGrants
photo.specs.readList and retrieve photo specifications.
photo.sessions.readRetrieve sessions and their previews.
photo.sessions.writeCreate uploads and photo sessions (previews).
photo.finalize.writeFinalize a session into a no-watermark order.
photo.orders.readRetrieve orders and download finals.

Keeping keys safe

Secret keys are server-to-server only. Never embed a secret key in browser or mobile code, a public repository, or a client-side bundle. If a key is exposed, rotate it immediately in the Partner Center.

Core API

Endpoints

Required fields are marked with *. Examples use placeholder credentials — never real keys.

POST/v1/uploads

Upload a photo

Create a short-lived presigned upload target, then PUT the raw image bytes straight to `upload_url`. This bypasses request-body size limits (product cap 10 MB). No quota is spent.

scope photo.sessions.write
Request
Body (application/json)
FieldTypeDescription
content_type*stringMIME type of the image.image/jpegimage/pngimage/webpimage/heic
  • `upload_url` is single-use and expires; upload promptly and do not modify the URL.
Example request
curl
# 1) Create the upload target
curl -X POST "https://partner.svoyager.com/v1/uploads" \
  -H "Authorization: Bearer svoy_sk_test_R2p9Kx7YOURKEYHERExxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"content_type":"image/jpeg"}'

# 2) PUT the bytes to the returned upload_url (exactly as returned)
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: image/jpeg" \
  --data-binary @photo.jpg
Response · 201
201 · application/json
{
  "object": "upload",
  "upload_url": "https://upload.svoyager.com/v1/EXAMPLE?signature=…",
  "upload_ref": "up_ref_tXk3W2b8Qe7Ra1Nc0",
  "max_bytes": 10485760,
  "expires_at": "2026-07-24T18: 20: 00Z"
}
Errors
StatusCodeWhen
400missing_content_type`content_type` was not provided.
415unsupported_formatThe content type is not an accepted image format.
503temporarily_unavailableUploads are temporarily unavailable.
502upload_init_failedThe upload could not be initialised — retry shortly.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

POST/v1/photo-sessions

Create a photo session

Generate a watermarked preview from an upload against a specification. Spends one `preview` unit of your plan. Review `issues` before finalizing.

scope photo.sessions.write Idempotency-Key required
Request
Body (application/json)
FieldTypeDescription
upload_ref*stringThe `upload_ref` from Upload a photo.
spec_id*stringThe `photo_specification` id (`spc_…`) to render against.
Example request
curl
curl -X POST "https://partner.svoyager.com/v1/photo-sessions" \
  -H "Authorization: Bearer svoy_sk_test_R2p9Kx7YOURKEYHERExxxxxxx" \
  -H "Idempotency-Key: a7f3c9e1-2b4d-4c8a-9f10-3e5d6b7a8c9d" \
  -H "Content-Type: application/json" \
  -d '{"upload_ref":"up_ref_tXk3W2b8Qe7Ra1Nc0","spec_id":"spc_9wTv2c1Kd8Qe"}'
Response · 201
201 · application/json
{
  "object": "photo_session",
  "id": "3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10",
  "livemode": false,
  "status": "preview_ready",
  "spec_id": "spc_9wTv2c1Kd8Qe",
  "preview_url": "https://partner.svoyager.com/v1/photo-sessions/3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10/preview",
  "watermarked": true,
  "issues": [],
  "created_at": "2026-07-24T18: 30: 00Z"
}
Errors
StatusCodeWhen
400missing_idempotency_keyThe `Idempotency-Key` header is missing.
400missing_upload_ref`upload_ref` is missing.
400missing_spec_id`spec_id` is missing.
400invalid_upload_refThe referenced upload could not be read.
413file_too_largeThe uploaded file exceeds 10 MB.
415unsupported_formatThe uploaded file is not an accepted image format.
404spec_not_foundNo active specification matches `spec_id`.
402no_active_planNo active plan is assigned for this environment.
402quota_exceededIncluded preview quota for the period is exhausted.
402plan_inactiveThe plan is not active for this environment.
409idempotency_key_reuseThe key was reused with a different body.
409request_in_progressA request with this key is still processing.
422unprocessable_imageThe image could not be processed.
502provider_errorThe preview could not be generated upstream — retry shortly.
503provider_busyThe processing service is busy — retry shortly.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

GET/v1/photo-sessions/{id}

Retrieve a photo session

Fetch a session by id, including its current status, `preview_url`, and any `issues`.

scope photo.sessions.read
Request
Path parameters
FieldTypeDescription
id*stringSession id.
Example request
curl
curl "https://partner.svoyager.com/v1/photo-sessions/3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10" \
  -H "Authorization: Bearer svoy_sk_test_R2p9Kx7YOURKEYHERExxxxxxx"
Response · 200
200 · application/json
{
  "object": "photo_session",
  "id": "3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10",
  "livemode": false,
  "status": "preview_ready",
  "spec_id": "spc_9wTv2c1Kd8Qe",
  "preview_url": "https://partner.svoyager.com/v1/photo-sessions/3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10/preview",
  "watermarked": true,
  "issues": [],
  "created_at": "2026-07-24T18: 30: 00Z"
}
Errors
StatusCodeWhen
404session_not_foundNo session matches the id for this account/environment.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

GET/v1/photo-sessions/{id}/preview

Download the preview image

Stream the watermarked preview image bytes for a session (SVOYAGER-hosted; the upstream URL is never exposed). This is the resource `preview_url` points to.

scope photo.sessions.readReturns image bytes
Request
Path parameters
FieldTypeDescription
id*stringSession id.
  • Returns raw `image/*` bytes on success (not JSON).
Example request
curl
curl "https://partner.svoyager.com/v1/photo-sessions/3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10/preview" \
  -H "Authorization: Bearer svoy_sk_test_R2p9Kx7YOURKEYHERExxxxxxx" \
  --output preview.jpg
Response · 200

On success the endpoint streams raw image/* bytes (not JSON). Save the response body to a file.

Errors
StatusCodeWhen
404preview_not_foundThe session or its preview does not exist.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

POST/v1/photo-sessions/{id}/finalize

Finalize a photo

Turn a reviewed session into a deliverable order. In live mode this produces the no-watermark final and spends one `final` unit; in test mode it emulates the final from the preview.

scope photo.finalize.write Idempotency-Key required
Request
Path parameters
FieldTypeDescription
id*stringSession id to finalize.
Example request
curl
curl -X POST "https://partner.svoyager.com/v1/photo-sessions/3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10/finalize" \
  -H "Authorization: Bearer svoy_sk_live_A8m4Qz1YOURKEYHERExxxxxxx" \
  -H "Idempotency-Key: a7f3c9e1-2b4d-4c8a-9f10-3e5d6b7a8c9d"
Response · 201Returns 200 with the existing order if the session was already finalized (never double-charged).
201 · application/json
{
  "object": "photo_order",
  "id": "8a2d5b40-71c6-4f0e-b3a9-1e7c9d05f284",
  "livemode": true,
  "sandbox_emulated": false,
  "status": "final_ready",
  "session_id": "3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10",
  "spec_id": "spc_9wTv2c1Kd8Qe",
  "final_url": "https://partner.svoyager.com/v1/orders/8a2d5b40-71c6-4f0e-b3a9-1e7c9d05f284/final",
  "watermarked": false,
  "created_at": "2026-07-24T18: 31: 00Z"
}
Errors
StatusCodeWhen
400missing_idempotency_keyThe `Idempotency-Key` header is missing.
404session_not_foundNo session matches the id for this account/environment.
422session_not_finalizableThe session is not in a finalizable state.
402no_active_planNo active plan is assigned for this environment.
402quota_exceededIncluded final quota for the period is exhausted.
402plan_inactiveThe plan is not active for this environment.
409idempotency_key_reuseThe key was reused with a different request.
409request_in_progressA request with this key is still processing.
502provider_errorThe final could not be produced upstream — retry shortly.
503provider_busyThe processing service is busy — retry shortly.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

GET/v1/orders/{id}

Retrieve an order

Fetch a finalized order by id, including its status and `final_url` once ready.

scope photo.orders.read
Request
Path parameters
FieldTypeDescription
id*stringOrder id.
Example request
curl
curl "https://partner.svoyager.com/v1/orders/8a2d5b40-71c6-4f0e-b3a9-1e7c9d05f284" \
  -H "Authorization: Bearer svoy_sk_live_A8m4Qz1YOURKEYHERExxxxxxx"
Response · 200
200 · application/json
{
  "object": "photo_order",
  "id": "8a2d5b40-71c6-4f0e-b3a9-1e7c9d05f284",
  "livemode": true,
  "sandbox_emulated": false,
  "status": "final_ready",
  "session_id": "3f9c1e77-2b8a-4a1e-9d2c-6b5a4c3d2e10",
  "spec_id": "spc_9wTv2c1Kd8Qe",
  "final_url": "https://partner.svoyager.com/v1/orders/8a2d5b40-71c6-4f0e-b3a9-1e7c9d05f284/final",
  "watermarked": false,
  "created_at": "2026-07-24T18: 31: 00Z"
}
Errors
StatusCodeWhen
404order_not_foundNo order matches the id for this account/environment.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

GET/v1/orders/{id}/final

Download the final image

Stream the final image bytes for an order (SVOYAGER-hosted). In live mode this is the no-watermark deliverable; in test mode it is the sandbox-emulated copy. This is the resource `final_url` points to.

scope photo.orders.readReturns image bytes
Request
Path parameters
FieldTypeDescription
id*stringOrder id.
  • Returns raw `image/*` bytes on success (not JSON).
Example request
curl
curl "https://partner.svoyager.com/v1/orders/8a2d5b40-71c6-4f0e-b3a9-1e7c9d05f284/final" \
  -H "Authorization: Bearer svoy_sk_live_A8m4Qz1YOURKEYHERExxxxxxx" \
  --output final.jpg
Response · 200

On success the endpoint streams raw image/* bytes (not JSON). Save the response body to a file.

Errors
StatusCodeWhen
404final_not_foundThe order or its final does not exist.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

GET/v1/photo-specifications

List photo specifications

Return a cursor-paginated list of available photo specifications. Filter by country or document type to find the `spec_id` you need.

scope photo.specs.read
Request
Query parameters
FieldTypeDescription
limitintegerPage size, 1–100 (default 20).
starting_afterstringA specification id (`spc_…`) to page after.
countrystringISO-3166 alpha-2 filter, e.g. `US`.
document_typestringFilter by document type, e.g. `passport`.
Example request
curl
curl -G "https://partner.svoyager.com/v1/photo-specifications" \
  -H "Authorization: Bearer svoy_sk_test_R2p9Kx7YOURKEYHERExxxxxxx" \
  --data-urlencode "country=US" \
  --data-urlencode "limit=20"
Response · 200
200 · application/json
{
  "object": "list",
  "data": [
    {
      "object": "photo_specification",
      "id": "spc_9wTv2c1Kd8Qe",
      "name": "United States Passport"
    }
  ],
  "has_more": false,
  "next_cursor": null
}
Errors

This endpoint returns only the standard errors below.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

GET/v1/photo-specifications/{id}

Retrieve a photo specification

Fetch a single specification by its stable `spec_id`, including its full requirements.

scope photo.specs.read
Request
Path parameters
FieldTypeDescription
id*stringSpecification id (`spc_…`).
Example request
curl
curl "https://partner.svoyager.com/v1/photo-specifications/spc_9wTv2c1Kd8Qe" \
  -H "Authorization: Bearer svoy_sk_test_R2p9Kx7YOURKEYHERExxxxxxx"
Response · 200
200 · application/json
{
  "object": "photo_specification",
  "id": "spc_9wTv2c1Kd8Qe",
  "name": "United States Passport",
  "country_code": "US",
  "document_type": "passport",
  "physical_size": {
    "width": 51,
    "height": 51,
    "unit": "mm"
  },
  "pixel_size": {
    "width": 600,
    "height": 600
  },
  "dpi": 300,
  "background": {
    "color": "#FFFFFF",
    "name": "white"
  },
  "requirements": {
    "head_height": "25–35 mm (1–1⅜ in)",
    "eye_to_bottom": "28–35 mm (1⅛–1⅜ in)",
    "recency_months": 6,
    "expression": "neutral",
    "color": true,
    "glasses": "not allowed",
    "eyes_and_ears": "both eyes open"
  },
  "updated_at": "2026-07-01T12: 00: 00Z"
}
Errors
StatusCodeWhen
404spec_not_foundNo active specification matches the id.

Plus the standard authentication, permission and rate-limit errors — see Errors & retries.

Send an Idempotency-Key header (a unique value, e.g. a UUID) so a retried request never double-charges or double-processes. It is required on:

  • POST /v1/photo-sessions
  • POST /v1/photo-sessions/{id}/finalize
  • • Same key + same body → the original result is replayed (no second charge, no second render).
  • • Same key + a different body → 409 idempotency_key_reuse.
  • • A retryable failure lets you retry with the same key, body and upload_ref.
  • • Concurrent finalize requests for one session can never create or charge two orders.

A session moves through preview states; an order through finalization states.

Session status
  • preview_ready — preview generated, no issues.
  • preview_has_issues — preview generated with issues.
  • preview_failed — the preview could not be produced.
  • finalized — the session was finalized.
Order status
  • final_ready — the final is available at final_url.
  • processing — the final is being prepared.
  • failed — finalization failed.

The environment is decided by your key prefix — clients can never pass it. Test mode runs the real, watermarked preview so you exercise the true flow, but emulates the final so no live quota or production render is used.

AspectTestLive
livemodefalsetrue
PreviewReal, watermarkedReal, watermarked
FinalSandbox-emulated from the previewReal, no watermark
sandbox_emulatedtrue (on the order)false
watermarked (final)truefalse
Quota consumedTest quota onlyLive quota only

Your plan includes a number of previews and finals per period, metered separately. Creating a session spends one preview; finalizing spends one final. Quota is atomically reserved before processing.

  • When the included quota for a meter is exhausted, the request returns 402 quota_exceeded.
  • 402 is a hard stop — retrying will not succeed until the next period or a plan change.
  • Test and live quotas are fully independent.

Every error uses one envelope. The request_id also appears in the X-Request-Id response header — include it in support requests.

Error envelope
{
  "error": {
    "type": "invalid_request_error",
    "code": "quota_exceeded",
    "message": "Your included final quota for this period is exhausted.",
    "request_id": "req_9wTv2c1Kd8QeR7"
  }
}

Standard errors (all endpoints)

StatusCodeWhen
401invalid_api_keyMissing, malformed, or unknown API key.
401revoked_api_keyThe key was revoked.
401expired_api_keyThe key passed its rotation grace period.
401wrong_environmentA test key was used on live traffic (or vice versa).
403client_disabledThe API client is disabled.
403api_access_revokedAPI access for the account is not active.
403insufficient_scopeThe key lacks the required scope for this endpoint.
429rate_limitedPer-key rate limit exceeded. Honour `Retry-After`.

422 is returned only for a confirmed image problem. 402 means quota is exhausted. 409 is an idempotency or resource conflict.

429 means you hit the per-key rate limit — honour the Retry-After header. For 502/503 and other 5xx, retry with backoff and the same Idempotency-Key.

A preview can return zero or more issues, each a stable { code, message }. Surface these to the end user so they can retake the photo. An unrecognised upstream signal degrades to other.

CodeMessage
face_not_foundNo face was detected in the photo.
uneven_lightingLighting on the face is uneven.
poor_brightnessFace brightness is too dark, too bright, or the photo is low quality.
low_sharpnessThe photo is blurry or not sharp enough.
eyeglasses_not_allowedEyeglasses are not allowed.
sunglasses_not_allowedSunglasses are not allowed.
headwear_not_allowedHats or head coverings are not allowed.
face_mask_not_allowedA face mask is not allowed.
face_covering_foundA face covering was detected.
expression_not_neutralThe expression is not neutral.
crop_bottomNot enough space is visible below the shoulders.
crop_leftNot enough of the left shoulder is visible.
crop_rightNot enough of the right shoulder is visible.
head_turnedThe head is turned too far to the side.
head_tiltedThe head is tilted too far up or down.
not_in_colorThe photo must be in color.
otherThe photo did not meet a requirement.
  • • Previews and finals are served only from SVOYAGER-hosted, authenticated URLs (preview_url / final_url). Access requires your API key and the correct scope — the underlying storage is never exposed publicly.
  • • Uploaded images are private and scoped to your account and environment; upload targets are short-lived and single-use.
  • • Document photos are processed to produce your result and are not shared across accounts.

See SVOYAGER’s Privacy Policy and Partner Agreement & Terms for data-processing, retention and deletion details.