Skip to content

Media API

Base URL: http://localhost:3003 (dev) · port 8003 (Docker)

Two JWT audiences are accepted depending on the caller:

  • media — end users (selfie upload)
  • backoffice — operators and photographers (profile pictures, photo uploads)

All file uploads use presigned PUT URLs — the server never proxies file bytes. The client receives a URL and PUTs the file directly to Cloudflare R2.


Selfie Upload

POST /v1/selfie/upload-url

Request a presigned PUT URL to upload a selfie.

Audience media · Role user

Requires consent

This endpoint is guarded by requireConsent('biometric_processing'). The request is rejected with 403 if the user has no active consent row.

Response 201

json
{
  "selfieId": "uuid",
  "uploadUrl": "string",
  "key": "string",
  "expiresInSeconds": 600
}

After uploading to uploadUrl, the process_selfie job is enqueued automatically, which indexes the face to AWS Rekognition.


Photo Upload

POST /v1/photos/upload-url

Request a presigned PUT URL to upload a project photo.

Audience media · Role photographer

Body { "projectId": "uuid" }

Response 201

json
{
  "photoId": "uuid",
  "uploadUrl": "string",
  "key": "string",
  "expiresInSeconds": 600
}

On upload the following jobs are enqueued:

  • generate_thumbnail — Sharp resize to 400px and 800px
  • generate_watermark — Sharp composite with text overlay, EXIF stripped
  • recognize_faces — Rekognition face search against the project collection

POST /v1/photographers/me/photos/upload-url

Same as above but accepts a backoffice-audience token. Used by the backoffice-web photographer dashboard.

Audience backoffice · Role photographer


Operator Media

POST /v1/operators/me/profile-picture/upload-url

Request a presigned PUT URL for an operator profile picture.

Audience backoffice · Role operator

Response 200

json
{ "uploadUrl": "string", "fileUrl": "string" }

fileUrl is the permanent R2 path to store on the operator profile after upload.


Storage Key Conventions

selfies/{userId}/{timestamp}.jpg
photos/{projectId}/{photoId}/original.jpg
photos/{projectId}/{photoId}/thumb-400.jpg
photos/{projectId}/{photoId}/thumb-800.jpg
photos/{projectId}/{photoId}/watermarked.jpg
operators/{operatorId}/profile.jpg

Never return public R2 URLs

All download URLs served to clients must be presigned GET URLs (15-minute expiry). The R2_PUBLIC_URL is used for internal key construction only.

Acme Photo Platform — Internal Documentation