> ## Documentation Index
> Fetch the complete documentation index at: https://humanity-eaeda8f6.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# How to: SDK recipes

> Task-based playbooks built on top of @humanity-org/connect-sdk.

This tab collects prescriptive walkthroughs that tie the Humanity SDK to everyday integration tasks. Each recipe is copy/pasteable, highlights the minimum required inputs, and points to the underlying API surface when you need to drop down a level.

## Prerequisites

* Approved Humanity app with `client_id`, optional `client_secret`, and a registered `redirect_uri`.
* Access to the SDK package published as `@humanity-org/connect-sdk`.
* Somewhere secure (session storage, KV store, Redis, etc.) to persist the PKCE `code_verifier`, OAuth tokens, and any cursors returned by feed helpers.

```ts theme={null}
import { HumanitySDK } from '@humanity-org/connect-sdk';

const sdk = new HumanitySDK({
  clientId: process.env.HUMANITY_CLIENT_ID!,
  redirectUri: process.env.HUMANITY_REDIRECT_URI!,
  environment: process.env.HUMANITY_ENVIRONMENT ?? 'sandbox',
  clientSecret: process.env.HUMANITY_CLIENT_SECRET, // omit for public clients
});
```

## Example 1 — Kick off OAuth with PKCE

Use `buildAuthUrl` anywhere you can redirect the user (Next.js Route Handler shown here). Humanity issues the scopes that correspond to the presets you plan to evaluate. Persist the `codeVerifier` server-side for five minutes max.

```ts theme={null}
import { cookies } from 'next/headers';

export async function GET() {
  const sdk = new HumanitySDK({
    clientId: process.env.HUMANITY_CLIENT_ID!,
    redirectUri: `${process.env.APP_URL}/api/humanity/callback`,
  });

  const { url, codeVerifier } = sdk.buildAuthUrl({
    // Request OAuth scopes - see /concepts/scopes for the full list
    scopes: ['identity:read', 'identity:date_of_birth', 'kyc:read'],
    state: crypto.randomUUID(),
    additionalQueryParams: { prompt: 'consent' },
  });

  cookies().set('humanity_code_verifier', codeVerifier, {
    httpOnly: true,
    sameSite: 'lax',
    maxAge: 60 * 5,
  });

  return Response.redirect(url);
}
```

**Behind the scenes:** the helper generates a PKCE pair, resolves the correct `/oauth/authorize` URL for the environment, and uses the OAuth scopes (e.g. `identity:read`, `identity:date_of_birth`) that determine which presets your app can access.

## Example 2 — Exchange the code and persist the session

Once Humanity redirects back to your `redirect_uri`, grab the authorization `code` and the stored `code_verifier` and trade them for tokens. The helper returns a typed payload that already includes granted preset keys.

```ts theme={null}
import { cookies } from 'next/headers';

export async function GET(request: Request) {
  const code = new URL(request.url).searchParams.get('code');
  const codeVerifier = cookies().get('humanity_code_verifier')?.value;
  if (!code || !codeVerifier) throw new Error('Missing PKCE state');

  const sdk = new HumanitySDK({
    clientId: process.env.HUMANITY_CLIENT_ID!,
    redirectUri: process.env.HUMANITY_REDIRECT_URI!,
  });

  const token = await sdk.exchangeCodeForToken(code, codeVerifier);

  await persistSession({
    authorizationId: token.authorizationId,
    accessToken: token.accessToken,
    refreshToken: token.refreshToken,
    expiresAt: Date.now() + token.expiresIn * 1000,
    grantedPresets: token.presetKeys,
  });

  return Response.redirect('/dashboard');
}
```

* Refresh tokens are only returned for confidential clients—guard the storage location accordingly.
* `token.rateLimit` (if present) mirrors the `X-RateLimit-*` headers for your own observability.

## Example 3 — Gate features by presets

`verifyPreset` and `verifyPresets` hydrate evidence payloads and map rate-limit metadata for you. Use them immediately after sign-in or every time the user attempts to unlock a sensitive action.

```ts theme={null}
const sdk = new HumanitySDK({ /* ... */ });

const verification = await sdk.verifyPresets({
  accessToken: session.accessToken,
  presets: ['is_human', 'age_over_21', 'country_of_residence'],
});

const failedChecks = verification.results.filter(
  (preset) => preset.status !== 'valid' || !preset.value,
);
const failedErrors = verification.errors.map((error) => ({
  preset: error.preset,
  reason: error.error.error_description ?? error.error.error,
}));

if (failedChecks.length || failedErrors.length) {
  return {
    allowed: false,
    failures: [
      ...failedChecks.map((preset) => ({
        preset: preset.preset,
        status: preset.status,
        expiresAt: preset.expiresAt,
      })),
      ...failedErrors,
    ],
  };
}

enableHighRiskFeature();
```

Notes:

* Maximum of 10 presets per batch request; the helper enforces this before hitting the API.
* `verification.results` includes evidence metadata, timestamps, and rate-limit context for audit logging, while `verification.errors` captures API-side failures.

## Example 4 — Poll credential and authorization feeds

Feeds expose incremental changes so you can keep downstream systems synchronized. Store the `cursor` (or `updatedSince`) and pass it back to pick up where you left off.

```ts theme={null}
const sdk = new HumanitySDK({ /* ... */ });

const credentials = await sdk.pollCredentialUpdates(session.accessToken, {
  updatedSince: lastCredentialSyncIso,
  limit: 50,
});

await processCredentialChanges(credentials.items);
await persistCursor('credentials', credentials.cursor ?? credentials.latestUpdatedAt);

const authorizations = await sdk.pollAuthorizationUpdates(session.accessToken, {
  status: 'revoked',
  updatedSince: lastAuthorizationSyncIso,
});

await processAuthorizationChanges(authorizations.items);
await persistCursor('authorizations', authorizations.cursor ?? authorizations.latestUpdatedAt);
```

* The SDK validates `limit` (1–100) so you fail fast instead of receiving a 4xx from the API.
* Both helpers surface `rateLimit` data; honor `remaining` and `reset` to avoid throttling.

## Example 5 — Proactively revoke access

Revoke a single token, a batch of refresh tokens, or every authorization tied to a user. Set `cascade: true` to clear refresh tokens that belong to the same authorization automatically.

```ts theme={null}
const sdk = new HumanitySDK({ /* ... */ });

await sdk.revokeTokens({
  token: session.refreshToken,
  tokenTypeHint: 'refresh_token',
  cascade: true,
});

await deleteSession(session.id);
```

You can also pass `tokens: string[]` to revoke multiple credentials in one request or supply an `authorizationId` to wipe an entire consent grant.

## Example 6 — Leverage discovery + health endpoints

When you boot your app, call discovery once to warm up the preset registry and know which scopes are currently available. Use `healthcheck`/`readiness` for alerting.

```ts theme={null}
const sdk = new HumanitySDK({ /* ... */ });

const configuration = await sdk.getConfiguration();
console.log('Active presets', configuration.presets.map((preset) => preset.key));

const status = await sdk.readiness();
if (status.status !== 'ready') {
  throw new Error('Humanity API is not ready, aborting startup');
}
```

* `getConfiguration(true)` forces a refresh if you suspect the cache is stale.
* `healthcheck` is a lightweight liveness probe; `readiness` asserts dependencies such as storage and preset registries.

## Example 7 — Generate JWTs with your secret key

After validating a Humanity access token, issue your own application JWT. This decouples your session management from Humanity tokens.

```ts theme={null}
import { HumanitySDK } from '@humanity-org/connect-sdk';
import * as jose from 'jose';

const APP_JWT_SECRET = new TextEncoder().encode(process.env.APP_JWT_SECRET!);

export async function POST(request: Request) {
  const { code, codeVerifier } = await request.json();

  const sdk = new HumanitySDK({
    clientId: process.env.HUMANITY_CLIENT_ID!,
    redirectUri: process.env.HUMANITY_REDIRECT_URI!,
    clientSecret: process.env.HUMANITY_CLIENT_SECRET,
  });

  // Exchange code for Humanity tokens
  const humanityToken = await sdk.exchangeCodeForToken(code, codeVerifier);

  // Verify the user is human
  const verification = await sdk.verifyPreset('isHuman', humanityToken.accessToken);

  // Issue your own app JWT
  const now = Math.floor(Date.now() / 1000);
  const expiresIn = 3600;

  const appToken = await new jose.SignJWT({
    sub: humanityToken.authorizationId,
    iss: 'my-app',
    aud: 'my-app',
    iat: now,
    exp: now + expiresIn,
    humanityUserId: humanityToken.authorizationId,
    isHuman: verification.status === 'valid' && verification.value === true,
    scopes: humanityToken.grantedScopes,
    presets: [verification.preset],
  })
    .setProtectedHeader({ alg: 'HS256' })
    .sign(APP_JWT_SECRET);

  return Response.json({
    token: appToken,
    expiresAt: new Date((now + expiresIn) * 1000).toISOString(),
    isHuman: verification.value,
  });
}
```

To verify your JWT later:

```ts theme={null}
import * as jose from 'jose';

const APP_JWT_SECRET = new TextEncoder().encode(process.env.APP_JWT_SECRET!);

async function verifyAppToken(token: string) {
  try {
    const { payload } = await jose.jwtVerify(token, APP_JWT_SECRET, {
      issuer: 'my-app',
      audience: 'my-app',
    });
    return payload;
  } catch {
    return null;
  }
}
```

## Example 8 — Decode JWT payloads

Extract claims from any JWT without verification (useful for reading Humanity tokens after they've been validated):

```ts theme={null}
function decodeJwtPayload(token: string): Record<string, unknown> | null {
  try {
    const parts = token.split('.');
    if (parts.length < 2) return null;

    const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
    const padded = payload.padEnd(Math.ceil(payload.length / 4) * 4, '=');
    const decoded = Buffer.from(padded, 'base64').toString('utf-8');

    return JSON.parse(decoded);
  } catch {
    return null;
  }
}
```

<Warning>
  Always verify tokens before trusting their contents. Use `decodeJwtPayload` only to read claims from tokens already validated by the Humanity API.
</Warning>

## Example 9 — Refresh Humanity tokens

Refresh tokens before they expire:

```ts theme={null}
import { HumanitySDK } from '@humanity-org/connect-sdk';

export async function POST(request: Request) {
  const { refreshToken } = await request.json();

  const sdk = new HumanitySDK({
    clientId: process.env.HUMANITY_CLIENT_ID!,
    redirectUri: process.env.HUMANITY_REDIRECT_URI!,
    clientSecret: process.env.HUMANITY_CLIENT_SECRET,
  });

  const refreshed = await sdk.refreshAccessToken(refreshToken);

  return Response.json({
    accessToken: refreshed.accessToken,
    refreshToken: refreshed.refreshToken,
    expiresIn: refreshed.expiresIn,
    grantedScopes: refreshed.grantedScopes,
  });
}
```

## Example 10 — PKCE state and nonce verification

The SDK provides static helpers for verifying OAuth security parameters:

```ts theme={null}
import { HumanitySDK } from '@humanity-org/connect-sdk';

export async function GET(request: Request) {
  const url = new URL(request.url);
  const code = url.searchParams.get('code');
  const callbackState = url.searchParams.get('state');

  // Retrieve your stored session (from cookie, KV, etc.)
  const storedState = '...';    // from your session storage
  const storedNonce = '...';    // from your session storage
  const codeVerifier = '...';   // from your session storage

  // Verify state matches
  if (!HumanitySDK.verifyState(storedState, callbackState)) {
    return Response.json({ error: 'state_mismatch' }, { status: 400 });
  }

  const sdk = new HumanitySDK({
    clientId: process.env.HUMANITY_CLIENT_ID!,
    redirectUri: process.env.HUMANITY_REDIRECT_URI!,
  });

  const token = await sdk.exchangeCodeForToken(code!, codeVerifier);

  // Verify nonce in ID token (if openid scope was requested)
  if (token.idToken && storedNonce) {
    const idPayload = decodeJwtPayload(token.idToken);
    if (!HumanitySDK.verifyNonce(storedNonce, idPayload?.nonce as string)) {
      return Response.json({ error: 'nonce_mismatch' }, { status: 400 });
    }
  }

  return Response.json({
    accessToken: token.accessToken,
    authorizationId: token.authorizationId,
  });
}
```

## Example 11 — Server-to-server token acquisition

Get tokens for users who have already authorized your app without a browser:

```ts theme={null}
import { HumanitySDK } from '@humanity-org/connect-sdk';

export async function POST(request: Request) {
  const { email, userId, evmAddress } = await request.json();

  const sdk = new HumanitySDK({
    clientId: process.env.HUMANITY_CLIENT_ID!,
    redirectUri: process.env.HUMANITY_REDIRECT_URI!,
    clientSecret: process.env.HUMANITY_CLIENT_SECRET!,
  });

  // Get token by email
  if (email) {
    const result = await sdk.getClientUserToken({
      clientSecret: process.env.HUMANITY_CLIENT_SECRET!,
      email,
    });
    return Response.json(result);
  }

  // Get token by user ID
  if (userId) {
    const result = await sdk.getClientUserToken({
      clientSecret: process.env.HUMANITY_CLIENT_SECRET!,
      userId,
    });
    return Response.json(result);
  }

  // Get token by EVM wallet address
  if (evmAddress) {
    const result = await sdk.getClientUserToken({
      clientSecret: process.env.HUMANITY_CLIENT_SECRET!,
      evmAddress,
    });
    return Response.json(result);
  }

  return Response.json({ error: 'Provide email, userId, or evmAddress' }, { status: 400 });
}
```

<Info>
  This is useful for background jobs, webhooks, and server-to-server scenarios where you need to verify presets without a browser session.
</Info>

## Example 12 — Fetch user profile via preset

Get user profile information using the `humanity_user` preset:

```ts theme={null}
import { HumanitySDK } from '@humanity-org/connect-sdk';

export async function GET(request: Request) {
  const accessToken = request.headers.get('authorization')?.replace('Bearer ', '');
  if (!accessToken) {
    return Response.json({ error: 'Missing token' }, { status: 401 });
  }

  const sdk = new HumanitySDK({
    clientId: process.env.HUMANITY_CLIENT_ID!,
    redirectUri: process.env.HUMANITY_REDIRECT_URI!,
  });

  const result = await sdk.verifyPreset('humanity_user', accessToken);

  return Response.json({
    preset: result.preset,
    value: result.value,
    status: result.status,
    evidence: {
      sub: result.evidence?.sub,
      humanityId: result.evidence?.humanity_id,
      email: result.evidence?.email,
      emailVerified: result.evidence?.email_verified,
      walletAddress: result.evidence?.wallet_address,
      scopes: result.evidence?.scopes,
    },
    expiresAt: result.expiresAt,
  });
}
```

## Example 13 — Query Engine for declarative checks

Use the Query Engine for complex eligibility checks without hardcoding preset names:

```ts theme={null}
import { HumanitySDK } from '@humanity-org/connect-sdk';

const sdk = new HumanitySDK({
  clientId: process.env.HUMANITY_CLIENT_ID!,
  redirectUri: process.env.HUMANITY_REDIRECT_URI!,
});

// Simple age check
const ageCheck = await sdk.evaluateQuery({
  accessToken: session.accessToken,
  query: {
    check: { claim: 'identity.age', operator: '>=', value: 21 }
  }
});

if (!ageCheck.passed) {
  return Response.json({ error: 'Must be 21 or older' }, { status: 403 });
}

// Compound eligibility: US/CA resident AND KYC passed AND high net worth
const eligibility = await sdk.evaluateQuery({
  accessToken: session.accessToken,
  query: {
    policy: {
      allOf: [
        { check: { claim: 'kyc.passed', operator: '==', value: true } },
        { check: { claim: 'identity.country', operator: 'in', value: ['US', 'CA'] } },
        {
          policy: {
            anyOf: [
              { check: { claim: 'financial.net_worth', operator: '>=', value: 1000000 } },
              { check: { claim: 'financial.income', operator: '>=', value: 200000 } }
            ]
          }
        }
      ]
    }
  }
});

if (eligibility.passed) {
  enableAccreditedInvestorFeatures();
}

// Data extraction via projection
const userData = await sdk.evaluateQuery({
  accessToken: session.accessToken,
  query: {
    projections: [
      { claim: 'identity.email', lens: 'pluck' },
      { claim: 'identity.country', lens: 'pluck' },
      { claim: 'kyc.passed', lens: 'pluck' }
    ]
  }
});

console.log('User data:', userData.data);
// { "identity.email": "user@example.com", "identity.country": "US", "kyc.passed": true }
```

See the [Query Engine documentation](/api-reference/endpoint/queries-evaluate) for all operators.

## Dropping down to the generated client

All helpers ultimately call the fully generated REST client exposed as `sdk.client`. Reach for it when you need a controller method that does not yet have a convenience wrapper.

```ts theme={null}
const preset = await sdk.client.presets.getPreset('humanity_user', {
  headers: { Authorization: `Bearer ${session.accessToken}` },
});
```

This keeps the SDK future-proof: new API routes appear in the client immediately after pulling the latest release, while higher-level adapters can be layered on as needed.
