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.
POST /photosto get anuploadUrl.PUTthe file to that URL with the returned headers.POST /photos/{id}/finalizeto 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.
Request an upload URL
Section titled “Request an upload URL”| 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 |
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 |
Upload the file
Section titled “Upload the file”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.
curl -sS -X PUT "$UPLOAD_URL" \ -H "Content-Type: image/jpeg" \ --data-binary @emma.jpgThe URL expires at expiresAt. If it lapses, request a new one.
Finalise the upload
Section titled “Finalise the upload”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