---
title: "Verify a signed receipt"
method: POST
path: "/v1/verify"
tags: ["Receipts"]
---

# Verify a signed receipt

`POST /v1/verify`

## Request

- Base URL: `https://api.phototology.com`
- URL: `https://api.phototology.com/v1/verify`
- Auth: none required

Checks a receipt from any analyze or lookup response. No API key, free, limited to 60 requests per minute per IP. It never reads our database, so anyone can reproduce the answer with the public key set at `/.well-known/phototology-keys.json`.

- `signature`: the detached ES256 JWS over the RFC 8785 canonical payload, checked against the key set. `unknown_key` when the key id is not published.
- `timestamp`: the RFC 3161 token, checked against SHA-256 of the signature and the pinned time-stamping authority roots, and the receipt's `timestamp.time` checked against the time inside the token (to the second; a different or missing time is `invalid`). `pending` and `unavailable` pass through from the receipt.
- `timestampedAt`: the time read from the token when the timestamp is valid, else null.
- `valid`: true when the signature is valid and the timestamp is valid, pending or unavailable.

A sandbox receipt (`pt_test_` keys) reads `{ valid: true, livemode: false, keyId: "sandbox" }`: it proves the integration works and nothing about any photo. A valid signature proves we issued the receipt; `payload.registry.url` says whether we kept the analysis, and answers only for the API key of the account the receipt was issued to (Bearer required).

## Request body

- object
  - `receipt` object, required
    - `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

## Response `200`

The verification result.

- object
  - `valid` boolean, required
  - `livemode` boolean, required
  - `keyId` string, required
  - `signature` 'valid' | 'invalid' | 'unknown_key', required
  - `timestamp` 'valid' | 'invalid' | 'pending' | 'unavailable' | 'untrusted_root', required
  - `timestampedAt` string, nullable, required
  - `issuedAt` string, required
  - `checkedAt` string, required

## Other responses

- `400`: The body has no receipt, or its fields have the wrong types.
- `429`: More than 60 requests in a minute from this IP.

---

[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)
