import { Code } from '@astrojs/starlight/components';
import { apiOrigin } from '../../../lib/docs-env';

An identity-verification check stores the images it worked from. `GET /v1/sessions/{id}/idv-image/{artifact}` streams one of them back to you, so your own product can show a person the face on the document instead of sending them to the console.

The response body is the image itself, not JSON.

## The endpoint

<Code
  code={`curl -s -o portrait.jpg \\\n  ${apiOrigin('us')}/v1/sessions/vs_01H.../idv-image/portrait \\\n  -H "Authorization: Bearer ah_sk_us_live_<your-key>"`}
  lang="bash"
/>

- **Scope**: `sessions:read_report`, the same scope that authorizes the PDF report. A key without it gets `403 insufficient_scope`. Mint one that carries it from **Developers → API keys → New API key** in the console, or add it the next time you rotate. See [Create an API key](/developers/api-keys).
- **Response**: `200`, `Content-Type: image/jpeg`, and the raw bytes. Every image this endpoint serves is a JPEG.
- **Caching**: `Cache-Control: no-store, private`. The bytes are personal data about the applicant, so hold them only for as long as your own retention policy allows.
- **No shareable link.** The API never issues a presigned or otherwise public URL for these images. If a browser in your product has to display one, proxy the request through your own authenticated backend: there is no URL you can put in an `<img src>` that works without your secret key.
- **Rate limit**: the `sessions:read_image` class, 120 requests per minute on every plan tier. See [Rate limits](/developers/rate-limits).
- **Region and mode**: send the request to the endpoint that matches your key, as with every other call. A key cannot read a verification from the other region, and a test-mode key cannot read a live-mode verification. See [Modes and regions](/developers/modes-and-regions).

### The `artifact` values

| `artifact` | What it is                                                                                                                                                                                                          |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `portrait` | The photograph of the document holder, cropped from the document image the applicant submitted. It is the smallest image that shows the face printed on the document, so it is usually the one you want to display. |
| `front`    | The front of the identity document, as the applicant submitted it.                                                                                                                                                  |
| `selfie`   | The applicant's selfie or liveness frame, when the workflow ran a face check.                                                                                                                                       |

That is also the order the API lists them in.

**The back of a document is not served here.** It is captured and stored for two-sided documents, and your reviewers see it in the console, but it carries dense printed and machine-readable personal data that a gate or check-in flow has no need for, so it stays inside the console. Asking for `back`, or for any value this API does not recognize, returns `404 resource_not_found` and does not repeat the value you sent.

## Ask which images exist before you fetch

`GET /v1/sessions/{id}` carries an `idv` object for every verification that ran an identity check:

```json
{
  "data": {
    "id": "vs_01H...",
    "status": "completed",
    "idv": {
      "capture_acquisition": "camera",
      "available_images": ["portrait", "front"]
    }
  }
}
```

`available_images` names exactly the artifacts this verification has stored bytes for. Read it first and request only what it lists. That is the difference between one fetch and one fetch per artifact with a `404` for each one that is not there, and it is the single most effective thing you can do to stay under the rate limit.

It never lists the back of a document, even for a two-sided document that captured one, because this API would refuse that request.

`available_images` is a locator, not a grant. It is on the retrieve response for any key with `sessions:read`, and it tells you only whether an image exists. Fetching the bytes still needs `sessions:read_report`.

The `idv` object is on `GET /v1/sessions/{id}` only. It is not on the list endpoint, not on the create response, and absent altogether from a verification that has not run an identity check, so read it defensively rather than assuming it is present.

## When no image is stored: `artifact_not_available`

**Expect this response and handle it as a normal outcome, not a fault.** It is the most common thing this endpoint says about older verifications, and code that treats it as an error will report failures that are not failures.

A verification whose identity check ran before the document-holder portrait was produced has no portrait. The crop exists only if it was asked for at the moment the document was read, so nothing reconstructs it from what is already stored, and every verification recorded before this capability existed answers `artifact_not_available` for `portrait`.

That state is not permanent, though it is not a backfill either. Re-verifying the identity step reads the document again, and that run produces a crop like any other run, so `available_images` begins listing `portrait` for the verification once the new run completes. A re-verification asks the applicant for their document again and is capped per verification, so reach for it when a particular verification needs the crop rather than as a way to populate old records.

The same response covers three other situations: the run produced no usable face on the document, the workflow never reached that step, and the image has already been deleted under data retention or an erasure request.

What to do, in order:

1. **Do not retry.** An image that was never produced will not appear later, and a deleted one does not come back.
2. **Fall back to another artifact** from `available_images`. For confirming a person against their document, `front` shows the same printed photograph, uncropped.
3. **If your workflow needs the portrait specifically** for an older verification, ask the person to verify again. A new run produces one. See [Start a verification](/guides/verification/create-a-verification).

Keep the two 404s apart. They mean opposite things:

| Response                     | Meaning                                                                                                                                                                 | What to do                                                                                                                              |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `404 resource_not_found`     | Your key cannot see this verification id, or the `artifact` is not one this API serves. The response deliberately echoes nothing back, so it looks the same either way. | Check the id, then check that the key's region and mode match the verification. See [Modes and regions](/developers/modes-and-regions). |
| `404 artifact_not_available` | Your key **is** authorized for this verification and the artifact **is** one we serve. There is simply nothing stored for it.                                           | Fall back to another artifact, as above. Full guidance: [`artifact_not_available`](/api/errors/artifact_not_available).                 |

Neither is retryable.

## How the document arrived: `capture_acquisition`

`idv.capture_acquisition` records how the applicant supplied this run's document images. It has three states, and the third one is not what most readers assume.

| Value      | Meaning                                                                                                                                           |
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `"camera"` | The applicant photographed the document with the device camera during the verification.                                                           |
| `"upload"` | The applicant supplied a photo of the document they already had. This is offered only when the workflow template turns on **Allow photo upload**. |
| `null`     | This run was recorded before capture provenance was collected. It is a statement about the age of the run, nothing more.                          |

**`null` does not mean camera, and it does not mean the source was unknown or lost.** Every run the platform records now carries one of the two values, so a `null` you see is an old run and not a gap in a current one. Do not fold `null` into `"camera"` in your own code: that would report a provenance the platform never recorded.

**The value is advisory, and it is not a control.** It originates in the applicant's browser, which means anyone able to tamper with the page can change it, exactly as with any other value a client declares. It is honest context for a person reviewing a verification. It is never an input to the verification outcome, and it never should be one in your integration either:

- **It does not change the verdict.** An uploaded document runs the same document validations, the same authenticity heuristics and the same face comparison as a photographed one, and it can pass automatically. The difference is recorded, not scored. The assurance tradeoff of turning photo upload on is described in [Document upload](/guides/workflows/workflow-templates-check-types#document-upload).
- **Do not gate anything of your own on it.** Treating `"camera"` as proof of a live capture would be trusting a value the browser declared. If you need a stronger signal about a real, present person, that is what the `liveness` biometric mode is for.

If you host the capture flow yourself with the Web SDK rather than using the managed journey, your `submit` callback receives the `acquisition` argument and has to forward it. Drop it and the run is recorded as a camera capture, which is correct until you turn photo upload on and then silently wrong. See [Capture](/developers/sdks/capture).

## Billing and account state

These reads go through the same account-entitlement check as the rest of `/v1`, even though fetching an image is not itself billable. Only a small set of account-state reads is exempt, and this route is not one of them, so evidence you have already collected is not retrievable while the account cannot spend:

- **`402 payment_required`** when the wallet balance is insufficient, or the wallet is locked or absent. Top up or turn auto recharge back on, then retry. See [Wallet balance too low](/guides/billing/payment-required) and [Auto recharge](/guides/billing/auto-recharge).
- **`403 subscription_required`** when the plan was canceled or has passed the payment grace period. Restart the plan, then retry. See [Subscription](/guides/billing/subscription).

If this endpoint sits in an operational path where a billing lapse would be disruptive, keep [auto recharge](/guides/billing/auto-recharge) enabled and set the threshold above your busiest day. That, rather than retry logic, is what keeps the path open.

## Every fetch is recorded

Every request that reaches one of your verifications writes one entry to your organization's activity log, whether it served an image or refused. A request turned away before that point is not logged: an image type we do not serve, a malformed verification id, or an id that is not yours are all refused before the verification is read, so they leave no entry. The entry records the action (`kyc.idv.evidence_exported` on a successful export, `kyc.idv.evidence_export_failed` when an authorized request served nothing), the artifact requested, and the outcome. It carries nothing about the applicant and no image data. See [Activity log](/guides/activity/activity-log).

This is also why there is no shareable URL. The only way to fetch these bytes is with a secret key you issued and can revoke at any time, so an export is never anonymous. See [Rotate or revoke an API key](/developers/api-keys-rotate).

## Related pages

- [Rate limits](/developers/rate-limits) for the `sessions:read_image` class and what to do about a 429.
- [Choosing scopes](/developers/choosing-scopes) for which scopes an integration like this needs.
- [Document upload](/guides/workflows/workflow-templates-check-types#document-upload) for what **Allow photo upload** does and when to turn it on.
- [Capture](/developers/sdks/capture) for the `acquisition` argument on a self-hosted capture flow.
- [API error codes](/api/errors) and the [interactive reference](/reference) for the full wire contract.