> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Download User History PDF

> Download one PDF bundling every reportable verification session of a user, keyed by vendor_data, for audits, SARs and periodic compliance exports.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

<AgentPromptAccordion
  title="User History PDF Prompt"
  prompt={`Goal: download one audit-ready PDF containing the full verification history of a Didit user.

Endpoint: GET https://verification.didit.me/v3/users/{vendor_data}/generate-pdf/
Auth header: x-api-key: YOUR_API_KEY

Path param:
- vendor_data (string, REQUIRED) - your own identifier for the user, the same value you pass when creating sessions. Free-form string, NOT a UUID. URL-encode it; it cannot contain "/".

curl - save the PDF to disk:
curl --fail 'https://verification.didit.me/v3/users/user-abc-123/generate-pdf/' \\
-H 'x-api-key: YOUR_API_KEY' \\
--output user-history.pdf

Response:
- Body is binary (application/pdf). DO NOT parse as JSON.
- Content-Disposition: attachment; filename=user_{didit_internal_id}.pdf
- Content: a cover page (profile summary, feature status, session index) followed by the standard report of every included session, oldest first. Each session report is the same document GET /v3/session/{session_id}/generate-pdf/ returns.
- Only sessions in "Approved", "Declined", "In Review" or "Kyc Expired" are included, capped at the 20 most recent. The cover states when older sessions were left out.
- Identical to the Console export at User detail → Actions → Download PDF. White-label logo and privacy-policy URL apply when enabled.
- Rendering is synchronous and takes a few seconds per session: use a read timeout of at least 120s and stream the body to disk.

Failure modes (envelope {"detail": "..."}):
- 401 - no credentials supplied.
- 403 - invalid or revoked API key, a Console user token instead of an API key, OR the user exists but has no session in a reportable status ("This user has no sessions in review, declined, approved or kyc expired to report.").
- 404 - {"detail": "User not found."} - no user with that vendor_data in the application behind the API key. A key can never export a user of another application.
- 429 - shares the PDF rate limit with the per-session endpoint (50 requests per minute per credential). Wait Retry-After seconds.

Trailing slash: the canonical route ends with "/". Call it as written above.

When to call:
- Periodic compliance exports of a customer's whole KYC history.
- SAR / regulator attachments that must cover every verification of one person.
- Pulling a user's history into your own back office without a Console login.

Related: GET /v3/session/{session_id}/generate-pdf/ for a single session; GET /v3/sessions/?vendor_data=... for the JSON list of sessions.

For end-to-end Didit integration, paste in the full prompt at /integration/integration-prompt.`}
/>

## Overview

Returns one PDF with the full verification history of a [User entity](/entities/users/overview), keyed by `vendor_data`.
It is the same document the Console produces from **User detail → Actions → Download PDF**, so compliance teams can automate exports instead of clicking once per user.

The report contains:

* **A cover page** with the profile summary (name, date of birth, issuing states, approved emails and phones), the latest status of every feature, the session counters and an index of the sessions included.
* **One full session report per included session**, in chronological order (oldest first). Each one is the same report [Generate PDF](/sessions-api/generate-pdf) returns for that session on its own.

## Eligible sessions and cap

Only User Verification (KYC) sessions in `Approved`, `Declined`, `In Review` or `Kyc Expired` are included. Sessions in any other status (`Not Started`, `In Progress`, `Abandoned`, `Expired`, …) are skipped.

The report is capped at the **20 most recent** reportable sessions. When a user has more, the newest 20 are included and the cover page states how many older sessions were left out. Use [List sessions](/sessions-api/list-sessions) filtered by `vendor_data` when you need the complete list as JSON.

## Response

The body is the PDF itself (`application/pdf`), not a JSON wrapper or a download URL. The `Content-Disposition` header is `attachment; filename=user_<didit_internal_id>.pdf`, where `didit_internal_id` is the user's stable Didit identifier.

## White-label

When white-label customization is enabled on your application, the cover and every session report carry your logo and privacy-policy URL instead of Didit branding, exactly like the per-session endpoint. Configure it at **Console → Customization**.

## Caching and latency

Nothing is cached: every call re-renders the report from the current data, so a report requested after a manual review reflects the reviewer's decision. Two calls can produce byte-different files; archive the downloaded file when you need an immutable copy.

Rendering is synchronous and downloads every stored image of every included session, so a user with many media-heavy sessions can take tens of seconds. Use a generous client read timeout (120 s recommended) and stream the body to disk. Generation stops at a server-side time budget rather than running until your client gives up: if that budget is reached, the report still returns a valid PDF with the most recent sessions and the cover states how many were omitted.

## Trailing slash

The canonical route ends with a trailing slash (`…/generate-pdf/`). A request without it is served directly with the same response: there is no `301` redirect, so no client has to follow redirects and `curl` does not need `-L`. Use the slashed URL as in the samples; it is the form the OpenAPI spec and your logs show.

## Errors

| Status | When                                                                                                                                                                | `detail`                                                                                 |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `401`  | No `x-api-key` header (and no `Authorization: Bearer` token).                                                                                                       | `You must be authenticated with a valid access token to access this endpoint.`           |
| `403`  | Invalid or revoked API key, or a Console user token instead of an API key.                                                                                          | `You do not have permission to perform this action.`                                     |
| `403`  | The user exists but none of its sessions is in a reportable status.                                                                                                 | `This user has no sessions in review, declined, approved or kyc expired to report.`      |
| `404`  | No user with this `vendor_data` in the application behind the API key, including users that belong to another application.                                          | `User not found.`                                                                        |
| `429`  | More than 50 PDF generations per minute from the same credential. The budget is shared with [Generate PDF](/sessions-api/generate-pdf). Wait `Retry-After` seconds. | `Session PDF generation rate limit exceeded. You can make up to 50 requests per minute.` |

## Examples

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl --fail 'https://verification.didit.me/v3/users/user-abc-123/generate-pdf/' \
      -H 'x-api-key: YOUR_API_KEY' \
      --output user-history.pdf
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import requests

    response = requests.get(
        "https://verification.didit.me/v3/users/user-abc-123/generate-pdf/",
        headers={"x-api-key": "YOUR_API_KEY"},
        stream=True,
        timeout=120,
    )
    response.raise_for_status()
    with open("user-history.pdf", "wb") as fh:
        for chunk in response.iter_content(chunk_size=8192):
            fh.write(chunk)
    ```
  </Tab>

  <Tab title="JavaScript">
    ```javascript theme={null}
    import { writeFile } from 'node:fs/promises';

    const vendorData = encodeURIComponent('user-abc-123');
    const response = await fetch(
      `https://verification.didit.me/v3/users/${vendorData}/generate-pdf/`,
      { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },
    );
    if (!response.ok) throw new Error(`PDF generation failed: HTTP ${response.status}`);
    await writeFile('user-history.pdf', Buffer.from(await response.arrayBuffer()));
    ```
  </Tab>
</Tabs>

## Permissions

Any active API key of the application can call this endpoint. Didit API keys are
**application-scoped, not role-scoped**: the Console's [roles and permissions](/console/roles-permissions)
(`read:users` and the rest) decide what a *person* can do in the Console, and are not evaluated
for Management API traffic. A key can only export users of its own application.

## Related

* [Generate PDF](/sessions-api/generate-pdf) — the same report for a single session
* [User KYC history](/entities/users/kyc-history) — listing and inspecting a user's sessions as JSON
* [Export PDF & CSV](/console/export-pdf-csv) — the Console side of both exports
* [Get user](/management-api/users/get) — the user's profile and feature map as JSON


## OpenAPI

````yaml GET /v3/users/{vendor_data}/generate-pdf/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: Identity verification API. Authenticate with x-api-key header.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/users/{vendor_data}/generate-pdf/:
    get:
      tags:
        - Users
      summary: Download the full user history PDF report
      description: >-
        Render and download one compliance-ready PDF containing the full
        verification history of the user identified by `vendor_data`. It is the
        same document the Console produces from **User detail → Actions →
        Download PDF**, so periodic compliance exports no longer need a person
        clicking in the Console.


        **What the report contains.** A cover page with the profile summary
        (name, date of birth, issuing states, approved emails and phones), the
        latest status of every feature, the session counters and an index of the
        sessions included — followed by the standard report of every included
        session, in chronological order (oldest first). Each session report is
        the same document `GET /v3/session/{sessionId}/generate-pdf/` returns
        for that session.


        **Eligible sessions.** Only User Verification (KYC) sessions in
        `Approved`, `Declined`, `In Review` or `Kyc Expired` are included;
        sessions in any other status are skipped. If the user has no session in
        one of those statuses the endpoint returns `403` — see below.


        **Cap.** The report is capped at the **20 most recent** reportable
        sessions. When a user has more, the newest 20 are included and the cover
        page states how many older sessions were left out. Use `GET
        /v3/sessions/?vendor_data=…` for the complete list as JSON.


        **Branding.** If your application has white-label customization, the
        cover and every session report are rendered with your logo and
        privacy-policy URL; otherwise they carry Didit branding. Reports are
        rendered in English — there is no language parameter.


        **No caching.** Every call re-renders the PDF from the current data, so
        a report generated after a manual review reflects the reviewer's
        decision. Two calls for the same user can produce byte-different files —
        archive the downloaded file if you need an immutable copy.


        **Latency.** Rendering is synchronous and downloads every stored image
        of every included session, so a user with many media-heavy sessions can
        take tens of seconds. Use a generous client read timeout (120 s
        recommended) and stream the body to disk. Generation stops at a
        server-side time budget instead of running until your client gives up:
        if that budget is reached, the response is still a valid PDF containing
        the most recent sessions, and the cover states how many were omitted.


        **Scope.** The user is resolved inside the application that owns the API
        key. A key can never export a user of another application — such a user
        does not exist for it and the endpoint returns `404`.


        **Trailing slash.** The canonical route ends with a trailing slash
        (`…/generate-pdf/`). A request without it is served directly with the
        same response — there is no `301` redirect, so no client has to follow
        redirects and `curl` does not need `-L`. Use the slashed URL as in the
        samples.
      operationId: get_user_generate_pdf
      parameters:
        - name: vendor_data
          in: path
          required: true
          schema:
            type: string
          description: >-
            Your unique identifier for the user — a free-form string (NOT a
            UUID). This is the same value you passed as `vendor_data` when
            creating the user's sessions, matched exactly as sent. URL-encode
            it; a value containing `/` cannot be addressed by this route.
          example: user-abc-123
      responses:
        '200':
          description: >-
            The rendered PDF document, returned directly as binary
            `application/pdf` — there is no JSON wrapper and no download-URL
            indirection. The body starts with the `%PDF` magic bytes. Save it to
            a `.pdf` file or stream it through to your caller.
          headers:
            Content-Disposition:
              description: >-
                `attachment; filename=user_{didit_internal_id}.pdf` — the file
                is named after the user's stable Didit identifier
                (`didit_internal_id` on `GET /v3/users/{vendor_data}/`), not
                after `vendor_data`.
              schema:
                type: string
                example: >-
                  attachment;
                  filename=user_f4e5e1f2-94a9-4f86-8c16-2b7d9b4db418.pdf
            Content-Length:
              description: Total size of the PDF in bytes; the body is not chunked.
              schema:
                type: integer
                example: 148213
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '401':
          description: >-
            No credentials supplied. Requests to `/v3/users/*` paths without an
            `x-api-key` header (or `Authorization: Bearer` token) are rejected
            by the authentication middleware before reaching the API.
          content:
            application/json:
              examples:
                Missing credentials:
                  value:
                    detail: >-
                      You must be authenticated with a valid access token to
                      access this endpoint.
        '403':
          description: >-
            Authorization or eligibility failure. Distinguish the two by the
            `detail` string:


            - `"You do not have permission to perform this action."` — the API
            key is invalid or revoked, or the request carries a Console user
            token instead of an application API key.

            - `"This user has no sessions in review, declined, approved or kyc
            expired to report."` — the user exists in your application but none
            of its sessions is in a reportable status yet (for example every
            session is still `In Progress`, or the user was created through
            `POST /v3/users/create/` and has never verified). Wait for a
            `status.updated` webhook before requesting the report.
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                    example: >-
                      This user has no sessions in review, declined, approved or
                      kyc expired to report.
              examples:
                Invalid credentials:
                  summary: Revoked or invalid API key, or a Console user token
                  value:
                    detail: You do not have permission to perform this action.
                No reportable sessions:
                  summary: >-
                    User exists but has no Approved / Declined / In Review / Kyc
                    Expired session
                  value:
                    detail: >-
                      This user has no sessions in review, declined, approved or
                      kyc expired to report.
        '404':
          description: >-
            No user with the supplied `vendor_data` exists in the application
            behind the API key. Users of other applications are invisible to
            this key and return the same `404`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                    example: User not found.
              examples:
                Unknown user:
                  summary: vendor_data does not match any user of this application
                  value:
                    detail: User not found.
        '429':
          description: >-
            Rate limit exceeded. PDF generation has a dedicated limit of **50
            requests per minute per credential** (API key, Bearer token, or
            source IP when unauthenticated), shared between this endpoint and
            `GET /v3/session/{sessionId}/generate-pdf/`, in addition to the
            global 600/min GET limit. Wait `Retry-After` seconds (also reflected
            in the `X-RateLimit-*` headers), then retry. Reports are not cached
            server-side, so download each report once and store the file instead
            of re-fetching it.
          headers:
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                minimum: 1
                example: 30
            X-RateLimit-Limit:
              description: >-
                Maximum number of PDF generations allowed in the current window
                (50).
              schema:
                type: integer
                example: 50
            X-RateLimit-Remaining:
              description: Requests remaining in the current window.
              schema:
                type: integer
                example: 0
            X-RateLimit-Reset:
              description: Unix timestamp (seconds) at which the current window resets.
              schema:
                type: integer
                example: 1750000000
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                    description: Human-readable explanation of the rate-limit breach.
                    example: >-
                      Session PDF generation rate limit exceeded. You can make
                      up to 50 requests per minute.
              examples:
                Throttled:
                  summary: >-
                    More than 50 PDF generations in one minute from the same
                    credential
                  value:
                    detail: >-
                      Session PDF generation rate limit exceeded. You can make
                      up to 50 requests per minute.
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: curl
          label: cURL
          source: |-
            curl --fail \
              'https://verification.didit.me/v3/users/user-abc-123/generate-pdf/' \
              -H 'x-api-key: YOUR_API_KEY' \
              --output user-history.pdf
        - lang: python
          label: Python
          source: |-
            import requests

            response = requests.get(
                "https://verification.didit.me/v3/users/user-abc-123/generate-pdf/",
                headers={"x-api-key": "YOUR_API_KEY"},
                stream=True,
                timeout=120,
            )
            response.raise_for_status()
            with open("user-history.pdf", "wb") as fh:
                for chunk in response.iter_content(chunk_size=8192):
                    fh.write(chunk)
        - lang: javascript
          label: JavaScript
          source: >-
            import { writeFile } from 'node:fs/promises';


            const vendorData = encodeURIComponent('user-abc-123');

            const response = await fetch(
              `https://verification.didit.me/v3/users/${vendorData}/generate-pdf/`,
              { headers: { 'x-api-key': process.env.DIDIT_API_KEY } },
            );

            if (!response.ok) throw new Error(`PDF generation failed: HTTP
            ${response.status}`);

            await writeFile('user-history.pdf', Buffer.from(await
            response.arrayBuffer()));
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````