# KYCPort > KYCPort is verified-human identity infrastructure: KYC/KYB-as-a-Service and "Sign in with KYCPort". A person verifies once and reuses that assurance across every tenant (relying party) — you read their verification status and tier instead of re-collecting documents. Multi-tenant, datacenter-hosted, OIDC/OAuth2-native. This file is written for AI coding assistants integrating KYCPort on behalf of a tenant. Follow it literally; every endpoint and field below is real. Base URL for all examples: `https://www.kycport.com`. ## Core model (read this first) - **You are a tenant (relying party).** You register an OAuth/OIDC client and get a `client_id` (+ `client_secret` for confidential clients). You never see a user's documents — only their *verification result*. - **Verify once, reuse everywhere.** Users carry a reusable assurance: `kyc_status` (`verified` | `pending` | `rejected` | `unverified` | `expired`) and an assurance `kyc_tier` (`t0`..`t3`). You request the tier you need; KYCPort returns whether the human meets it. - **BFF pattern is mandatory for browser apps.** Run a thin Backend-for-Frontend. Tokens and client secrets live ONLY on your server; the browser holds an httpOnly session cookie. Never put tokens in `localStorage` or expose `client_secret` to the client. - **HTTPS only, everywhere.** TLS 1.2+. Reject non-HTTPS redirect URIs. ## Authentication — OAuth2 Authorization Code + PKCE (the only supported browser flow) OIDC discovery (machine-readable, start here): `GET https://www.kycport.com/api/oidc/.well-known/openid-configuration` → `issuer` = `https://www.kycport.com`, plus `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `jwks_uri`. Endpoints: - `GET /authorize` — browser authorization endpoint; user consents; returns an authorization `code` to your `redirect_uri`. - `POST /api/oidc/token/` — exchange `code` (+ PKCE `code_verifier`) for tokens. ID + access tokens are JWTs signed **EdDSA (Ed25519)**; access tokens carry `typ: at+jwt`. - `GET /api/oidc/userinfo/` — `Authorization: Bearer ` → the user's claims (sub, email, and — if you requested the `kyc` scope — verification status + tier). Rules (non-negotiable): 1. **PKCE is required.** Generate a high-entropy `code_verifier`; send `code_challenge` = BASE64URL(SHA256(verifier)) with `code_challenge_method=S256`. Plain is rejected. 2. **Use `state` (CSRF) and `nonce` (replay).** Bind `state` to the user's session; reject a callback whose `state` doesn't match. 3. **Validate every token:** signature via `jwks_uri`, `iss` == `https://www.kycport.com`, `aud` == your `client_id`, `exp`/`nbf`, and `nonce`. 4. Scopes: `openid` (required), `email`, `profile`, `kyc` (verification status + tier). Request the minimum you need. 5. `redirect_uri` must exactly match a registered URI. Register the real HTTPS callback on your BFF. ## Reading verification status (the reusable-identity payoff) Two ways to get a user's KYC standing: - In the **ID token / userinfo** when you requested the `kyc` scope (no extra call). - Server-to-server: `GET /api/v1/kyc/{person_id}/status/` with `Authorization: Bearer ` → `{ "kyc_status": "verified", "kyc_tier": "t2", "aml_clear": true }` Field names are exactly `kyc_status` and `kyc_tier` (not `status`/`tier`). If the user isn't at your required tier, send them through verification (KYCPort hosts the capture flow) and re-check the status; do not collect documents yourself. ## Verification flow (if you host your own capture instead of redirecting) Tenant-key auth (`Authorization: Bearer `): 1. `POST /api/v1/verification/cases/` → `{ person_id, level }` opens a case. 2. `POST /api/v1/verification/cases/{id}/upload/` — multipart `document`, optional `document_back`, `selfie`. KYCPort stores the bytes and runs the real engine (document forensics, liveness/PAD, face-match + 1:N dedup). 3. `POST /api/v1/verification/cases/{id}/decide/` → the enforced decision (`outcome`, `risk_level`, `human_verified`, `automation_tier`). NEVER submit a precomputed analysis/verdict — the score is always computed server-side. ## Webhooks Subscribe to verification + status-change events. Each delivery is signed — verify the signature (HMAC over the raw body) before trusting it, and treat handlers as **idempotent** (deliveries can repeat). Return 2xx fast; KYCPort retries with backoff on non-2xx. ## Best practices checklist (apply all) - [ ] Confidential client + `client_secret` on the server only; rotate periodically. - [ ] PKCE S256 on every authorize; `state` + `nonce` validated on callback. - [ ] Validate `iss`, `aud`, `exp`, signature (EdDSA via jwks) on every token. - [ ] Browser holds only an httpOnly, `Secure`, `SameSite=Lax` session cookie — never a token. - [ ] Request least-privilege scopes; cache the user's tier, re-check on sensitive actions. - [ ] Idempotent webhook handlers with signature verification + replay protection. - [ ] All redirect URIs HTTPS and exact-match registered. - [ ] Handle errors: act on the JSON `error` field; retry only idempotent GETs/5xx with backoff; respect `429`. ## Full docs - Human + AI guide: `https://www.kycport.com/docs` - Partner server-to-server verification API (create case → upload → decide → poll/webhook), full reference: `docs/integration/partner-api.md` in your integration pack. - OpenID config: `https://www.kycport.com/api/oidc/.well-known/openid-configuration`