Skip to content

Passport Photo ​

AI-powered passport and ID photo generator. Two-phase workflow: analyze (face detection + background removal) then generate (crop, resize, and tile for printing).

API Endpoints ​

This tool uses a two-phase flow with separate endpoints for analysis and generation.

Model bundles: background-removal and face-detection


Phase 1: Analyze ​

POST /api/v1/tools/image/passport-photo/analyze

Detects face landmarks and removes the background. Returns landmark data and a preview for the frontend to display a crop preview.

Parameters ​

ParameterTypeRequiredDefaultDescription
filefileYes-Image file (multipart)
clientJobIdstringNo-Optional job ID for progress tracking via SSE

Example Request ​

bash
curl -X POST http://localhost:1349/api/v1/tools/image/passport-photo/analyze \
  -F "file=@headshot.jpg"

Response (200 OK) ​

json
{
  "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "filename": "headshot.jpg",
  "preview": "<base64-encoded PNG>",
  "previewWidth": 800,
  "previewHeight": 1067,
  "landmarks": {
    "leftEye": { "x": 0.42, "y": 0.35 },
    "rightEye": { "x": 0.58, "y": 0.35 },
    "eyeCenter": { "x": 0.50, "y": 0.35 },
    "chin": { "x": 0.50, "y": 0.65 },
    "forehead": { "x": 0.50, "y": 0.22 },
    "crown": { "x": 0.50, "y": 0.18 },
    "nose": { "x": 0.50, "y": 0.48 },
    "faceCenterX": 0.50
  },
  "imageWidth": 2400,
  "imageHeight": 3200
}

Progress (SSE, optional) ​

If clientJobId is provided, progress is streamed (0-30% for face detection, 30-95% for background removal).

Error: No Face Detected (422) ​

json
{
  "error": "No face detected",
  "details": "Could not detect a face in the uploaded image. Please upload a clear, front-facing photo with good lighting."
}

Phase 2: Generate ​

POST /api/v1/tools/image/passport-photo/generate

Crops, resizes, and optionally tiles the photo onto a print sheet. Uses cached images from Phase 1 (no AI re-run).

Parameters (JSON body) ​

ParameterTypeRequiredDefaultDescription
jobIdstringYes-Job ID from Phase 1
filenamestringYes-Original filename from Phase 1
countryCodestringYes-Country code for passport spec (e.g., US, GB, IN)
documentTypestringNo"passport"Document type (from country spec)
bgColorstringNo"#FFFFFF"Background color hex
printLayoutstringNo"none"Print paper layout: none, 4x6, a4
maxFileSizeKbnumberNo0Max file size in KB, where 1 KB = 1000 bytes (0 = no limit)
dpinumberNo300Output DPI (72-1200)
customWidthMmnumberNo-Custom photo width in mm (overrides country spec)
customHeightMmnumberNo-Custom photo height in mm (overrides country spec)
zoomnumberNo1Zoom factor (0.5-3). Values > 1 crop tighter
adjustXnumberNo0Horizontal position adjustment
adjustYnumberNo0Vertical position adjustment
landmarksobjectYes-Landmarks object from Phase 1 response
imageWidthnumberYes-Image width from Phase 1 response
imageHeightnumberYes-Image height from Phase 1 response

Example Request ​

bash
curl -X POST http://localhost:1349/api/v1/tools/image/passport-photo/generate \
  -H "Content-Type: application/json" \
  -d '{
    "jobId": "a1b2c3d4-...",
    "filename": "headshot.jpg",
    "countryCode": "US",
    "documentType": "passport",
    "bgColor": "#FFFFFF",
    "printLayout": "4x6",
    "dpi": 300,
    "zoom": 1,
    "adjustX": 0,
    "adjustY": 0,
    "landmarks": { "leftEye": {"x":0.42,"y":0.35}, "rightEye": {"x":0.58,"y":0.35}, "eyeCenter": {"x":0.50,"y":0.35}, "chin": {"x":0.50,"y":0.65}, "forehead": {"x":0.50,"y":0.22}, "crown": {"x":0.50,"y":0.18}, "nose": {"x":0.50,"y":0.48}, "faceCenterX": 0.50 },
    "imageWidth": 2400,
    "imageHeight": 3200
  }'

Response (200 OK) ​

json
{
  "jobId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "downloadUrl": "/api/v1/download/{jobId}/headshot_passport.jpg",
  "dimensions": {
    "widthMm": 51,
    "heightMm": 51,
    "widthPx": 602,
    "heightPx": 602,
    "dpi": 300
  },
  "spec": {
    "country": "United States",
    "countryCode": "US",
    "documentType": "passport",
    "documentLabel": "Passport"
  },
  "printDownloadUrl": "/api/v1/download/{jobId}/headshot_passport_print_4x6.jpg"
}

Error: Analysis Expired (410) ​

Generate works from the background-removed image that analyze stored for jobId. Stored outputs are cleaned up after a while, so if too much time passes between the two calls, that image is gone. Call analyze again with the same photo and use the jobId it returns.

json
{
  "error": "This photo's analysis has expired. Analyze it again, then generate.",
  "code": "ANALYSIS_EXPIRED"
}

A jobId or filename that can't name a stored file (a path separator, .., an empty string) answers 400.


Base Route ​

POST /api/v1/tools/image/passport-photo

Returns guidance to use the correct sub-endpoint.

json
{
  "error": "Use /api/v1/tools/image/passport-photo/analyze or /generate"
}

Notes ​

  • Requires the background-removal and face-detection model bundles to be installed.
  • Phase 1 runs AI (face landmarks + background removal) and caches results. Phase 2 is pure Sharp image manipulation (fast, no AI needed).
  • Landmarks are returned as normalized coordinates (0-1 range relative to image dimensions).
  • The preview field in the analyze response is a base64-encoded PNG (max 800px wide) for fast display.
  • Country specs include document dimensions, head height ratios, and eye-line positioning based on official passport photo requirements.
  • The printLayout option generates a tiled sheet on 4x6" or A4 paper with 2mm gutters between photos.
  • When maxFileSizeKb is set, the output's JPEG quality is lowered step by step until it fits. The pixel size is fixed by the country spec, so if the photo still doesn't fit at the lowest quality, /generate returns 422 with a message saying so, rather than a file over the limit.