---
title: "Analyze a photo"
method: POST
path: "/v1/analyze"
tags: ["Analysis"]
---

# Analyze a photo

`POST /v1/analyze`

## Request

- Base URL: `https://api.phototology.com`
- URL: `https://api.phototology.com/v1/analyze`
- Auth: HTTP bearer

Send one or more images and receive structured AI analysis. Pick a curated **stack** for a domain-specific workflow, compose your own **custom stack** from individual lenses, or augment a curated stack with extra lenses.

### Image input
Provide a single image via `imageUrl` or `imageBase64`, or multiple images via the `images` array (max 50). Do not mix single and multi-image inputs.

### Stack and lens selection
- `preset`: pick a curated stack by name (e.g. `full-analysis`, `vehicle-condition`). The field is named `preset` on the wire for back-compat; the concept is a stack.
- `modules`: pass an explicit lens list to build a custom stack (overrides `preset`). The field is named `modules` on the wire for back-compat; the concept is lenses.
- `modulesAdd` / `modulesRemove`: augment a curated stack with extra lenses, or strip a lens you do not need.

### Delta billing
Repeat calls on the same image hit the per-user-per-photo lens projection. Already-analyzed lenses are served from the projection at zero cost; only newly-requested lenses are billed and sent to the LLM. Pass `refresh: true` to bypass the cache and re-bill every requested lens. A full cache hit returns `meta.cacheHit: true` and `usage.creditsCharged: 0`.

### Test sandbox
Requests with a `pt_test_` key return fixture data instantly. No AI call is made, no usage is billed. Use this for development and CI.

## Request body

- object
  - `imageUrl` string, uri
  - `imageBase64` string
  - `images` object[]
    - `url` string, uri
    - `base64` string
  - `preset` string
  - `modules` string[]
  - `modulesAdd` string[]
  - `modulesRemove` string[]
  - `moduleOptions` object
  - `context` object
    - `knownPeople` object[]
      - `name` string, required
      - `birthYear` integer
      - `deathYear` integer
      - `role` string
    - `vehicle` object
      - `vin` string
      - `mileage` integer
      - `year` integer
      - `make` string
      - `model` string
    - `customInstructions` string
  - `options` object
    - `includeEmbedding` boolean
    - `includeFingerprint` boolean
  - `refresh` boolean

## Response `200`

Analysis completed successfully. The `output` object contains module-specific results keyed by module name. On a registry cache hit, `meta.cacheHit` is `true` and `usage.creditsCharged` is `0`.

**Content safety block.** Moderation runs on every photo before analysis. If an image is blocked, this endpoint still returns **200** (deliberately not a 4xx) with `output.moderation.safe: false`, a human-readable `output.moderation.reason`, `meta.safetyBlockInfo`, `usage.creditsCharged: 0`, and `Cache-Control: no-store, private`. Check `output.moderation.safe` before trusting any analysis fields. See the `moderation-block` example.

- object
  - `id` string, required
  - `object` 'analysis', required
  - `livemode` boolean, required
  - `schemaVersion` string, required
  - `createdAt` string, required
  - `output` object, required
  - `outputSchema` 'photo' | 'vehicle', required
  - `usage` object, required
    - `inputTokens` number, required
    - `outputTokens` number, required
    - `totalTokens` number, required
    - `modulesUsed` string[], required
    - `creditsCharged` integer
    - `creditsSaved` integer
    - `usd` object
      - `charged` string, required
      - `saved` string, required
  - `warnings` string[], required
  - `meta` object, required
    - `processingTimeMs` number, required
    - `provider` string, required
    - `promptHash` string, nullable, required
    - `requestId` string, required
    - `cacheHit` boolean
    - `ai_generated` boolean, required
    - `model` string, required
    - `vendor` 'google' | 'openai' | 'anthropic', required
  - `embedding` number[]
  - `fingerprint` object
    - `pHash` string, required
    - `dHash` string, required
    - `sha256` string, required
    - `pixelsSha256` string
    - `pdq` object
      - `hash` string, required
      - `quality` integer, required
      - `variants` string[], required
    - `iscc` string
  - `receipts` object[]
    - `payload` object, required
      - `type` 'phototology.receipt', required
      - `version` 1, required
      - `id` string, required
      - `livemode` boolean, required
      - `issuer` 'https://phototology.com', required
      - `keyId` string, required
      - `subject` object, required
        - `bytesSha256` string, required
        - `pixelsSha256` string
        - `pHash` string, required
        - `dHash` string, required
        - `pdq` object
          - `hash` string, required
          - `quality` number, required
        - `iscc` string
      - `analysis` object, required
        - `requestId` string, required
        - `lenses` object[], required
          - `id` string, required
          - `version` string, required
          - `outputSha256` string, required
        - `provider` string, required
        - `model` string, nullable, required
        - `modelVersion` string, nullable, required
        - `outputSha256` string, required
        - `analyzedAt` string, required
      - `firstSeenAt` string, required
      - `credentials` object, required
        - `status` string, required
      - `registry` object, required
        - `url` string, required
    - `signature` string, required
    - `timestamp` object, required
      - `status` 'ok' | 'pending' | 'unavailable', required
      - `tsa` string
      - `token` string
      - `time` string
  - `credentials` object
    - `status` 'none' | 'invalid' | 'valid' | 'trusted' | 'unread', required
    - `aiGenerated` boolean, nullable, required
    - `signer` object, nullable, required
      - `name` string, required
      - `issuer` string, required
    - `captureDevice` string, nullable, required
    - `signedAt` string, nullable, required
    - `trustList` 'c2pa' | 'legacy', nullable, required
    - `assertions` string[], required
    - `note` string, required

## Other responses

- `400`: Request validation failed. Common causes: no image provided, both single and multi-image inputs used, invalid URL format, base64 string too large (max 67 MB), unknown module name.
- `401`: Missing or invalid API key. Ensure your `Authorization` header is `Bearer pt_live_...` or `Bearer pt_test_...`.
- `402`: Insufficient credits. Response body includes a `credits` object with the per-pool breakdown (`needed`, `community`, `purchased`, `total`) plus `resetsInDays` (relative integer; never an absolute timestamp). The route also sets `Cache-Control: no-store, private` so intermediaries do not cache the credit breakdown.
- `422`: Image fetch failed or image payload invalid (e.g. unreachable URL, unsupported format, exceeds size cap).
- `429`: Rate limit exceeded. All authenticated keys share a single 600 req/min cap. Anonymous `pt_test_*` keys (no user account) are capped at 2 req/min plus 10 req/day per IP for anti-abuse. Retry after the `Retry-After` header value (seconds).
- `502`: Upstream AI provider unavailable or returned an error. Retryable. The platform automatically falls back across providers; a 502 means every configured provider failed.

---

[API](https://skmtc.dev/phototology/apis/phototology-api.md) · [All operations](https://skmtc.dev/phototology/apis/phototology-api/llms.txt) · [OpenAPI document](https://skmtc.dev/phototology/apis/phototology-api/revisions/3cb0fc707b63?raw)
