AI Photos of You

AI Photos of You Studio API

The API used by Studio to manage profiles, private photos, shoots, and credits.

OpenAPI specification (JSON) · API catalog · Service health

Authentication and access

Requests use the current site’s /api base path. Sign in through the application to establish a Better Auth session cookie. There are no API keys or bearer tokens. Protected endpoints require that session, and mutations require an Origin header matching the application origin. This is a session-based application API, not an unauthenticated generation service.

GET /api/health and GET /api/config are public. GET /api/me returns the signed-in user, credits, and payment-processing state, or null when signed out. Health reports service liveness; it does not verify generation, payment, email delivery, or photo quality.

Reference and generated photos are private. Resource operations check ownership. Authentication routes under /api/auth/ are handled by Better Auth. POST /api/webhooks/polar is a signature-verified payment-provider callback, not a customer endpoint.

Example requests

Public requests do not need credentials:

curl -i https://aiphotosofyou.com/api/health \
  -H 'API-Version: 1'
curl https://aiphotosofyou.com/api/config

After signing in, an authorized browser agent can read the account's photos in the same-origin browser session. Session cookies are credentials: do not share them or include them in logs. This read does not spend credits:

const response = await fetch('/api/photos?limit=10', {
  credentials: 'same-origin',
  headers: { 'API-Version': '1' }
});
const page = await response.json();

To create a profile with the owner's permission, use the same signed-in browser context. The browser supplies the matching Origin header automatically:

const response = await fetch('/api/subjects', {
  method: 'POST',
  credentials: 'same-origin',
  headers: { 'Content-Type': 'application/json', 'API-Version': '1' },
  body: JSON.stringify({ name: 'My profile' })
});

Versioning and deprecation

Send API-Version: 1 to select the current major version. Existing clients that omit this header use version 1. Responses identify the version with API-Version: 1; unsupported values return HTTP 400 with code unsupported_api_version. This contract applies to the Studio endpoints listed below, excluding Better Auth routes and Polar webhooks.

Breaking contract changes require a new major version. Clients should tolerate additive fields and unknown error codes. Planned retirement will be announced here and signaled with the Deprecation header (a Structured Field date, such as @1798761600) and the Sunset header (an HTTP date) when dates are scheduled. No version retirement is currently scheduled.

Photo workflow

  1. Get or create your saved reference set with POST /api/subjects and upload reference photos with POST /api/uploads. Uploads use multipart fields subjectId and file; accepted files are JPEG, PNG, or WebP, at most 2 MiB each, with up to five references per account across devices. Repeated subject creation returns the existing set with 200; the first creation returns 201.
  2. Obtain explicit consent before submitting a shoot. Generation requires consent: true, reference photos, and sufficient credits. Each requested output reserves two credits. Paid generation must be authorized by the account owner.
  3. Submit POST /api/shoots or POST /api/shoots/batch with a new UUID. Reuse that UUID when retrying the same submission. A successful response acknowledges the job; it does not mean the photos are ready.
  4. Poll GET /api/shoots for progress and GET /api/photos for available outputs. A shoot can have partial failures; successful photos remain available and failed outputs receive credit refunds. Output quality and likeness require human review.
  5. Read image bytes at GET /api/photos/{id}/file. Add ?download to request an attachment.

Single shoots accept 1–12 outputs with ratio 2:3, 1:1, or 3:2. Batches accept up to 48 distinct style or curated pack shot IDs and 48 total outputs. Each batch item can include a prompt of up to 2,000 characters; the shared batch prompt follows those directions for every shot. At most two shoots or batches can be active per account. Custom generation and editing require a nonblank prompt. Editing and enhancement use an owned generated photo as sourceId; enhancement does not promise exact dimensions or pixel preservation.

Endpoints

All paths below are relative to /api. The OpenAPI specification includes request fields and response descriptions.

Method and path Purpose
GET /health
GET /config
GET /me
Liveness, public configuration, and current session.
GET, POST /subjects
DELETE /subjects/{id}
List, create, or delete profiles. Delete associated shoots before deleting a profile.
POST /uploads Upload a private reference photo.
GET /photos
GET /photos/{id}/file
PATCH, DELETE /photos/{id}
List photos, retrieve bytes, set a favorite, or delete a photo.
GET, POST /shoots
POST /shoots/batch
DELETE /shoots/{id}
List progress, create paid generation/edit/enhancement jobs, create a style batch, or delete a finished shoot and its photos.
POST /checkout
GET /checkout/{id}
POST /billing
GET /credits
Create a hosted checkout, read payment status, open the billing portal, or read the latest 100 ledger entries.
POST /attribution Save first account attribution with the user’s measurement consent.
DELETE /account Delete the account and media with {"confirmation":"DELETE"}. Requires no active shoots. Required financial records are retained.

Pagination, payments, and failures

Photo and shoot lists return {"items": [...], "nextCursor": ...}. Use limit (1–100, default 100) and pass the returned nextCursor as the next request’s cursor. A null cursor marks the final page.

A checkout redirect is not proof of payment. Credit grants depend on verified payment reconciliation. Check /checkout/{id} and the current balance before proceeding.

Application errors return JSON with an error message and a machine-readable code, for example {"error":"Please sign in to continue.","code":"unauthenticated"}. Branch on the code and HTTP status, rather than parsing the message. The OpenAPI ApiError schema covers 4xx and 5xx responses. Common statuses are 400 for invalid input, 401 for missing authentication, 403 for an invalid origin, 404 for missing or unowned resources, 409 for credit/limit/active-work conflicts, 413 for oversized uploads, 429 for rate limits, and 503 for unavailable integrations. Deleting photos, shoots, or accounts can be blocked while active jobs need them.

Rate limits

Profile creation allows 120 requests per hour, reference uploads 600, checkout creation 20, and billing portal creation 30. Each quota is per account. Responses after a quota check carry RateLimit-Policy (for example "profile";q=120;w=3600) and RateLimit (for example "profile";r=119;t=3600). The values describe requests allowed per window, requests remaining, and seconds until reset. These Structured Fields follow the HTTPAPI rate-limit draft, which is not a published RFC. Other endpoints do not advertise an account request quota.

HTTP 429 responses include Retry-After in seconds. Wait at least that long before retrying; successful responses do not guarantee later requests will be accepted. Requests rejected before a quota check may omit quota headers. Separate profile, upload, credit, and active-shoot limits can return 409 and are not hourly request quotas.