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

# Individual KYC

> Verify an individual's identity on a provider-hosted page, then read eligibility before offering a bank rail.

Identity verification (KYC) for an `individual` account runs on a page hosted by a verification provider. You start a session, send the customer to the `verificationUrl` it returns, and read the result.

## KYC via hosted link

The hosted link is the only way to verify an individual. There is no partner-collected KYC: no API accepts identity data or documents from you, and the provider's decision and documents are never returned to you.

<Steps>
  <Step title="Verify the email">
    KYC needs a verified email first. See [Email verification](/concepts/email-verification).
  </Step>

  <Step title="Start a session">
    `POST /v2/accounts/{accountId}/kyc/session`. It returns the hosted `verificationUrl`.
  </Step>

  <Step title="Send the customer to the link">
    Open `verificationUrl` for the customer. The page takes no return URL and does not redirect back to you.
  </Step>

  <Step title="Wait for the result">
    Act on the `kyc.updated` webhook, or poll `GET /v2/accounts/{accountId}/kyc`.
  </Step>

  <Step title="Read eligibility">
    Offer a bank rail only when `eligibility.status` is `eligible`. Then [activate a capability](/concepts/capability-activation).
  </Step>
</Steps>

## Start a session

```bash theme={null}
curl -X POST https://staging-api-v2.stableyard.fi/v2/accounts/acct_123/kyc/session \
  -u "$APP_ID:$APP_SECRET"
```

```json theme={null}
{
  "id": "kyc_123",
  "accountId": "acct_123",
  "status": "in_progress",
  "verificationUrl": "https://…",
  "eligibility": { "status": "not_eligible" },
  "nextAction": { "type": "complete_verification" },
  "createdAt": "2026-10-01T12:00:00Z",
  "updatedAt": "2026-10-01T12:00:00Z"
}
```

* **No body, no return URL.** The account path fixes the subject, and the provider configuration is Stableyard's.
* **Repeated calls reuse the active session** and return the same link. A session still being prepared returns `status: "pending"` without `verificationUrl`; call again shortly or wait for `kyc.updated`.
* **An approved, current verification is returned without a link.** Nothing new is created.
* **At most three provider sessions per account.** A rejected or expired session can be retried with a new session; the fourth attempt is refused with `409` and needs manual review.

`verificationUrl` is returned only while a session is open. It is absent once the verification is approved, rejected or expired. What the customer sees on the page is on [Branding and your frontend](/white-label/branding-and-frontend#surfaces-that-are-not-yours).

## Read the result

```bash theme={null}
curl https://staging-api-v2.stableyard.fi/v2/accounts/acct_123/kyc \
  -u "$APP_ID:$APP_SECRET"
```

```json theme={null}
{
  "accountId": "acct_123",
  "current": {
    "id": "kyc_123",
    "accountId": "acct_123",
    "status": "approved",
    "eligibility": {
      "status": "eligible",
      "verifiedAt": "2026-10-01T12:20:00Z",
      "validUntil": "2027-10-01T12:20:00Z"
    },
    "nextAction": { "type": "none" },
    "completedAt": "2026-10-01T12:20:00Z",
    "createdAt": "2026-10-01T12:00:00Z",
    "updatedAt": "2026-10-01T12:20:00Z"
  },
  "verifications": [{ "id": "kyc_123", "status": "approved" }]
}
```

`current` is the newest verification, and `verifications` holds the ten most recent, newest first. Before the first session, `current` is absent and `verifications` is empty.

## KYC states

```mermaid theme={null}
stateDiagram-v2
  [*] --> pending: session created
  pending --> in_progress: hosted link issued
  in_progress --> approved
  in_progress --> rejected
  in_progress --> expired
  in_progress --> requires_review
  requires_review --> in_progress
  requires_review --> approved
  requires_review --> rejected
  requires_review --> expired
  approved --> requires_review: later review
  approved --> rejected: withdrawn
  approved --> expired
  rejected --> [*]
  expired --> [*]
```

| `status` | Meaning |
| - | - |
| `not_started` | Verification has not begun |
| `pending` | Session created; the hosted link is being prepared |
| `in_progress` | The hosted link is ready and the customer has not finished |
| `approved` | Verified |
| `rejected` | Not verified. A new session counts toward the limit of three |
| `expired` | The session or approval lapsed. A session the customer abandons also ends here |
| `requires_review` | Held for manual review |

`rejected` and `expired` are final for that verification. Another attempt is a new session.

## Eligibility

`eligibility.status` is what regulated operations check, and they recheck it every time they run. Read it, not `status`, before you offer a bank rail.

| `eligibility.status` | Meaning |
| - | - |
| `eligible` | Verified and current until `validUntil` |
| `expired` | The approval lapsed |
| `revoked` | A previously eligible verification was withdrawn |
| `requires_review` | Held for review |
| `not_eligible` | Not verified |

| Field | Meaning |
| - | - |
| `verifiedAt` | When the identity was verified |
| `validUntil` | When the approval lapses. After it, `eligibility.status` reads `expired` |
| `revokedAt` | When an eligible verification was withdrawn |
| `reasonCode` | Why the verification is not eligible, when known |

## Next actions

| `nextAction.type` | Do this |
| - | - |
| `start_verification` | Call `POST /v2/accounts/{accountId}/kyc/session` |
| `complete_verification` | Send the customer to `verificationUrl` |
| `refresh_verification` | Eligibility expired or was revoked. Start a new session |
| `contact_support` | Stableyard must review the account |
| `none` | Nothing. The customer is eligible |

## Errors

| Code | HTTP | When |
| - | - | - |
| `not_found` | 404 | The account is not in your app environment |
| `conflict` | 409 | No verified email (`details.nextAction.type: "verify_account_email"`), or a fourth session (`details.reason: "kyc_session_attempt_limit_reached"`) |
| `account_subject_unclassified` | 422 | An account created before classification cannot start verification |
| `provider_failure` | 502 | The verification provider could not create the session. The verification is held at `requires_review` for Stableyard to resolve |
| `provider_unavailable` | 503 | Identity verification is not configured in this environment |

## Webhooks

| Event | Fires when |
| - | - |
| `kyc.session_created` | A verification session was created |
| `kyc.updated` | The verification status changed. Read `GET /v2/accounts/{accountId}/kyc` |

## Related

<CardGroup cols={2}>
  <Card title="Email verification" icon="envelope" href="/concepts/email-verification">
    The gate before KYC.
  </Card>

  <Card title="Capability activation" icon="toggle-on" href="/concepts/capability-activation">
    The gate after KYC: approval for a bank service.
  </Card>

  <Card title="Onboarding overview" icon="user-check" href="/concepts/onboarding">
    Every gate between an account and a fiat rail.
  </Card>

  <Card title="Event catalog" icon="list" href="/webhooks/event-catalog#kyc-events">
    Every KYC and compliance event.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.