Skip to content

Identity Check Integration Guide

This guide walks you through integrating LMIF identity checks into your avatar creation flow.

The identity check should happen:

  1. Before avatar creation - Check when users submit avatar details
  2. On existing content - Scan your existing avatars periodically
  3. On webhook events - Re-check when new identities are boxed
  1. Add the check to your avatar creation flow

    Insert the identity check before saving the avatar to your database:

    // Before
    async function createAvatar(data: AvatarData) {
    const avatar = await db.avatars.create(data);
    return avatar;
    }
    // After
    async function createAvatar(data: AvatarData) {
    // Check identity first
    const check = await lmif.identity.check({
    name: data.name,
    imageUrl: data.imageUrl,
    description: data.description,
    });
    // Handle the result
    if (check.action === 'BLOCK') {
    throw new AvatarBlockedError(
    'This identity is protected and cannot be used',
    check.matchedIdentity
    );
    }
    // Store LMIF data with avatar
    const avatar = await db.avatars.create({
    ...data,
    lmif: {
    boxId: check.matchedIdentity?.boxId,
    policy: check.policy,
    royaltyRate: check.policyDetails?.royaltyRate,
    checkedAt: new Date(),
    },
    });
    return avatar;
    }
  2. Handle all policy types

    Create handlers for each policy type:

    async function handleIdentityCheck(check: IdentityCheckResult) {
    switch (check.action) {
    case 'ALLOW':
    return { proceed: true };
    case 'BLOCK':
    return {
    proceed: false,
    error: 'This identity is protected',
    suggestion: 'Try creating an original character instead',
    };
    case 'REQUIRE_LICENSE':
    return {
    proceed: false,
    requiresLicense: true,
    licenseUrl: `/license/${check.matchedIdentity.boxId}`,
    licenseTiers: await getLicenseTiers(check.matchedIdentity.boxId),
    };
    case 'TRACK_REVENUE':
    return {
    proceed: true,
    tracking: {
    enabled: true,
    boxId: check.matchedIdentity.boxId,
    royaltyRate: check.policyDetails.royaltyRate,
    },
    };
    case 'VERIFY_COMMERCIAL':
    return {
    proceed: 'pending',
    question: 'Will this avatar be used for commercial purposes?',
    onYes: { action: 'BLOCK' },
    onNo: { action: 'ALLOW', tracking: 'non_commercial' },
    };
    case 'REVIEW_PARODY':
    return {
    proceed: 'pending',
    question: 'Is this a parody or satirical avatar?',
    requirements: 'Parody avatars must include a clear disclaimer',
    };
    default:
    return { proceed: false, error: 'Unknown policy' };
    }
    }
  3. Build the UI for blocked identities

    When an identity is blocked, show a helpful message:

    function AvatarBlockedDialog({ identity }: { identity: MatchedIdentity }) {
    return (
    <Dialog>
    <DialogTitle>Identity Protected</DialogTitle>
    <DialogContent>
    <p>
    <strong>{identity.name}</strong> has protected their identity.
    AI avatars using this likeness are not allowed.
    </p>
    <p>
    <Link href="/create/original">Create an original character instead</Link>
    </p>
    </DialogContent>
    </Dialog>
    );
    }
  4. Build the licensing flow

    For LICENSE policy, guide users through obtaining a license:

    function LicenseRequired({ boxId }: { boxId: string }) {
    const [tiers, setTiers] = useState<LicenseTier[]>([]);
    useEffect(() => {
    loadLicenseTiers(boxId).then(setTiers);
    }, [boxId]);
    return (
    <Dialog>
    <DialogTitle>License Required</DialogTitle>
    <DialogContent>
    <p>This identity requires a license. Choose a tier:</p>
    {tiers.map((tier) => (
    <LicenseTierCard
    key={tier.name}
    tier={tier}
    onSelect={() => requestLicense(boxId, tier.name)}
    />
    ))}
    </DialogContent>
    </Dialog>
    );
    }
  5. Track revenue for MONETIZE policy

    When TRACK_REVENUE is returned, enable usage tracking:

    class AvatarUsageTracker {
    async trackInteraction(avatarId: string, type: 'message' | 'tip' | 'subscription') {
    const avatar = await db.avatars.get(avatarId);
    if (!avatar.lmif?.boxId) return;
    // Store locally for batch reporting
    await db.usageMetrics.increment({
    avatarId,
    boxId: avatar.lmif.boxId,
    type,
    date: new Date().toISOString().split('T')[0],
    });
    }
    async reportMonthlyUsage() {
    const metrics = await db.usageMetrics.getByMonth(getCurrentMonth());
    for (const avatarMetrics of metrics) {
    const license = await lmif.licenses.getByBox(avatarMetrics.boxId);
    await lmif.licenses.reportUsage(license.id, {
    period: getCurrentMonth(),
    metrics: avatarMetrics.counts,
    revenue: await calculateRevenue(avatarMetrics),
    });
    }
    }
    }

The system requires both image AND name to match. “John Smith” alone won’t trigger a match:

// This won't match a protected identity
const check = await lmif.identity.check({
name: 'John Smith', // Common name
imageUrl: 'https://example.com/generic-person.jpg', // Generic image
});
// check.isBoxed === false (probably)
// This WILL match if John Smith the celebrity is protected
const check2 = await lmif.identity.check({
name: 'John Smith',
imageUrl: 'https://example.com/john-smith-celebrity.jpg', // Actual likeness
});
// check2.isBoxed === true (if protected)

Handle parody detection appropriately:

if (check.action === 'REVIEW_PARODY') {
// Ask user if this is parody
const isParody = await promptUser('Is this a parody avatar?');
if (isParody) {
// Check if policy allows parody
if (check.policyDetails?.allowedUses?.includes('parody')) {
// Create with parody disclaimer
await createAvatar({
...data,
isParody: true,
disclaimer: 'This is a parody account and is not affiliated with the real person.',
});
} else {
throw new Error('This identity does not allow parody content');
}
}
}

Handle API errors gracefully:

async function safeIdentityCheck(name: string, imageUrl: string) {
try {
return await lmif.identity.check({ name, imageUrl });
} catch (error) {
if (error.code === 'RATE_LIMITED') {
// Implement queuing or retry
await addToCheckQueue({ name, imageUrl });
return { action: 'PENDING', message: 'Check queued' };
}
// For other errors, decide on fail-open or fail-closed
console.error('Identity check failed:', error);
// Fail-closed (safer) - block if we can't check
return { action: 'BLOCK', reason: 'Unable to verify identity' };
// Fail-open (riskier) - allow if we can't check
// return { action: 'ALLOW', warning: 'Unable to verify identity' };
}
}

When you first integrate LMIF, scan your existing avatars:

async function scanExistingAvatars() {
const avatars = await db.avatars.findAll({ lmif: null });
// Use batch API for efficiency
const batches = chunk(avatars, 100);
for (const batch of batches) {
const results = await lmif.identity.checkBatch(
batch.map((a) => ({
id: a.id,
name: a.name,
imageUrl: a.imageUrl,
}))
);
for (const result of results) {
await db.avatars.update(result.id, {
lmif: {
boxId: result.matchedIdentity?.boxId,
policy: result.policy,
checkedAt: new Date(),
},
});
// Handle violations
if (result.isBoxed && result.action === 'BLOCK') {
await handleExistingViolation(result.id, result);
}
}
}
}

Test your integration with sandbox identities:

describe('Identity Check Integration', () => {
it('blocks protected identities', async () => {
const result = await createAvatar({
name: 'Test Celebrity A', // Sandbox: Always blocked
imageUrl: 'https://example.com/test.jpg',
});
expect(result.error).toBe('This identity is protected');
});
it('tracks revenue for monetized identities', async () => {
const result = await createAvatar({
name: 'Test Celebrity B', // Sandbox: Monetized
imageUrl: 'https://example.com/test.jpg',
});
expect(result.tracking.enabled).toBe(true);
expect(result.tracking.royaltyRate).toBe(0.10);
});
it('handles rate limits gracefully', async () => {
// Trigger rate limit in sandbox
for (let i = 0; i < 100; i++) {
await lmif.identity.check({ name: 'Test', imageUrl: 'https://...' });
}
// Should handle gracefully
const result = await createAvatar({ name: 'Test', imageUrl: '...' });
expect(result.action).toBe('PENDING');
});
});