> ## 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.

# Generate PDF

> Render and download a compliance-ready PDF report for a finished verification session. The same path serves both User Verification (KYC) and Business Verification (KYB) sessions — the server looks the `sessionId` up in both tables and picks the matching report template.

**Eligible statuses.** The session must already carry a final or reviewable status:

- KYC sessions: `Approved`, `Declined`, `In Review`, or `Kyc Expired`.
- KYB sessions: `Approved`, `Declined`, or `In Review`.

Any other status (`Not Started`, `In Progress`, `Expired`, `Abandoned`, …) returns `403` with an explanatory `detail` string — see the `403` response below.

**What the report contains.** The PDF mirrors the console session view at the moment of the request:

- **KYC** — rolled-up session status, the warnings list, and one section per executed feature: ID Verification (document images plus extracted fields), NFC, Liveness, Face Match, Email Verification, Phone Verification, Proof of Address, Questionnaire answers, Database Validation, AML Screening, and IP Analysis — followed by session tags, console review activity (comments and status changes), and the session event timeline.
- **KYB** — registry checks (company data), AML screenings, business document verifications, key-people checks, questionnaire responses, phone/email verifications, IP analyses, and the session event timeline, followed by tags and review activity.

**Branding.** If your application has white-label customization, the report is rendered with your logo and links to your privacy-policy URL; otherwise it carries Didit branding. Reports are rendered in English — there is no language parameter.

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

**Latency.** Rendering downloads every stored image (document sides, selfies, extra files) before composing the PDF, so media-heavy sessions can take several seconds. Use a generous client read timeout (60 s recommended) and stream the body to disk.

**Trailing slash.** The canonical route ends with a trailing slash (`…/generate-pdf/`). Requests without it receive a `301` redirect to the slashed URL — most HTTP clients follow it automatically for `GET`, but plain `curl` needs `-L` (or call the slashed URL directly, as in the samples).

If you need the underlying data as JSON instead of a rendered report, use `GET /v3/session/{sessionId}/decision/`.

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="Generate PDF Prompt"
  prompt={`Download an audit-ready PDF report for a finished Didit verification session.

Endpoint:
GET https://verification.didit.me/v3/session/{session_id}/generate-pdf

Authentication:
Use the x-api-key header with your Didit API key (or a user-auth token). Privilege: read:sessions.

Path parameter:
- session_id (UUID) — works for both User Verification (KYC) and Business Verification (KYB) sessions. Didit routes to the correct template automatically.

Prerequisites:
- Session status must be one of "Approved", "Declined", "In Review". Any other status returns 403.
- Session must belong to the authenticated application — cross-application reads return 404.

curl example — save the PDF to disk:
curl https://verification.didit.me/v3/session/<SESSION_ID>/generate-pdf \\
-H "x-api-key: <API_KEY>" \\
-o report.pdf

Response:
- Body is binary (application/pdf). DO NOT parse as JSON.
- Content-Type: application/pdf
- Content-Disposition: attachment; filename=session_<session_id>.pdf for KYC sessions, business_<session_id>.pdf for KYB sessions.
- Typical wall time ~1-3s (server-side WeasyPrint rendering).

Template contents:
- KYC PDF: Identity (OCR), Liveness, Face Match, AML, POA, IP analysis, reviews, events.
- KYB PDF: Company registry data, officers, beneficial owners (UBOs), KYB documents, AML hits, questionnaires, phone verification, email verification, IP analysis, reviews, events.

White-label:
If white-label customization is enabled on your application, the PDF uses your logo and privacy-policy URL. Configure at Console → Customization.

Failure modes:
- 401 — invalid credentials.
- 403 — { "detail": "You do not have permission to perform this action." } when the key lacks read:sessions OR when the session is in a non-printable status (Not Started, In Progress, Expired, Abandoned, Kyc Expired, Resubmitted, Awaiting User).
- 404 — { "detail": "Not found." } when the session is not visible to this application.

Idempotency & cost:
Safe to retry — generation is read-only. Counts against the standard rate limit.

When to call:
- Compliance archives (regulator submissions, internal audit binders).
- Customer support replies (proof of decision for a specific session).
- After approve / decline via /sessions-api/update-status — render and store the resulting PDF.

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

## KYC and KYB support

Works for both **User Verification (KYC)** and **Business Verification (KYB)** sessions. Didit routes to the appropriate PDF template automatically:

* User Verification (KYC) session → report with ID verification, liveness, face match, AML, POA sections.
* Business Verification (KYB) session → report with company registry, key people (UBOs/officers), documents, AML, questionnaire, phone, email, and IP analysis sections when those features are part of the workflow.

Both use the same PDF generation pipeline and support white-label customization (logo, privacy policy URL).

## Filename convention

| Kind     | Filename                    |
| -------- | --------------------------- |
| User     | `session_<session_id>.pdf`  |
| Business | `business_<session_id>.pdf` |

Returned via the `Content-Disposition: attachment; filename=...` header.

## Status requirement

The session must be in `APPROVED`, `DECLINED`, or `IN_REVIEW` to generate a PDF. In-progress or not-started sessions return `403`.

## Examples

<Tabs>
  <Tab title="User Verification (KYC) PDF">
    ```bash theme={null}
    curl https://verification.didit.me/v3/session/4c5c7f3a-.../generate-pdf \
      -H "x-api-key: YOUR_API_KEY" \
      -o kyc-report.pdf
    ```
  </Tab>

  <Tab title="Business Verification (KYB) PDF">
    ```bash theme={null}
    curl https://verification.didit.me/v3/session/bs-01HJX1.../generate-pdf \
      -H "x-api-key: YOUR_API_KEY" \
      -o kyb-report.pdf
    ```
  </Tab>
</Tabs>

## White-label

When the application has white-label customization enabled, the PDF uses your logo and privacy-policy URL. Configure at *Console → Customization*.

## Permission

Requires `read:sessions`. Same scope for both kinds.

## Related

* [Sessions overview](/sessions-api/overview)
* [Retrieve session](/sessions-api/retrieve-session) — JSON version of the same decision
* [KYB response schema](/business-verification/response-schema) — fields included in business PDFs


## OpenAPI

````yaml GET /v3/session/{sessionId}/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/session/{sessionId}/generate-pdf:
    get:
      tags:
        - Sessions
      summary: Download a PDF report for a verification session
      description: >-
        Render and download a compliance-ready PDF report for a finished
        verification session. The same path serves both User Verification (KYC)
        and Business Verification (KYB) sessions — the server looks the
        `sessionId` up in both tables and picks the matching report template.


        **Eligible statuses.** The session must already carry a final or
        reviewable status:


        - KYC sessions: `Approved`, `Declined`, `In Review`, or `Kyc Expired`.

        - KYB sessions: `Approved`, `Declined`, or `In Review`.


        Any other status (`Not Started`, `In Progress`, `Expired`, `Abandoned`,
        …) returns `403` with an explanatory `detail` string — see the `403`
        response below.


        **What the report contains.** The PDF mirrors the console session view
        at the moment of the request:


        - **KYC** — rolled-up session status, the warnings list, and one section
        per executed feature: ID Verification (document images plus extracted
        fields), NFC, Liveness, Face Match, Email Verification, Phone
        Verification, Proof of Address, Questionnaire answers, Database
        Validation, AML Screening, and IP Analysis — followed by session tags,
        console review activity (comments and status changes), and the session
        event timeline.

        - **KYB** — registry checks (company data), AML screenings, business
        document verifications, key-people checks, questionnaire responses,
        phone/email verifications, IP analyses, and the session event timeline,
        followed by tags and review activity.


        **Branding.** If your application has white-label customization, the
        report is rendered with your logo and links to your privacy-policy URL;
        otherwise it carries Didit branding. Reports are rendered in English —
        there is no language parameter.


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


        **Latency.** Rendering downloads every stored image (document sides,
        selfies, extra files) before composing the PDF, so media-heavy sessions
        can take several seconds. Use a generous client read timeout (60 s
        recommended) and stream the body to disk.


        **Trailing slash.** The canonical route ends with a trailing slash
        (`…/generate-pdf/`). Requests without it receive a `301` redirect to the
        slashed URL — most HTTP clients follow it automatically for `GET`, but
        plain `curl` needs `-L` (or call the slashed URL directly, as in the
        samples).


        If you need the underlying data as JSON instead of a rendered report,
        use `GET /v3/session/{sessionId}/decision/`.
      operationId: get_v3_session_generate_pdf
      parameters:
        - name: sessionId
          in: path
          required: true
          description: >-
            UUID of the User Verification (KYC) or Business Verification (KYB)
            session to render — the `session_id` returned by `POST
            /v3/session/`. The same path works for both session kinds; use the
            `Content-Disposition` filename on the response to tell which report
            type you received.


            **Must be a well-formed, lowercase UUID.** The route only matches
            valid UUIDs, so a malformed value is rejected by the URL router
            before any application code runs and the response is a `404`
            **HTML** page rather than the JSON `{"detail": ...}` envelope shown
            below. The route's UUID converter only matches the canonical
            lowercase form — uppercase-hex UUIDs are rejected with the HTML 404.
          schema:
            type: string
            format: uuid
            example: 11111111-2222-3333-4444-555555555555
      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 (PDF 1.7).
            Save it to a `.pdf` file or stream it through to your caller.
          headers:
            Content-Disposition:
              description: >-
                `attachment; filename=session_{sessionId}.pdf` for KYC sessions,
                `attachment; filename=business_{sessionId}.pdf` for KYB sessions
                — the prefix tells you which report template was rendered.
              schema:
                type: string
                example: >-
                  attachment;
                  filename=session_11111111-2222-3333-4444-555555555555.pdf
            Content-Length:
              description: Total size of the PDF in bytes; the body is not chunked.
              schema:
                type: integer
                example: 25191
          content:
            application/pdf:
              schema:
                type: string
                format: binary
        '403':
          description: >-
            Authentication, authorization, or session-status failure. **This
            endpoint never returns `401`** — Didit's API-key authentication does
            not emit a `WWW-Authenticate` challenge, so missing or invalid
            credentials also surface as `403`. Distinguish the failure modes by
            the `detail` string:


            - `"Authentication credentials were not provided or are invalid."` —
            no `x-api-key` header (and no `Authorization: Bearer` token), or the
            supplied credential failed introspection (revoked, expired, or
            malformed).

            - `"You do not have permission to perform this action."` — the
            credential is valid but cannot read this session: a console-user
            Bearer token lacks the `read:sessions` permission or cannot access
            the owning application, or an API key belongs to a different
            organization than the session.

            - `"You can only generate a PDF for sessions in review, declined,
            approved or kyc expired"` — the KYC session exists but is not in an
            eligible status yet (for example `Not Started` or `In Progress`).
            Wait for the `status.updated` webhook before requesting the report.

            - `"You can only generate a PDF for sessions in review, declined or
            approved"` — same condition for KYB sessions.


            Note that for a completely unknown `sessionId` the endpoint resolves
            the owning application before validating credentials, so a missing
            session returns `404` even when the credential is missing or
            invalid.
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                    example: >-
                      You can only generate a PDF for sessions in review,
                      declined, approved or kyc expired
              examples:
                Missing or invalid credentials:
                  summary: No x-api-key header, or the key failed introspection
                  value:
                    detail: >-
                      Authentication credentials were not provided or are
                      invalid.
                Insufficient permissions:
                  summary: >-
                    Valid credential lacking read:sessions or access to the
                    owning application
                  value:
                    detail: You do not have permission to perform this action.
                KYC session not finished:
                  summary: KYC session still Not Started / In Progress
                  value:
                    detail: >-
                      You can only generate a PDF for sessions in review,
                      declined, approved or kyc expired
                KYB session not finished:
                  summary: KYB session still Not Started / In Progress
                  value:
                    detail: >-
                      You can only generate a PDF for sessions in review,
                      declined or approved
        '404':
          description: >-
            No active session with this `sessionId` exists — it was never
            created, or it has been deleted. Owner resolution happens before
            credential validation, so this `404` is returned even for
            unauthenticated requests. Depending on which lookup misses, the
            `detail` string is `"Not found."` (the common case) or `"Session not
            found."`.
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
                    example: Not found.
              examples:
                Unknown session:
                  summary: sessionId does not match any KYC or KYB session
                  value:
                    detail: Not found.
                Recently deleted session:
                  summary: Session was deleted between lookups
                  value:
                    detail: Session 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), 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/session/11111111-2222-3333-4444-555555555555/generate-pdf/ \
              -H 'x-api-key: YOUR_API_KEY' \
              --output report.pdf
        - lang: python
          label: Python
          source: |-
            import requests

            response = requests.get(
                "https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/",
                headers={"x-api-key": "YOUR_API_KEY"},
                stream=True,
                timeout=60,
            )
            response.raise_for_status()
            with open("report.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 response = await fetch(
              'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/generate-pdf/',
              { headers: { 'x-api-key': 'YOUR_API_KEY' } },
            );

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

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

````