How to keep passport and visa photos in your own storage when you call an ID photo API

A security review of an ID-photo integration usually stalls on one question: after the call returns, where does the portrait sit? The person in the frame is applying for a passport, a visa, or a badge. The data privacy policy describes two storage choices. Teams often pick one in a design document and then call an endpoint that does the other.

This article is that choice, written against the published Passport Photo API. It is for visa-agency platforms, travel apps, photo studios, and HR systems that already know which document they are sizing. Which country code to send is covered in how to map visa photo requirements by country. The camera screen and the retake copy are in how to add passport photo capture to your app. Writing a finished file onto an employee profile is in how to automate employee badge photos. What follows is where the bytes go, and which products cannot avoid a hosted file.

Two storage options, and a range rather than a single expiry

The privacy policy states the practices that a reviewer can rely on without a side letter:

The page does not publish a per-endpoint retention table or a list of subprocessors. If a customer needs a data-processing addendum, or a deletion window narrower than “2 hours to 14 days,” discuss it with support before you put a tighter number in your own privacy notice.

Option 2 is the one to design for when the portrait must live only in your database, your object store, or the HR system of record. Option 1 is the default image URL. “2 hours to 14 days” is a range, not a promise that every signed URL dies at the second hour. Download anything you still need.

Keep the secret, and the portrait, off the client

The phone, the kiosk, or the case-worker browser captures or uploads one color portrait. Your backend encodes it and calls https://api-us.idphotoapp.com or https://api-eu.idphotoapp.com, whichever region the account is configured for. Other regional deployments may be available on request. The sample Python code reads IDPHOTO_API_KEY and IDPHOTO_API_SECRET from the environment and sets a 120-second timeout on the HTTP client.

Do not put the secret in a mobile binary, a frontend bundle, a theme file, or a log line. Do not log the request body. A Base64 portrait is the photograph, and a truncated copy in an access log is still a copy. Turn body capture off in error trackers for these routes. The source still has to be a photograph the checks can use: one person, even light, both shoulders, original color. The common mistakes guide is the list for the capture screen. A file that fails those checks is not worth keeping.

Ask for Base64 on the call that creates the final file

POST /v2/makeIdPhoto is the direct call: one portrait, one specCode, a final JPEG. It accepts outputFormat. The documented values are S3_URL and IMAGE_BASE64. The default is S3_URL. If the policy you sold is “store nothing on the service,” the default is the wrong setting. Send IMAGE_BASE64 on every final-photo call, including the ones a background job makes. A worker that omits the field will silently take the URL path.

A data-URI prefix on imageBase64 is optional here. The Python sample sends the raw Base64 string. Background removal requires a prefix, so do not reuse one encoder blindly.

curl -sS -X POST "https://api-us.idphotoapp.com/v2/makeIdPhoto" \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "YOUR_API_KEY",
    "apiSecret": "YOUR_API_SECRET",
    "imageBase64": "BASE64_IMAGE",
    "specCode": "us-passport",
    "outputFormat": "IMAGE_BASE64"
  }'

A successful response includes photoUuid, specCode, dimensions, an issues array, and idPhotoImageBase64 when Base64 output was requested. URL fields may be null. Decode on the server, write the JPEG to your bucket, and store your object key next to photoUuid. Return a link your own authorization layer can open, not the only copy of the Base64 string.

Read the specification before you describe the file you stored. POST /v2/getIdPhotoSpec for us-passport returns a 2 × 2 in photo, 600 × 600 px, 300 DPI, white background, neutral expression, glasses not allowed. Those numbers belong to that code. The spec code reference documents 150+ specifications and tells you to query POST /v2/listIdPhotoSpecCode. uk-passport and uk-passport2 are both listed, and the reference says those names are not aliases.

An empty issues array means the checks that ran did not report a listed problem. It does not mean a consulate has accepted the picture. You may still receive an image when issues is not empty. Map codes to one sentence with the issue code reference, and ask for a new photo before you nitpick framing. Do not quiet a government-document check with issueSensitivityOverride. Overrides change detection, from VERY_LOW to VERY_HIGH, and they do not change the specification.

The watermarked preview is a hosted URL

A preview before payment is two calls, and it does not match option 2 as cleanly as direct mode. POST /v2/makeIdPhotoWatermark takes the portrait and the spec code. Optional watermarkText accepts only Preview or Aperçu, case-insensitively. The sample response has waterMark: true, a photoUuid, and idPhotoUrl. outputFormat is not a request field.

POST /v2/getIdPhotoNoWatermark takes the key, the secret, photoUuid, and an optional photoSheet. The sample response has waterMark: false and idPhotoUrl. Download that file into your store immediately. The privacy policy’s window for URL output is 2 hours to 14 days. If a temporary URL is outside your policy, use direct mode with IMAGE_BASE64 instead of assuming the retrieval call accepts an output flag it does not list. Store photoUuid on your server. Both calls must use the same regional host. The EU base URL is only for an account configured for the EU region, and the hostname does not change that retention range.

Print sheets and UK photo codes are hosted on purpose

Two products need a URL that someone else can open. Base64-only mode does not replace them. Branch on the document instead of forcing every response through one storage story.

Print sheets

photoSheet.sheetSizes asks for up to 10 layouts, each width*height in pixels. The example in the docs is 1800*1200. The response maps each size to a URL. The print and mailing page says sheet fields come back as URLs even when the single photo is Base64. Download each sheet if the customer will print later. A mailing address, a carrier, and a tracking webhook are not on the request. Confirm postage with support before you sell delivery. Do not print while issues would make the paper unusable. You can request photoSheet on getIdPhotoNoWatermark after the person confirms, so a rejected preview never becomes a sheet.

UK photo codes

An online UK passport application asks the applicant to enter a photo code. That code is a short HTTPS address for a JPEG the supplier hosts. HM Passport Office retrieves the file and checks it. The mechanism is described on the GOV.UK photo code page for suppliers. On this API the field is idPhotoCode, shaped like idfoto.net/P5E9E47P. It is returned for uk-passport and uk-digital-passport. The reference lists uk-driverlicense as optional for a photo code. Do not expect idPhotoCode on us-passport or schengen-visa.

Use uk-passport or uk-digital-passport when the photograph needs to meet GOV.UK photo rules. The reference describes uk-passport as the standard UK passport photo at 35 × 45 mm. Codes from this API stay retrievable for 21 days. That window is our hosting period, not an HM Passport Office rule, so this path cannot be “store nothing.” GOV.UK separately expects the photograph to have been taken in the last month. Confirm the current rule on GOV.UK’s photo guidance. A visa file that must stay in your bucket is a separate IMAGE_BASE64 call. Direct mode can return the code with the JPEG. Watermark mode returns it on getIdPhotoNoWatermark, after confirmation. Show the code and the 21-day window. HM Passport Office still checks a photo that meets GOV.UK photo rules. An empty issues array is not an acceptance decision. Studio counters are covered in the UK photo code workflow, and the product page is the UK passport photo code API.

Supporting calls that already return Base64

Background removal (POST /v2/removeBackground, or POST /v2/removeBackgroundFast) requires a data-URI prefix, outputFormat of PNG or JPEG, and outputType IMAGE_BASE64. The response field is output. PNG keeps transparency. The cutout is not a passport photo until makeIdPhoto applies a spec code, and you can often skip it because the specification already carries a background color. See the background removal reference.

Photo enhancement (POST /v2/photoEnhance) and shadow removal (POST /v2/removeShadow) default to Base64 in output. scaleFactor defaults to 1. enableFaceEnhance defaults to false because it can change small details. Neither call selects a document size. For a US passport, the US Department of State says not to upload a photo created or edited with artificial intelligence or other digital tools. A UK passport photograph that meets GOV.UK photo rules is unedited. Ask for a new capture instead. A corrected portrait can still fit a badge whose policy allows it, which is what the AI Photo Editing API is for.

Face Match (POST /v2/faceMatch) returns isMatch and similarity, not an image. Send both portraits from your server and do not log them. Access is enabled separately. Omit similarityThreshold to use the integration default. Scores are not comparable with another vendor’s, and a match is not a legal identity decision or an acceptance by a consulate. The reference does not include liveness, document OCR, or anti-spoofing. The Face Match reference separates a non-match from authentication failures, bad image data, a missing face, and timeouts.

A directory is a queue of single calls

A roster or a drop folder is the same makeIdPhoto call repeated. There is no bulk-upload method. Read POST /v2/queryAccountStatus first. The quota fields in the docs are an example payload, not a plan size. Send IMAGE_BASE64 on each call if the run should not leave URLs behind. On timeout, do not blindly retry. The API does not push objects into your cloud bucket. The batch ID photo page describes the loop.

What belongs in your database

Once the bytes are yours, retention is your policy. A useful row has:

Do not store the API secret, and do not store the Base64 string in a log, an analytics event, or a support ticket. Serve the photo from your bucket through your own authorization check, and expire it on the schedule your privacy notice states. HTTP failures are separate from issue codes: bad credentials, malformed JSON, invalid Base64, an unknown spec, a processing failure, a timeout. ISSUE_PHOTO_SPEC_NOT_EXIST is a configuration bug. A timeout is not proof the photo was skipped.

A sequence you can hand to engineering

  1. Create a key in the dashboard. Note the US or EU host. Confirm Face Match separately if you will compare two portraits.
  2. Government and visa files that must stay in your systems use IMAGE_BASE64 on POST /v2/makeIdPhoto. A UK passport photo code uses the hosted path, for a photograph that meets GOV.UK photo rules, and you tell the applicant about the 21-day window.
  3. Map the document to a specCode from getIdPhotoSpec. Refresh the cache when you add a custom badge size.
  4. Accept one color portrait on your server. Do not log it.
  5. Call makeIdPhoto. Decode idPhotoImageBase64 into your object store before you respond to the client.
  6. If issues is not empty, map the codes to one retake. For a US or UK passport, do not offer an edited face as the repair.
  7. Persist photoUuid, the spec, the issue list, and your object key.
  8. Download any photoSheet URL immediately. Talk to support before you promise postage.
  9. Watch queryAccountStatus so a quota failure pages an operator.

Headshot generation is a different endpoint, POST /api/generate-portrait, and those files are not document photos. The AI headshot API is for directory portraits. If a reviewer asks for a subprocessors list, a shorter deletion window, or a residency claim the privacy policy does not state, ask support before you write it down.

FAQ

Does IMAGE_BASE64 mean the portrait is not stored on the service?

That is how the privacy policy describes option 2. Send outputFormat IMAGE_BASE64 on POST /v2/makeIdPhoto. The default is S3_URL, temporary storage with a stated retention of 2 hours to 14 days.

Can the watermarked preview return Base64?

The documented watermark request does not include outputFormat, and the samples return idPhotoUrl. The same is true of POST /v2/getIdPhotoNoWatermark. If a temporary URL is outside your policy, use direct mode with Base64.

How long does a UK photo code stay online?

Photo codes from this API remain retrievable for 21 days. That is our hosting period. GOV.UK’s rule for how recently the photograph was taken is separate. Confirm it on GOV.UK. A photo from uk-passport or uk-digital-passport meets GOV.UK photo rules. HM Passport Office still checks it. A code is not an acceptance decision.

Should I enhance the photo before I store it?

Enhancement and shadow removal return Base64, and they do not apply a document specification. Face enhancement can change small details. US passport guidance rejects photos created or edited with artificial intelligence or other digital tools. A UK passport photo that meets GOV.UK photo rules is unedited. Store the file from makeIdPhoto, and retake when issue codes say the capture is the problem.

Where should the file live after the call?

In your object bucket, your case file, or the employee profile. Persist photoUuid and the spec code beside your object key. Download print-sheet URLs if you requested them.