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

# Update Session Status

> Manually override the final decision of a KYC or KYB verification session. Set `new_status` to `Approved` or `Declined` to record a manual decision, or to `Resubmitted` to clear specific verification steps and let the user redo them through the same verification URL. The endpoint accepts both user (KYC) and business (KYB) session IDs.

Call it once a session has reached a reviewable state: the current status must be `Approved`, `Declined`, `In Review`, `Kyc Expired`, `Abandoned`, or `Resubmitted`. Sessions that are still `Not Started`, `In Progress`, or `Awaiting User`, or that ended as `Expired`, return `400`. The new status must differ from the current one, and your API key's organization needs the `write:sessions` permission. The eligible-state check runs before body validation, so an ineligible session returns the `Wrong Current Status` error even when the payload is also invalid.

For `Resubmitted`, pass `nodes_to_resubmit` to choose which workflow steps the user must redo, or omit it to auto-select existing feature attempts in a resubmittable state (`Declined`, `In Review`, `Not Finished`, `Expired`). Never-attempted features are not auto-selected — list them explicitly in `nodes_to_resubmit`, or the request returns `400` when no recorded attempt needs resubmission. Captured data for the selected steps is deleted, the steps are reordered to match the workflow graph, the session's expiration window restarts, and the original verification URL becomes usable again. Backend-only steps (`AML`, `DATABASE_VALIDATION`, `IP_ANALYSIS`) at the head of the list run immediately without user interaction; if every resubmitted step is backend-only, the session re-finalizes within the same request. `KYB_REGISTRY` and `KYB_KEY_PEOPLE` cannot be resubmitted directly — resubmit the relevant child KYC sessions instead and the parent business session recomputes automatically.

Every successful call fires the `status.updated` [webhook](/integration/webhooks) once the change commits and appends an activity entry — with actor attribution and your `comment` — to the session's `reviews`, visible in [Get Decision](#get-/v3/session/-sessionId-/decision/) and in the console activity timeline. The user-profile aggregates linked to the session's `vendor_data` update as well. Declining disables ongoing AML monitoring for the session; re-approving re-enables it (from `Resubmitted`, `Declined`, or `Kyc Expired`) when the workflow has monitoring turned on. Approving or declining a child KYC that belongs to a KYB key-people check recomputes the parent business session's status. Set `send_email: true` (with `email_address`) to notify the user: a status notice for `Approved`/`Declined`, or a resubmission email containing the verification link and per-step reasons for `Resubmitted`.

The operation is intentionally not repeatable with the same value: re-sending the current status returns `400` (`The new status is the same as the current status.`), so accidental double-submissions are harmless. Authentication and permission failures both return `403` — this API never responds `401`.

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="Update Session Status Prompt"
  prompt={`Manually approve, decline, or re-open a Didit verification session.

Endpoint:
PATCH https://verification.didit.me/v3/session/{session_id}/update-status/

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

Path parameter:
- session_id (UUID) — KYC or KYB; Didit looks it up in both tables.

Request body:
- new_status (string, required) — One of "Approved", "Declined", "Resubmitted". Any other value is rejected.
- comment (string, optional) — Free-text note saved on the audit trail (visible in the Console review log).
- nodes_to_resubmit (array, required when new_status="Resubmitted") — Which feature steps the user must redo. Each entry: { "node_id": "feature_poa_1", "feature": "POA" }. KYB-only nodes "KYB_REGISTRY" and "KYB_KEY_PEOPLE" cannot be resubmitted directly (the parent recomputes from child KYCs).
- send_email (boolean, optional, default false) — If true with new_status="Resubmitted", email the user a fresh hosted link.
- email_address (string, required when send_email=true).
- email_language (string, optional, default "en") — ISO 639-1.

Allowed transitions:
Current status must be one of: Approved, Declined, In Review, Kyc Expired, Abandoned, Resubmitted. new_status must DIFFER from current — calling with same status returns 400 "The new status is the same as the current status."

curl example — approve after manual review:
curl -X PATCH https://verification.didit.me/v3/session/<SESSION_ID>/update-status/ \\
-H "x-api-key: <API_KEY>" \\
-H "Content-Type: application/json" \\
-d '{
"new_status": "Approved",
"comment": "Cleared after compliance review."
}'

curl example — request resubmission of the proof-of-address step:
curl -X PATCH https://verification.didit.me/v3/session/<SESSION_ID>/update-status/ \\
-H "x-api-key: <API_KEY>" \\
-H "Content-Type: application/json" \\
-d '{
"new_status": "Resubmitted",
"nodes_to_resubmit": [{ "node_id": "feature_poa_1", "feature": "POA" }],
"comment": "Please upload a clearer proof of address",
"send_email": true,
"email_address": "user@example.com"
}'

Response: the updated session (same shape as the session decision, with session_kind included).

Side effects:
- Fires a status.updated webhook (plus user.status.updated or business.status.updated alias).
- Writes an entry to the Console review trail with the actor and your comment.
- "Resubmitted" tears down the failed feature rows in nodes_to_resubmit, resets the session expiration window, and (if send_email=true) emails the user a fresh link.

Failure modes:
- 400 — invalid new_status, status transition not allowed, missing nodes_to_resubmit on resubmit, send_email=true without email_address, etc. Body is { "<field>": ["..."] }.
- 401 — invalid credentials.
- 403 — { "detail": "You do not have permission to perform this action." } when the caller lacks write:sessions.
- 404 — { "detail": "Not found." } when the session does not exist for this application.

Idempotency:
Not idempotent — repeating the same call returns 400.

Next step:
After updating, call GET /v3/session/{session_id}/decision/ to fetch the refreshed payload — see /sessions-api/retrieve-session.

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

## KYC and KYB support

This endpoint works identically for **User Verification (KYC)** and **Business Verification (KYB)** sessions. Didit looks up the `session_id` in both tables; status-transition validation is model-agnostic (the same enum of statuses applies to both).

The response includes `session_kind` so your downstream logic can switch on the outcome kind.

## Allowed transitions

You can move a session to `APPROVED`, `DECLINED`, `IN_REVIEW`, or `RESUBMITTED`. The session's current status must be one of: `APPROVED`, `DECLINED`, `IN_REVIEW`, `KYC_EXPIRED`, `ABANDONED`, or `RESUBMITTED` — otherwise the API returns a validation error.

## Examples

<Tabs>
  <Tab title="Approve a User Verification (KYC) session">
    ```bash theme={null}
    curl -X PATCH https://verification.didit.me/v3/session/4c5c7f3a-.../update-status/ \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "new_status": "Approved",
        "comment": "Cleared after manual review by compliance team"
      }'
    ```
  </Tab>

  <Tab title="Decline a Business Verification (KYB) session">
    ```bash theme={null}
    curl -X PATCH https://verification.didit.me/v3/session/bs-01HJX1.../update-status/ \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "new_status": "Declined",
        "comment": "Sanctioned subsidiary detected in ownership chain"
      }'
    ```
  </Tab>

  <Tab title="Request resubmission">
    ```bash theme={null}
    curl -X PATCH https://verification.didit.me/v3/session/.../update-status/ \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "new_status": "Resubmitted",
        "nodes_to_resubmit": [
          { "node_id": "feature_poa_1", "feature": "POA" }
        ],
        "comment": "Please upload a clearer proof of address",
        "send_email": true
      }'
    ```
  </Tab>
</Tabs>

## Permission

Requires the `write:sessions` privilege. The same privilege covers both kinds.

## Related

* [Verification statuses](/integration/verification-statuses) — status-machine reference.
* [Retrieve session](/sessions-api/retrieve-session) — fetch the decision after updating.
* [Sessions overview](/sessions-api/overview) — kind discrimination.


## OpenAPI

````yaml PATCH /v3/session/{sessionId}/update-status/
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}/update-status/:
    patch:
      tags:
        - Sessions
      summary: Approve, decline, or request resubmission for a verification session
      description: >-
        Manually override the final decision of a KYC or KYB verification
        session. Set `new_status` to `Approved` or `Declined` to record a manual
        decision, or to `Resubmitted` to clear specific verification steps and
        let the user redo them through the same verification URL. The endpoint
        accepts both user (KYC) and business (KYB) session IDs.


        Call it once a session has reached a reviewable state: the current
        status must be `Approved`, `Declined`, `In Review`, `Kyc Expired`,
        `Abandoned`, or `Resubmitted`. Sessions that are still `Not Started`,
        `In Progress`, or `Awaiting User`, or that ended as `Expired`, return
        `400`. The new status must differ from the current one, and your API
        key's organization needs the `write:sessions` permission. The
        eligible-state check runs before body validation, so an ineligible
        session returns the `Wrong Current Status` error even when the payload
        is also invalid.


        For `Resubmitted`, pass `nodes_to_resubmit` to choose which workflow
        steps the user must redo, or omit it to auto-select existing feature
        attempts in a resubmittable state (`Declined`, `In Review`, `Not
        Finished`, `Expired`). Never-attempted features are not auto-selected —
        list them explicitly in `nodes_to_resubmit`, or the request returns
        `400` when no recorded attempt needs resubmission. Captured data for the
        selected steps is deleted, the steps are reordered to match the workflow
        graph, the session's expiration window restarts, and the original
        verification URL becomes usable again. Backend-only steps (`AML`,
        `DATABASE_VALIDATION`, `IP_ANALYSIS`) at the head of the list run
        immediately without user interaction; if every resubmitted step is
        backend-only, the session re-finalizes within the same request.
        `KYB_REGISTRY` and `KYB_KEY_PEOPLE` cannot be resubmitted directly —
        resubmit the relevant child KYC sessions instead and the parent business
        session recomputes automatically.


        Every successful call fires the `status.updated`
        [webhook](/integration/webhooks) once the change commits and appends an
        activity entry — with actor attribution and your `comment` — to the
        session's `reviews`, visible in [Get
        Decision](#get-/v3/session/-sessionId-/decision/) and in the console
        activity timeline. The user-profile aggregates linked to the session's
        `vendor_data` update as well. Declining disables ongoing AML monitoring
        for the session; re-approving re-enables it (from `Resubmitted`,
        `Declined`, or `Kyc Expired`) when the workflow has monitoring turned
        on. Approving or declining a child KYC that belongs to a KYB key-people
        check recomputes the parent business session's status. Set `send_email:
        true` (with `email_address`) to notify the user: a status notice for
        `Approved`/`Declined`, or a resubmission email containing the
        verification link and per-step reasons for `Resubmitted`.


        The operation is intentionally not repeatable with the same value:
        re-sending the current status returns `400` (`The new status is the same
        as the current status.`), so accidental double-submissions are harmless.
        Authentication and permission failures both return `403` — this API
        never responds `401`.
      operationId: patch_v3_session_update_status
      parameters:
        - in: path
          name: sessionId
          required: true
          description: >-
            UUID of the verification session to update. Accepts both user (KYC)
            and business (KYB) session IDs — the service resolves the ID across
            both session types.
          schema:
            type: string
            format: uuid
            example: 11111111-2222-3333-4444-555555555555
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - new_status
              properties:
                new_status:
                  type: string
                  description: >-
                    Target status. `Approved` and `Declined` record a final
                    manual decision (each can also overturn the other).
                    `Resubmitted` clears the selected steps and sends the
                    session back to the user. Any other value returns `400`.
                  enum:
                    - Approved
                    - Declined
                    - Resubmitted
                  example: Approved
                comment:
                  type: string
                  description: >-
                    Free-text reason for the change, stored on the session's
                    review trail and returned in the `reviews` array of [Get
                    Decision](#get-/v3/session/-sessionId-/decision/). For
                    example `Duplicated user`.
                  example: All checks passed manual review
                nodes_to_resubmit:
                  type: array
                  description: >-
                    Workflow steps the user must redo. Only acted on when
                    `new_status` is `Resubmitted`. For `Approved`/`Declined` the
                    entries are still schema-validated (an invalid `feature`
                    value returns `400`) but schema-valid entries are
                    semantically ignored. If omitted, the server auto-selects
                    existing OCR, Liveness, Face Match, POA, Phone, Email, AML,
                    Database Validation, and Questionnaire attempts whose status
                    is `Declined`, `In Review`, `Not Finished`, or `Expired`
                    (NFC, IP analysis, age estimation, face search, and KYB
                    document attempts are never auto-selected; non-face-match
                    face attempts are selected as `LIVENESS`) — features the
                    user never attempted (no recorded attempt) are NOT selected,
                    so a session with zero attempts returns `400` ("No features
                    found that need resubmission") even though nothing was
                    approved; pass `nodes_to_resubmit` explicitly in that case.
                    Steps are executed in workflow-graph order regardless of the
                    order you send them. `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and
                    the `KYB` alias are rejected with `400` — those parent
                    checks recompute from their child KYC sessions.
                  items:
                    type: object
                    required:
                      - node_id
                      - feature
                    properties:
                      node_id:
                        type: string
                        description: >-
                          Node identifier as it appears in the session's
                          workflow definition (for example `feature_ocr`,
                          `feature_liveness`).
                        example: feature_ocr
                      feature:
                        type: string
                        description: >-
                          Feature type of the node. The aliases
                          `ID_VERIFICATION`, `POA`, `PHONE`, and `EMAIL` are
                          normalized server-side to `OCR`, `PROOF_OF_ADDRESS`,
                          `PHONE_VERIFICATION`, and `EMAIL_VERIFICATION`.
                          `KYB_REGISTRY`, `KYB_KEY_PEOPLE`, and `KYB` pass
                          schema validation but are always rejected with the
                          business-rule `400` shown in the examples.
                        enum:
                          - OCR
                          - OCR_BACK
                          - NFC
                          - AML
                          - FACE
                          - LIVENESS
                          - FACE_MATCH
                          - IP_ANALYSIS
                          - AGE_ESTIMATION
                          - PROOF_OF_ADDRESS
                          - PHONE_VERIFICATION
                          - EMAIL_VERIFICATION
                          - FACE_SEARCH
                          - DATABASE_VALIDATION
                          - QUESTIONNAIRE
                          - DOCUMENT_AI
                          - KYB_DOCUMENTS
                          - ID_VERIFICATION
                          - POA
                          - PHONE
                          - EMAIL
                          - KYB_REGISTRY
                          - KYB_KEY_PEOPLE
                          - KYB
                        example: OCR
                send_email:
                  type: boolean
                  description: >-
                    Whether to email the user about the change. Requires
                    `email_address`. For `Approved`/`Declined` the user receives
                    a status notice; for `Resubmitted` the email includes the
                    verification link and the per-step resubmission reasons.
                  default: false
                  example: false
                email_address:
                  type: string
                  format: email
                  description: >-
                    Recipient for the notification email. **Required when
                    `send_email` is `true`** — omitting it returns `400`.
                  example: user@example.com
                email_language:
                  type: string
                  description: >-
                    Language for the notification email. Accepts any string at
                    schema level; unsupported codes silently fall back to
                    English (`en`).
                  default: en
                  example: en
            examples:
              Approve Session:
                summary: Approve a session after manual review
                value:
                  new_status: Approved
                  comment: All checks passed manual review
              Decline Session:
                summary: Decline a session
                value:
                  new_status: Declined
                  comment: Suspected fraud
              Resubmit Specific Steps:
                summary: Request resubmission of chosen steps, with email
                value:
                  new_status: Resubmitted
                  nodes_to_resubmit:
                    - node_id: feature_ocr
                      feature: OCR
                    - node_id: feature_liveness
                      feature: LIVENESS
                  send_email: true
                  email_address: user@example.com
                  email_language: en
              Resubmit Auto-select:
                summary: Request resubmission (auto-select non-approved steps)
                value:
                  new_status: Resubmitted
      responses:
        '200':
          description: >-
            Status updated. The response contains only the `session_id` — fetch
            the updated session via [Get
            Decision](#get-/v3/session/-sessionId-/decision/). The
            `status.updated` webhook fires once the change commits.
          content:
            application/json:
              schema:
                type: object
                properties:
                  session_id:
                    type: string
                    format: uuid
                    description: UUID of the updated session.
              examples:
                Updated:
                  summary: Returned for Approved, Declined, and Resubmitted alike
                  value:
                    session_id: 3472fb7c-8f7c-4d1a-9cf4-cf3d74ce1a60
        '400':
          description: >-
            Validation error. Field errors arrive as `{"<field>":
            ["<message>"]}` (some business-rule rejections use a string value
            instead: `{"nodes_to_resubmit": "<message>"}`), cross-state errors
            as `{"detail": "<message>"}`, and the same-status error as a bare
            JSON array.
          content:
            application/json:
              examples:
                Wrong Current Status:
                  summary: Session is not in an updatable state
                  value:
                    detail: >-
                      Only sessions with status Approved, Declined, In Review,
                      Kyc Expired, Abandoned, Resubmitted can be updated.
                      Current status: In Progress
                Invalid Status Value:
                  summary: >-
                    new_status is a real session status but not one of
                    Approved/Declined/Resubmitted
                  value:
                    new_status:
                      - >-
                        You can only update the status to 'Approved',
                        'Declined', or 'Resubmitted'.
                Missing new_status:
                  summary: Required field omitted
                  value:
                    new_status:
                      - This field is required.
                Same Status:
                  summary: New status equals the current status
                  value:
                    - The new status is the same as the current status.
                Email Address Required:
                  summary: send_email true without email_address
                  value:
                    email_address:
                      - Email address is required when send_email is true.
                Non-resubmittable Feature:
                  summary: Resubmit targets a KYB parent check
                  value:
                    nodes_to_resubmit: >-
                      The following features cannot be resubmitted directly:
                      KYB_REGISTRY. Resubmit the relevant child KYC sessions
                      instead; the parent will recompute automatically.
                Nothing To Resubmit:
                  summary: Auto-select found no non-approved features
                  value:
                    nodes_to_resubmit: >-
                      No features found that need resubmission. All features are
                      already approved.
                Invalid Feature Choice:
                  summary: Unknown feature in nodes_to_resubmit
                  value:
                    nodes_to_resubmit:
                      - feature:
                          - >-
                            "BOGUS" is not a valid choice. Valid choices are:
                            OCR, OCR_BACK, NFC, AML, FACE, LIVENESS, FACE_MATCH,
                            IP_ANALYSIS, AGE_ESTIMATION, PROOF_OF_ADDRESS,
                            PHONE_VERIFICATION, EMAIL_VERIFICATION, FACE_SEARCH,
                            DATABASE_VALIDATION, QUESTIONNAIRE, KYB_REGISTRY,
                            KYB_DOCUMENTS, KYB_KEY_PEOPLE, ID_VERIFICATION, POA,
                            PHONE, EMAIL, KYB
                Invalid Status Value (not a status at all):
                  summary: new_status is not a valid StatusChoices value
                  value:
                    new_status:
                      - '"Bogus" is not a valid choice.'
        '403':
          description: >-
            Missing/invalid credentials or insufficient permissions. This API
            returns `403` for authentication failures — never `401`.
          content:
            application/json:
              examples:
                Invalid Credentials:
                  summary: Missing or invalid x-api-key
                  value:
                    detail: >-
                      Authentication credentials were not provided or are
                      invalid.
                Missing Privilege:
                  summary: API key lacks write:sessions
                  value:
                    detail: You do not have permission to perform this action.
        '404':
          description: >-
            No KYC or KYB session with this `session_id` exists in your
            application (soft-deleted sessions also return `404`).
          content:
            application/json:
              examples:
                Not Found:
                  summary: Unknown session_id
                  value:
                    detail: Not found.
      security:
        - ApiKeyAuth: []
      x-codeSamples:
        - lang: curl
          label: curl
          source: |-
            curl -X PATCH \
              https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/ \
              -H 'x-api-key: YOUR_API_KEY' \
              -H 'Content-Type: application/json' \
              -d '{
                "new_status": "Approved",
                "comment": "All checks passed manual review"
              }'
        - lang: python
          label: Python
          source: >-
            import requests


            response = requests.patch(
                "https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/",
                headers={
                    "x-api-key": "YOUR_API_KEY",
                    "Content-Type": "application/json",
                },
                json={
                    "new_status": "Resubmitted",
                    "nodes_to_resubmit": [
                        {"node_id": "feature_ocr", "feature": "OCR"},
                        {"node_id": "feature_liveness", "feature": "LIVENESS"},
                    ],
                    "send_email": True,
                    "email_address": "user@example.com",
                    "email_language": "en",
                },
            )

            response.raise_for_status()

            print(response.json())  # {'session_id':
            '11111111-2222-3333-4444-555555555555'}
        - lang: javascript
          label: JavaScript
          source: |-
            const response = await fetch(
              'https://verification.didit.me/v3/session/11111111-2222-3333-4444-555555555555/update-status/',
              {
                method: 'PATCH',
                headers: {
                  'x-api-key': process.env.DIDIT_API_KEY,
                  'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                  new_status: 'Declined',
                  comment: 'Suspected fraud',
                }),
              },
            );
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            const { session_id } = await response.json();
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````