# PortraitDock API

The OpenAPI document at `/openapi.public.json` is authoritative for paths, fields, response shapes and limits.

## Access and safe use

The public API is mounted at `/api/public/v1` and uses `X-API-Key`. Create a business key in the authenticated browser API at `POST /api/v1/business-api/keys` with `{ "name": "PortraitDock", "readOnly": false }`; this existing endpoint returns the token once. The account must be active and the key needs `images:read` and `images:write`. Keep the key on a trusted server and never put it in a browser bundle, URL, repository, analytics event or log.

```sh
BASE=https://portraitdock.com/api/public/v1
KEY=ilo_sk_replace_me
curl -H "X-API-Key: $KEY" "$BASE/portrait-presets"
curl -X POST -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"label":"Team square","preset":{"background":{"kind":"solid","color":"#e8e4dc"},"output":"square"}}' "$BASE/portrait-presets"
curl -X POST -H "X-API-Key: $KEY" -H 'Idempotency-Key: batch-2026-001' -H 'Content-Type: application/json' \
  -d '{"label":"September team","presetVersionId":"PRESET_UUID"}' "$BASE/portrait-batches"
curl -X POST -H "X-API-Key: $KEY" -H 'Idempotency-Key: employee-001-v1' \
  -F employeeReference=employee-001 -F photo=@person.jpg "$BASE/portrait-batches/BATCH_UUID/items"
curl -X POST -H "X-API-Key: $KEY" "$BASE/portrait-batches/BATCH_UUID/process"
curl -H "X-API-Key: $KEY" "$BASE/portrait-batches/BATCH_UUID"
```

Poll the batch until individual portraits are `ready` or terminal. Review a ready item, then enqueue and download the approved ZIP export. Do not reuse an idempotency key with changed content. Use bounded backoff for `429` and transient `5xx` responses. Batch creation and individual-photo upload accept `Idempotency-Key`: retry either only with the same payload and key. For another interrupted write, read the current batch or portrait state before deciding whether to repeat it.

```sh
curl -X POST -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"decision":"approved"}' "$BASE/portraits/PORTRAIT_UUID/review"
curl -X POST -H "X-API-Key: $KEY" "$BASE/portrait-batches/BATCH_UUID/exports"
# Poll batch detail until exportManifest is present and its reviewVersion is current.
curl -H "X-API-Key: $KEY" "$BASE/portrait-batches/BATCH_UUID/exports/content" -o portraits.zip
```

Each preview, source or download URL returned to the public API requires the API key. An image must be approved before its full exports become available. Browser-session download URLs are separate and use the signed-in cookie.

If an item is `needs_input` and `canCorrect: true`, inspect `/portraits/PORTRAIT_UUID/source/content` and POST `{ "corrections": { "headTop": 0.12, "chin": 0.58, "faceCenterX": 0.5 } }` to `/portraits/PORTRAIT_UUID/corrections`. Coordinates measure the oriented **source**. The immutable company preset specifies **output** placement. This reserves one credit and reuses cached source/mask; it does not call the image provider again. Failed preparation releases its reservation. If `canCorrect` is false, provide a replacement source in a new batch using the same preset.

A batch accepts 100 images of at most 20 MiB/24 MP each. Upload at most three concurrently. ZIP output currently has a 256 MiB aggregate limit; per-image downloads remain available for larger collections. Before launch, verify representative 100-photo archive sizes and throughput; this limit is not a claim that every possible lossless 100-photo ZIP fits.

### Photographic studio presets

`GET /api/v1/portraits/config` advertises the current fixed studio IDs and their public thumbnail URLs. The photographic IDs are `warm-paper-photo-v1`, `cool-slate-photo-v1` and `neutral-linen-photo-v1`; submit them as `{ "kind": "studio", "id": "warm-paper-photo-v1" }` in either consumer or business presets. These same background images are used in final rendering. URLs/paths supplied by callers are not accepted as backgrounds. Existing `warm-studio`, `cool-studio` and `neutral-studio` values remain accepted and keep their original gradient appearance.

### Reusable logos and the default team look

Upload your logo once, use the returned `logo.id` in a preset, and select that preset as the workspace default. Logo reads require `images:read`; uploads and default changes require `images:write`. PNG, JPEG and WebP are accepted up to 5 MiB, with a 16-million-pixel decode limit. The server normalizes the image to a private PNG (maximum 1024 × 1024 and 1 MiB), preserving transparency and aspect ratio. SVG and animated files are not accepted.

```sh
curl -X POST -H "X-API-Key: $KEY" \
  -F logo=@company-logo.png "$BASE/portrait-logos"
# Use the returned logo.id as LOGO_UUID below.
curl -X POST -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"label":"Our team","logoAssetId":"LOGO_UUID","preset":{"output":"square","background":{"kind":"studio","id":"warm-paper-photo-v1"}}}' \
  "$BASE/portrait-presets"
# Use the returned preset.id as PRESET_UUID below.
curl -X PUT -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"presetVersionId":"PRESET_UUID"}' "$BASE/portrait-default-preset"
curl -H "X-API-Key: $KEY" "$BASE/portrait-default-preset"

# New batches now need only a label: background, logo and format are already set.
curl -X POST -H "X-API-Key: $KEY" -H 'Idempotency-Key: team-next-001' \
  -H 'Content-Type: application/json' -d '{"label":"New team members"}' \
  "$BASE/portrait-batches"
```

`GET /portrait-logos` lists reusable logos; `GET /portrait-logos/LOGO_UUID/content` returns the private normalized PNG. A preset or logo from another account is rejected. The logo is placed bottom-right, no wider than 22% of output width or taller than 8% of output height, with a 3% inset based on the shorter dimension. Small logos are not enlarged. PNG/JPEG downloads and scaled previews use the same branded output.

To change only one batch's background, add `backgroundOverride` using the same background shape as a preset. The saved default, logo and output format remain intact:

```json
{
  "label": "Conference speakers",
  "backgroundOverride": { "kind": "solid", "color": "#DCE6DE" }
}
```

You may still pass `presetVersionId` explicitly. The resolved preset and logo are fixed when the batch is created; subsequent edits/default changes do not alter it. Reusing a successful batch's idempotency key and identical request returns that original batch, including its original default resolution. If no default is configured and no preset is supplied, the API returns `409 PORTRAIT_DEFAULT_PRESET_REQUIRED`.
