Skip to content

Photos

Photo bytes never travel through the API. You ask for a short-lived upload URL, send the file straight to object storage, and then tell CardOffice it has landed.

  1. POST /photos to get an uploadUrl.
  2. PUT the file to that URL with the returned headers.
  3. POST /photos/{id}/finalize to queue processing.

After finalising, the photo is analysed automatically (resolution, focus, orientation, exposure, background, head size, centring, contrast and colour cast) and then waits in the staff review queue. Listen for the photo.approved and photo.rejected webhooks to know the outcome.

POST/photosscope: photos:write
Field Type Notes
cardHolderId uuid, required Must belong to your organisation
contentType enum, required image/jpeg, image/png, image/webp or image/heic
contentLength integer, required Size in bytes. Maximum 50 MB
filename string Optional, for your own reference
Terminal window
curl -sS -X POST "$CARDOFFICE_API/photos" \
-H "Authorization: Bearer $CARDOFFICE_KEY" \
-H "Content-Type: application/json" \
-d '{
"cardHolderId": "0b5e4c1a-6a53-4d3b-9d45-3f6f6b2c9a10",
"contentType": "image/jpeg",
"contentLength": 482113
}'

Returns 201:

{
"data": {
"photoId": "5d1f0a52-7e0b-4f7e-8d6e-2a9c1f0e6b33",
"uploadUrl": "https://storage.example/...signed...",
"headers": { "Content-Type": "image/jpeg" },
"expiresAt": "2027-02-01T02:29:09.000Z"
}
}
Status When
404 not_found The card holder does not exist in your organisation
413 quota_exceeded The upload would exceed the organisation’s photo storage quota
422 validation_failed Unsupported type, or a size over the limit

Send a PUT to uploadUrl with the body set to the raw image bytes. Include every header returned in headers, and make the body exactly contentLength bytes. This request goes to storage, not to the CardOffice API, so it carries no Authorization header.

Terminal window
curl -sS -X PUT "$UPLOAD_URL" \
-H "Content-Type: image/jpeg" \
--data-binary @emma.jpg

The URL expires at expiresAt. If it lapses, request a new one.

POST/photos/{photoId}/finalizescope: photos:write

Tells CardOffice the bytes have landed and queues processing. CardOffice checks storage rather than trusting the client. It returns:

{ "data": { "photoId": "5d1f0a52-7e0b-4f7e-8d6e-2a9c1f0e6b33", "status": "processing" } }

Finalising is idempotent. If the photo is already past the upload stage, the call reports its current status instead of failing, so bulk loaders can retry freely.

© 2026 Secure ID Ltd. All rights reserved.Documentation licence and terms