---
title: "Create verification request (dashboard)"
method: POST
path: "/api/v1/me/verification-requests"
tags: ["User Requests"]
---

# Create verification request (dashboard)

`POST /api/v1/me/verification-requests`

## Request

- Base URL: `https://api.proof.holdings`
- URL: `https://api.proof.holdings/api/v1/me/verification-requests`
- Auth: API key in cookie `auth_token`

Create a verification request from the dashboard.

## Request body

- CreateVerificationRequestBody: Request body for creating a multi-asset verification request
  - `reference_id` string: Your reference ID for this request
  - `public_profile_id` string: Public profile ID for branding
  - `assets` RequestedAsset[], required: Assets to verify
    - `type` 'phone' | 'email' | 'domain' | 'social' | 'wallet' | 'account' | 'telegram_bot', required: Type of asset being verified
    - `identifier` string, nullable: Specific identifier or null for user-provided
    - `channel` 'whatsapp' | 'telegram' | 'sms' | 'email' | 'dns' | 'http' | 'auto' | 'github' | 'google' | 'facebook' | 'x' | 'linkedin' | 'instagram' | 'youtube' | 'tiktok' | 'ethereum' | 'solana' | 'bitcoin' | 'coinbase' | 'kraken' | 'telegram_bot_token': Channel used for verification
    - `allowed_channels` VerificationChannel[]: Restrict to specific channels
    - `required` boolean: Whether this asset is required for completion
  - `action_type` 'verification' | '2fa' | 'login' | 'custom' | 'confirmation' | 'authorization': Type of action for verification request or authorization
  - `action_context` ActionContext: Context for custom and confirmation action types
    - `title` string: Action title (required for confirmation and custom types)
    - `description` string: Action description with details for the approver
    - `amount` number: Monetary amount for confirmation context
    - `currency` string: Currency code for confirmation context
    - `entity` string: Entity or resource being acted on
  - `validity_requirement` string: Required proof freshness (e.g., "7d", "24h")
  - `partial_ok` boolean: Complete even if only some assets verified
  - `redirect_url` string, uri: HTTPS URL to redirect after completion. Must be a public HTTPS URL (no localhost, internal IPs, or private networks).
  - `callback_url` string, uri: HTTPS URL for webhook notifications. Must be a public HTTPS URL (no localhost, internal IPs, or private networks).
  - `expires_in` integer: Time until expiry in seconds (max 30 days)
  - `proof_expiry_days` integer: Per-request proof expiry override in days (1-365). Overrides account default.

## Response `201`

Request created

- VerificationRequest: Multi-asset verification request
  - `id` string, required: MongoDB ObjectId
  - `reference_id` string
  - `status` 'pending' | 'partial' | 'completed' | 'expired' | 'cancelled', required: Status of a verification request
  - `computed_state` object: Additive machine-readable effective state: re-derives expiry from expires_at (never trusts the stored status). BOTH `is_expiring_soon` and `next_action` are role-aware — `next_action.role` says which side this envelope was computed for, and the two fields are consistent within it. Present only on authenticated owner/requester and recipient responses; absent on public/third-party surfaces.
    - `status` 'pending' | 'partial' | 'completed' | 'expired' | 'cancelled', required: Status of a verification request
    - `is_expiring_soon` boolean, required: Whether this request is close enough to `expires_at` to act on — and the WINDOW depends on which side of the request is reading, so one row can honestly answer differently on two surfaces. Read `next_action.role` to know which answer you have. For a `requester` the window is the lesser of 24h and HALF the request's own lifetime, so a just-created short-lived request is NOT "expiring soon" (advising a reminder before the recipient has had any time is the case this excludes). For a `recipient` the window is a flat 24h, because they already hold the link and what matters is the deadline itself.
    - `next_action` object, required
      - `code` string, required
      - `role` 'requester' | 'recipient', required
      - `label` string, required
  - `assets` object[], required
    - `type` 'phone' | 'email' | 'domain' | 'social' | 'wallet' | 'account' | 'telegram_bot', required: Type of asset being verified
    - `identifier` string, nullable
    - `channel` 'whatsapp' | 'telegram' | 'sms' | 'email' | 'dns' | 'http' | 'auto' | 'github' | 'google' | 'facebook' | 'x' | 'linkedin' | 'instagram' | 'youtube' | 'tiktok' | 'ethereum' | 'solana' | 'bitcoin' | 'coinbase' | 'kraken' | 'telegram_bot_token' | 'null', nullable: Preferred verification channel
    - `allowed_channels` VerificationChannel[], nullable: The channels this asset may be verified over, as set on the request. The backend ENFORCES it (a forbidden channel is refused with `channel_not_allowed`) and the recipient page offers only these; null or an empty array means no restriction. Echoed on owner responses so the party that sets it can read it back — the public recipient read returns the same field.
    - `required` boolean, required
    - `status` 'pending' | 'verified' | 'failed' | 'expired' | 'revoked' | 'cancelled', required: Current status of the verification
    - `verified_at` string, date-time
    - `verified_identifier` string: The actual identifier that was verified (only present when status is verified)
    - `verified_channel` 'whatsapp' | 'telegram' | 'sms' | 'email' | 'dns' | 'http' | 'auto' | 'github' | 'google' | 'facebook' | 'x' | 'linkedin' | 'instagram' | 'youtube' | 'tiktok' | 'ethereum' | 'solana' | 'bitcoin' | 'coinbase' | 'kraken' | 'telegram_bot_token': The channel used for verification (only present when status is verified)
    - `has_proof` boolean: Whether a proof token was generated for this asset
  - `action_type` 'verification' | '2fa' | 'login' | 'custom' | 'confirmation' | 'authorization', required: Type of action for verification request or authorization
  - `action_context` ActionContext: Context for custom and confirmation action types
    - `title` string: Action title (required for confirmation and custom types)
    - `description` string: Action description with details for the approver
    - `amount` number: Monetary amount for confirmation context
    - `currency` string: Currency code for confirmation context
    - `entity` string: Entity or resource being acted on
  - `partial_ok` boolean, required
  - `validity_requirement` string, nullable: Required proof freshness as set on the request (e.g. "7d"); null when none was set
  - `verification_url` string: URL for the verification flow
  - `redirect_url` string, nullable
  - `callback_url` string, nullable: The webhook URL set on the request, if any
  - `progress` object
    - `completed` integer, required
    - `completed_required` integer, required
    - `required` integer, required
    - `optional` integer, required
    - `total` integer, required
  - `expires_at` string, date-time, required
  - `completed_at` string, date-time
  - `created_at` string, date-time, required
  - `updated_at` string, date-time

## Other responses

- `400`: Validation error
- `402`: card_required. Only once billing is live: a free account with no card on file that has used its included proofs and the 50-proof grace this month is refused new proof requests until a card is added (add_card_url). Proofs already issued keep working. The error carries add_card_url, used, included and hard_limit.
- `403`: profile_not_owned — the named project does not belong to the account (h-subaccount-branding-per-project)

---

[API](https://skmtc.dev/proof/apis/proof-holdings-api.md) · [All operations](https://skmtc.dev/proof/apis/proof-holdings-api/llms.txt) · [OpenAPI document](https://skmtc.dev/proof/apis/proof-holdings-api/revisions/b15f506de696?raw)
