Developer Documentation
Complete guide to integrating RebelVerify into your application.
Installation
Install the RebelVerify SDK using npm or yarn:
npm install @elyndra-studios/rebelverify
# or
yarn add @elyndra-studios/rebelverifyFor TypeScript projects, types are included automatically. No additional type definitions needed.
Note: The SDK works in both browser and Node.js environments.
Quick Start
Get started with RebelVerify in just a few lines of code:
import { rebelVerify } from '@elyndra-studios/rebelverify';
// Initialize the SDK
await rebelVerify.initialize();
// Submit ID document (File, Blob, or data URL)
const result = await rebelVerify.submitID(documentFile);
if (result.success) {
const { verificationToken } = result.data;
// Generate proof for minimum age of 18
const proof = await rebelVerify.generateProof(verificationToken, 18);
if (proof.success) {
// Send proof to your backend for verification
console.log('Proof generated:', proof.data);
}
}What Happens Here?
- Initialize: Sets up cryptographic keys and ZKP service
- Submit ID: Runs client-side authenticity heuristics and OCR to extract birth date
- Generate Proof: Creates zero-knowledge proof without exposing birth date
- Verify: Send proof plus authenticity payload to backend for verification & billing
Authentication
RebelVerify uses API keys for backend authentication. Get your API key from the dashboard or create one programmatically.
Getting an API Key
API keys are required for backend verification endpoints. You can create them via the API or dashboard.
Using API Keys
Include your API key in the Authorization header:
// Verify a proof on backend
const response = await fetch('https://api.rebelverify.com/api/v1/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer YOUR_API_KEY`,
'Content-Type': 'application/json',
'X-RebelVerify-Mode': 'sandbox' // or 'production' for live keys
},
body: JSON.stringify({
proof: proof.proof,
publicSignals: proof.publicSignals,
signature: proof.signature
})
});
const result = await response.json();
if (result.valid) {
console.log('Verification successful!');
}Authenticity Payload: The SDK returns a DocumentAuthenticity object containing score, status (pass | review | fail), and heuristic signals. Use this to trigger manual review for borderline uploads.
Environment Header: Set X-RebelVerify-Mode to sandbox or production to match your API key. Production backends reject sandbox traffic automatically.
Security: Never expose your API key in client-side code. Always use it on your backend server.
initialize()
Initializes the SDK with cryptographic keys and ZKP service. Must be called before using any other SDK methods.
Signature
await rebelVerify.initialize(privateKey?: string): Promise<void>Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
privateKey | string | No | Existing private key as hex string. If not provided, generates a new key pair or uses stored key. |
Returns
Promise that resolves when initialization is complete. Throws an error if initialization fails.
Examples
// Initialize with new keys (recommended for first-time users)
await rebelVerify.initialize();
// Initialize with existing key (for returning users)
await rebelVerify.initialize('your-private-key-hex-string');
// Initialize in React component
useEffect(() => {
const initSDK = async () => {
try {
await rebelVerify.initialize();
console.log('SDK ready!');
} catch (error) {
console.error('Failed to initialize:', error);
}
};
initSDK();
}, []);Note: The SDK automatically stores your private key securely. You only need to provide it if you want to use an existing key from another device.
submitID()
Submits an ID document for processing. Runs client-side authenticity heuristics (sharpness, lighting, aspect ratio) and uses privacy-preserving OCR to extract the birth date. All analysis happens locally in the browser.
Signature
await rebelVerify.submitID(
idDocument: string | File | Blob,
metadata?: Record<string, any>
): Promise<VerificationResult>Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
idDocument | string | File | Blob | Yes | ID document as File object, Blob, or data URL string (e.g., "data:image/jpeg;base64,...") |
metadata | Record<string, any> | No | Additional metadata about the document (documentType, etc.) |
Returns
interface VerificationResult {
success: boolean;
error?: string;
data?: {
verificationToken: string;
birthYear: number;
documentType: string;
ocrConfidence?: number;
authenticity?: DocumentAuthenticity;
};
}Examples
// From file input
const fileInput = document.querySelector('input[type="file"]');
const file = fileInput.files[0];
const result = await rebelVerify.submitID(file);
if (result.success) {
const { verificationToken, birthYear, authenticity } = result.data;
console.log('Document processed. Birth year:', birthYear);
if (authenticity) {
console.log('Authenticity score:', authenticity.score, 'status:', authenticity.status);
}
}
// From data URL
const dataUrl = 'data:image/jpeg;base64,/9j/4AAQSkZJRg==';
const result = await rebelVerify.submitID(dataUrl, {
documentType: 'drivers-license'
});
// From Blob
const blob = await fetch('https://example.com/id.jpg').then(r => r.blob());
const result = await rebelVerify.submitID(blob);Privacy: OCR processing happens entirely on the client side. The document image never leaves the user's device.
generateProof()
Generates a zero-knowledge proof for age verification. The proof demonstrates that the user meets the minimum age requirement without revealing their actual birth date.
Signature
await rebelVerify.generateProof(
verificationToken: string,
minAge: number
): Promise<VerificationResult>Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
verificationToken | string | Yes | Token received from submitID() |
minAge | number | Yes | Minimum age requirement (e.g., 18, 21, 25) |
Returns
interface VerificationResult {
success: boolean;
error?: string;
data?: {
proof: AgeProof;
proofId: string;
minAge: number;
};
}
interface AgeProof {
proof: any; // ZK-SNARK proof object
publicSignals: string[]; // Public inputs (no private data)
signature: string; // Cryptographic signature
proofId: string; // Unique proof identifier
timestamp: number; // Proof generation timestamp
verificationKey: any; // Verification key for proof validation
authenticity?: DocumentAuthenticity; // Authenticity heuristics payload
}Examples
// Generate proof for 18+ verification
const submitResult = await rebelVerify.submitID(documentFile);
if (submitResult.success) {
const proof = await rebelVerify.generateProof(
submitResult.data.verificationToken,
18
);
if (proof.success) {
// Send to backend for verification
await sendToBackend(proof.data);
}
}
// Generate proof for 21+ verification
const proof = await rebelVerify.generateProof(verificationToken, 21);
// Handle age requirement not met
try {
const proof = await rebelVerify.generateProof(token, 25);
} catch (error) {
if (error.message.includes('minimum age')) {
console.log('User does not meet age requirement');
}
}Privacy Guarantee: The proof does not contain the user's birth date or birth year. Only the minimum age requirement is verified.
exportCredential()
Exports a verification credential as a portable string. Users can save this credential and reuse it across multiple platforms without re-verifying.
Signature
await rebelVerify.exportCredential(
proofId: string
): Promise<VerificationResult>Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
proofId | string | Yes | The proof ID from generateProof() result |
Returns
interface VerificationResult {
success: boolean;
error?: string;
data?: {
credential: string; // Base64-encoded credential string
proofId: string; // Original proof ID
format: 'base64'; // Credential format
instructions: string; // User instructions
};
}Examples
// Generate proof first
const proof = await rebelVerify.generateProof(verificationToken, 18);
if (proof.success) {
// Export credential for reuse
const exportResult = await rebelVerify.exportCredential(proof.data.proofId);
if (exportResult.success) {
const { credential } = exportResult.data;
// Save credential for user (localStorage, database, etc.)
localStorage.setItem('rebelverify-credential', credential);
// Or download as file
const blob = new Blob([credential], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'rebelverify-credential.txt';
a.click();
}
}
// Import credential on another device/platform
const credential = localStorage.getItem('rebelverify-credential');
const importResult = await rebelVerify.importCredential(credential);
if (importResult.success) {
// Use the imported proof
const proof = importResult.data.proof;
}Portability: Credentials can be saved and used across different devices and platforms. Users verify once, use everywhere.
POST /api/v1/verify
Verify an age verification proof on your backend server. This endpoint validates the zero-knowledge proof and returns verification status.
Endpoint
POST https://api.rebelverify.com/api/v1/verifyAuthentication
Requires API key authentication via Bearer token in the Authorization header.
Request Body
{
"proof": {
"type": "enhanced-fallback-zkp" | "zokrates-zkp",
"commitment": string, // For enhanced fallback
"a": [string, string], // For ZoKrates ZKP
"b": [[string, string], [string, string]], // For ZoKrates ZKP
"c": [string, string], // For ZoKrates ZKP
"metadata": {
"currentYear": number,
"minAge": number,
"isEligible": boolean
}
},
"publicSignals": string[], // Public inputs (no private data)
"signature": string, // Cryptographic signature
"timestamp"?: number, // Optional timestamp
"authenticity"?: {
"status": "pass" | "review" | "fail",
"score": number, // 0 - 1
"signals": [
{
"id": string,
"label": string,
"passed": boolean,
"score"?: number,
"message"?: string
}
],
"warnings"?: string[]
},
"document"?: {
"type"?: string,
"ocrConfidence"?: number
}
}Response
// Success Response (200)
{
"valid": true,
"timestamp": "2025-01-01T00:00:00.000Z",
"minAge": 18,
"authenticity": {
"status": "pass",
"score": 0.92
},
"document": {
"type": "passport",
"ocrConfidence": 0.88
}
}
// Error Response (400)
{
"valid": false,
"error": "Invalid proof structure",
"timestamp": "2025-01-01T00:00:00.000Z"
}
// Duplicate Proof (400)
{
"valid": false,
"error": "This proof has already been used",
"timestamp": "2025-01-01T00:00:00.000Z"
}Examples
// Using fetch
const response = await fetch('https://api.rebelverify.com/api/v1/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
proof: proof.proof,
publicSignals: proof.publicSignals,
signature: proof.signature,
authenticity: proof.authenticity,
document: {
type: proof.documentType,
ocrConfidence: proof.ocrConfidence
}
})
});
const result = await response.json();
if (result.valid) {
console.log('User verified! Minimum age:', result.minAge);
} else {
console.error('Verification failed:', result.error);
}
// Using axios
import axios from 'axios';
const response = await axios.post(
'https://api.rebelverify.com/api/v1/verify',
{
proof: proof.proof,
publicSignals: proof.publicSignals,
signature: proof.signature,
authenticity: proof.authenticity,
document: {
type: proof.documentType,
ocrConfidence: proof.ocrConfidence
}
},
{
headers: {
'Authorization': `Bearer ${apiKey}`,
'X-RebelVerify-Mode': 'sandbox'
}
}
);
if (response.data.valid) {
// User is verified
}
// Using curl
curl -X POST https://api.rebelverify.com/api/v1/verify \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "X-RebelVerify-Mode: sandbox" \
-d '{
"proof": {...},
"publicSignals": [...],
"signature": "...",
"authenticity": {...},
"document": {...}
}'Error Codes
| Status Code | Description |
|---|---|
200 | Verification successful |
400 | Invalid proof, missing fields, or duplicate proof |
401 | Invalid or missing API key |
429 | Rate limit exceeded |
500 | Internal server error |
GET /api/v1/usage
Get usage statistics for your API key, including total verifications, revenue, and recent activity.
Endpoint
GET https://api.rebelverify.com/api/v1/usageAuthentication
Requires API key authentication via Bearer token in the Authorization header.
Response
{
"apiKeyId": "uuid",
"platformName": "Your Platform",
"totalVerifications": 1250,
"totalRevenue": 312.50,
"pricePerVerification": 0.25,
"recentVerifications": [
{
"id": "uuid",
"amount": 0.25,
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
}Examples
// Using fetch
const response = await fetch('https://api.rebelverify.com/api/v1/usage', {
headers: {
'Authorization': `Bearer ${apiKey}`
}
});
const stats = await response.json();
console.log('Total verifications:', stats.totalVerifications);
console.log('Total revenue: £', stats.totalRevenue);
// Using axios
import axios from 'axios';
const response = await axios.get(
'https://api.rebelverify.com/api/v1/usage',
{
headers: {
'Authorization': `Bearer ${apiKey}`
}
}
);
const stats = response.data;
// Using curl
curl -X GET https://api.rebelverify.com/api/v1/usage \
-H "Authorization: Bearer YOUR_API_KEY"React Integration
Complete example of integrating RebelVerify into a React application with proper error handling and state management.
import React, { useState, useEffect } from 'react';
import { rebelVerify } from '@elyndra-studios/rebelverify';
function AgeVerification() {
const [proof, setProof] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [verificationToken, setVerificationToken] = useState(null);
// Initialize SDK on mount
useEffect(() => {
const initSDK = async () => {
try {
await rebelVerify.initialize();
console.log('SDK initialized');
} catch (err) {
setError('Failed to initialize SDK');
}
};
initSDK();
}, []);
const handleFileUpload = async (event) => {
const file = event.target.files?.[0];
if (!file) return;
setLoading(true);
setError(null);
try {
// Submit ID document
const submitResult = await rebelVerify.submitID(file);
if (!submitResult.success) {
setError(submitResult.error || 'Failed to process document');
return;
}
setVerificationToken(submitResult.data.verificationToken);
} catch (err) {
setError(err.message || 'An error occurred');
} finally {
setLoading(false);
}
};
const handleGenerateProof = async (minAge = 18) => {
if (!verificationToken) {
setError('Please upload a document first');
return;
}
setLoading(true);
setError(null);
try {
const proofResult = await rebelVerify.generateProof(verificationToken, minAge);
if (!proofResult.success) {
setError(proofResult.error || 'Failed to generate proof');
return;
}
setProof(proofResult.data);
// Send to backend
await verifyWithBackend(proofResult.data);
} catch (err) {
setError(err.message || 'An error occurred');
} finally {
setLoading(false);
}
};
const verifyWithBackend = async (proofData) => {
try {
const response = await fetch('/api/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
proof: proofData.proof.proof,
publicSignals: proofData.proof.publicSignals,
signature: proofData.proof.signature
})
});
const result = await response.json();
if (result.valid) {
console.log('Verified!');
}
} catch (err) {
console.error('Backend verification failed:', err);
}
};
return (
<div className="age-verification">
<input
type="file"
accept="image/*,.pdf"
onChange={handleFileUpload}
disabled={loading}
/>
{verificationToken && (
<div>
<button
onClick={() => handleGenerateProof(18)}
disabled={loading}
>
Verify 18+
</button>
<button
onClick={() => handleGenerateProof(21)}
disabled={loading}
>
Verify 21+
</button>
</div>
)}
{loading && <p>Processing...</p>}
{error && <p className="error">{error}</p>}
{proof && <p>Verification successful!</p>}
</div>
);
}
export default AgeVerification;Using with Next.js
// app/api/verify/route.ts (Next.js App Router)
import { NextRequest, NextResponse } from 'next/server';
export async function POST(request: NextRequest) {
const body = await request.json();
const apiKey = process.env.REBELVERIFY_API_KEY;
const response = await fetch('https://api.rebelverify.com/api/v1/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(body)
});
const result = await response.json();
return NextResponse.json(result);
}Error Handling
Always check the success property and handle errors appropriately. The SDK returns structured error messages to help with debugging.
Basic Error Handling
const result = await rebelVerify.submitID(file);
if (!result.success) {
console.error('Error:', result.error);
// Handle error (show message to user, retry, etc.)
return;
}
// Continue with successful result
const { verificationToken } = result.data;Comprehensive Error Handling
async function verifyAge(file: File, minAge: number) {
try {
// Initialize SDK
await rebelVerify.initialize();
} catch (error) {
return {
success: false,
error: 'Failed to initialize SDK. Please refresh and try again.'
};
}
try {
// Submit document
const submitResult = await rebelVerify.submitID(file);
if (!submitResult.success) {
// Handle specific errors
if (submitResult.error?.includes('OCR')) {
return {
success: false,
error: 'Could not read document. Please ensure the image is clear and try again.'
};
}
return {
success: false,
error: submitResult.error || 'Failed to process document'
};
}
// Generate proof
const proofResult = await rebelVerify.generateProof(
submitResult.data.verificationToken,
minAge
);
if (!proofResult.success) {
if (proofResult.error?.includes('minimum age')) {
return {
success: false,
error: `You must be at least ${minAge} years old to continue.`
};
}
return {
success: false,
error: proofResult.error || 'Failed to generate proof'
};
}
return {
success: true,
data: proofResult.data
};
} catch (error) {
// Handle unexpected errors
console.error('Unexpected error:', error);
return {
success: false,
error: 'An unexpected error occurred. Please try again.'
};
}
}Common Error Messages
| Error Message | Cause | Solution |
|---|---|---|
SDK not initialized | initialize() not called | Call initialize() first |
Failed to extract birth date | OCR failed | Use clearer image or different document |
does not meet minimum age | User too young | Inform user of age requirement |
Verification token not found | Invalid or expired token | Re-submit document |
Best Practices
Follow these best practices to ensure secure, reliable, and user-friendly integration.
Initialization
- Always initialize first: Call
initialize()before using any SDK methods - Handle initialization errors: Wrap initialization in try-catch and provide user feedback
- Initialize once: Initialize in your app's entry point or root component, not on every action
Security
- Store tokens securely: Use secure storage (localStorage for web, secure storage for mobile)
- Never expose API keys: Always use API keys on your backend, never in client-side code
- Use HTTPS: Always use HTTPS in production to protect data in transit
- Validate input: Validate file types and sizes before submission
User Experience
- Show loading states: Display loading indicators during processing
- Provide clear errors: Show user-friendly error messages, not technical details
- Guide users: Provide instructions on what documents are accepted
- Allow retries: Let users retry if verification fails
Performance
- Optimize images: Compress images before submission to reduce processing time
- Cache credentials: Store exported credentials to avoid re-verification
- Implement rate limiting: Add rate limiting on your backend to prevent abuse
Code Example
// Best practice: Complete integration example
import { rebelVerify } from '@elyndra-studios/rebelverify';
class AgeVerificationService {
private initialized = false;
async initialize() {
if (this.initialized) return;
try {
await rebelVerify.initialize();
this.initialized = true;
} catch (error) {
throw new Error('Failed to initialize verification service');
}
}
async verifyUser(file: File, minAge: number) {
// Ensure initialized
if (!this.initialized) {
await this.initialize();
}
// Validate file
if (!this.validateFile(file)) {
throw new Error('Invalid file type or size');
}
try {
// Submit document
const submitResult = await rebelVerify.submitID(file);
if (!submitResult.success) {
throw new Error(submitResult.error || 'Document processing failed');
}
// Generate proof
const proofResult = await rebelVerify.generateProof(
submitResult.data.verificationToken,
minAge
);
if (!proofResult.success) {
throw new Error(proofResult.error || 'Proof generation failed');
}
// Verify on backend
const backendResult = await this.verifyOnBackend(proofResult.data);
if (!backendResult.valid) {
throw new Error('Backend verification failed');
}
return {
success: true,
minAge: backendResult.minAge
};
} catch (error) {
console.error('Verification error:', error);
throw error;
}
}
private validateFile(file: File): boolean {
const maxSize = 10 * 1024 * 1024; // 10MB
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
return file.size <= maxSize && allowedTypes.includes(file.type);
}
private async verifyOnBackend(proofData: any) {
const response = await fetch('/api/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
proof: proofData.proof.proof,
publicSignals: proofData.proof.publicSignals,
signature: proofData.proof.signature
})
});
return response.json();
}
}