A human check.
A backend guarantee.
Start a verification transaction on your server, open the hosted flow, then redeem and consume the result before performing a protected action.
Protect account registration
- Create an application in the developer console. Register the exact callback URL.
- Create an application API key with
verification:createandverification:exchange. Keep it on your backend. - Generate a transaction and store its state, nonce, PKCE verifier, action reference and expiry in your backend, bound to the current browser.
- Open the returned
verificationUrlwith the React widget or a redirect. - Validate callback state and issuer, exchange the one-time code and validate the signed assertion.
- Atomically consume
verificationIdtogether with the protected action. A unique database constraint must prevent reuse.
import { ImReal, createTransaction } from '@imreal/server';
const imreal = new ImReal({
origin: process.env.IMREAL_ORIGIN,
apiKey: process.env.IMREAL_API_KEY,
applicationId: process.env.IMREAL_APPLICATION_ID,
environment: 'sandbox', // production is the SDK default
});
const tx = createTransaction();
// Save tx.codeVerifier privately, with the browser session.
const session = await imreal.createSession({
assurance: 'enrolled_human',
action: 'account.register',
actionReference: pendingRegistration.id,
redirectUri: 'https://your-app.com/verification/callback',
state: tx.state,
nonce: tx.nonce,
codeChallenge: tx.codeChallenge,
codeChallengeMethod: 'S256',
});The packages are included in this repository as workspaces. They are not published to npm yet. Build and install the workspace packages locally until a reviewed release is published.
Add the React component
<ImRealWidget
startEndpoint="/api/verification/start"
completionEndpoint="/api/verification/status"
verificationOrigin={IMREAL_ORIGIN}
csrfToken={csrfToken}
onComplete={() => refreshBackendVerificationStatus()}
/>The start endpoint returns verificationUrl and the transaction state. After backend redemption, the callback page can post an imreal:complete message containing state and an opaque artifact to the customer origin. The widget validates both message origin and popup source. If the browser blocks the popup, it uses a redirect.
Validate before the protected operation
const proof = await imreal.exchange({
code, codeVerifier: transaction.codeVerifier,
redirectUri: transaction.redirectUri,
action: 'account.register',
actionReference: transaction.actionReference,
nonce: transaction.nonce,
assurance: 'enrolled_human',
});
await db.transaction(async tx => {
// Unique constraint on verificationId rejects replay.
await tx.insert(consumedProofs).values({
verificationId: proof.verificationId,
});
await tx.insert(accounts).values(validatedRegistration);
});Only perform the action inside the successful database transaction. Never use a hidden input, widget state or browser callback as authorization. The working demo includes this enforcement.
Choose the evidence you need
enrolled_human requires active prior enrollment and a WebAuthn credential with local user verification. It proves control of that enrolled credential. fresh_presence requires a new provider-validated live-person check for this transaction within the configured freshness window.
Identity proofing and unique-person claims are not supported in this release. The API rejects unsupported policies. Device verification may use a PIN, password or biometric; the response does not claim which modality was used.
Connect a coding agent
The MCP endpoint is /api/mcp on the deployment origin. It uses the official MCP TypeScript v2 SDK, current Streamable HTTP transport and an external OAuth issuer. Protected-resource discovery is available at /.well-known/oauth-protected-resource.
“Integrate I’m Real into this application and protect
account registration. Use a sandbox, validate the backend
action, and report any unresolved production setup.”Agents can inspect applications, obtain guidance, provision authorized sandbox configuration and inspect pending test transactions. Read access is the default. Production configuration and secret creation stay in the dashboard; MCP cannot create successful human evidence.
The repository includes skills/imreal-integration/SKILL.md and a CLI. OAuth setup is required before remote MCP access; API keys cannot substitute for MCP OAuth.
Provision customers through your platform
A partner credential can provision customer organizations and their applications without a separate developer account for each customer. The credential is restricted to its partner hierarchy. Human credentials remain centrally managed; applications receive different pseudonymous subjects.
const org = await partner.createOrganization('Customer Ltd');
const app = await partner.createApplication({
organizationId: org.id,
name: 'Customer portal',
redirectUris: ['https://customer.com/verify/callback'],
policy: 'enrolled_human',
});Provider attempts, initial enrollments, returning verification and fresh checks are separate usage events. Wholesale aggregation is available through the usage API. Payment collection and partner retail billing are external integrations.
API reference
The running API generates its OpenAPI document from request schemas. Management routes require a scoped API credential or authenticated console session. Hosted-flow routes use an expiring browser transaction and CSRF protection.
| Operation | Endpoint |
|---|---|
| Create session | POST /v1/verification-sessions |
| Inspect session | GET /v1/verification-sessions/{id} |
| Redeem code | POST /v1/verification-sessions/exchange |
| Applications | GET / POST /v1/applications |
| Configure application | PATCH /v1/applications/{id} |
| Manage credentials | /v1/applications/{id}/keys |
| Register domains | /v1/applications/{id}/domains |
| Webhooks | /v1/applications/{id}/webhooks |
| Partner customers | GET / POST /v1/organizations |
| Usage | GET /v1/usage |
Errors
Errors use { error: { code, message, requestId } }. Handle expired, already_used, invalid_evidence, policy_unsatisfied, provider_unavailable and quota_exceeded with an explicit retry or failure path. Never downgrade assurance on failure.
Run locally
pnpm install --frozen-lockfile
pnpm exec tsx scripts/keys.ts
docker compose -f infra/docker/compose.yaml up -d
pnpm db:migrate
pnpm db:seed
pnpm dev
# Open http://localhost:3000/demoUse Node 24 or later. The generated environment file is private and ignored by Git. PostgreSQL and Redis remain local. The README describes tests, live provider configuration, signing-key rotation and Kubernetes deployment.