Skip to content

Main API

Base URL: http://localhost:3002 (dev) · port 8002 (Docker)

JWT audience: main — authenticated routes require a Bearer token unless noted.


Auth

POST /v1/auth/register

Register a new end user account.

Public

Body

json
{ "name": "string", "email": "string", "password": "string" }

Response 201

json
{ "token": "string" }

POST /v1/auth/login

Log in as an end user.

Public

Body { "email": "string", "password": "string" }

Response 200 { "token": "string" }


POST /v1/consent/biometric

Give biometric processing consent. Required before a selfie can be uploaded.

Body

json
{ "consentVersion": "string" }

Response 201


List all consent records for the authenticated user (including revoked).

Response 200 — array of consent rows.


Revoke biometric consent. Triggers a cascade:

  1. Rekognition DeleteFaces for the user's face
  2. Selfie row and R2 object deleted
  3. All auto-tagged photoTags soft-deleted
  4. Consent row marked revokedAt

All steps are wrapped in a DB transaction; R2 and Rekognition side effects are enqueued after commit.

Response 204


QR Join

POST /v1/qr/join

Join a project using a QR token. Creates a projectMembers row.

Body { "token": "string" }

Response 200

json
{ "projectId": "uuid", "projectName": "string" }

Photos

GET /v1/photos

List all photos the authenticated user is tagged in (reviewStatus = 'confirmed').

Pending tags are never included.

Response 200 — array of photo summaries with presigned thumbnail URLs.


GET /v1/photos/:id/download

Get a presigned GET URL (15-minute expiry) for a watermarked photo.

WARNING

Only available after the user has a paid purchase for the photo's project.

Response 200

json
{ "downloadUrl": "string", "expiresInSeconds": 900 }

Projects (user-facing)

GET /v1/projects/:id/packages

List available pricing packages for a project.

Used on the purchase flow to show the user what they can buy.

Response 200 — array of packages with name, price (cents), and photo count.


Purchases

POST /v1/purchases

Create a Stripe PaymentIntent for a project package.

Body

json
{ "projectId": "uuid", "packageId": "uuid" }

Response 201

json
{ "clientSecret": "string", "purchaseId": "uuid" }

The clientSecret is passed to Stripe.js on the frontend to complete the payment.


Stripe Webhook

POST /v1/stripe-webhook

Receives Stripe webhook events. Requires raw request body (no JSON parsing) for HMAC verification.

Handles:

  • payment_intent.succeeded → sets purchase.status = 'paid'
  • payment_intent.payment_failed → sets purchase.status = 'failed'

Acme Photo Platform — Internal Documentation