Developer Documentation

Complete guide to integrating RebelVerify into your application.

Installation

Install the RebelVerify SDK using npm or yarn:

bash
npm install @elyndra-studios/rebelverify
# or
yarn add @elyndra-studios/rebelverify

For 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:

typescript
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?

  1. Initialize: Sets up cryptographic keys and ZKP service
  2. Submit ID: Runs client-side authenticity heuristics and OCR to extract birth date
  3. Generate Proof: Creates zero-knowledge proof without exposing birth date
  4. 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:

typescript
// 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

typescript
await rebelVerify.initialize(privateKey?: string): Promise<void>

Parameters

ParameterTypeRequiredDescription
privateKeystringNoExisting 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

typescript
// 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

typescript
await rebelVerify.submitID(
  idDocument: string | File | Blob,
  metadata?: Record<string, any>
): Promise<VerificationResult>

Parameters

ParameterTypeRequiredDescription
idDocumentstring | File | BlobYesID document as File object, Blob, or data URL string (e.g., "data:image/jpeg;base64,...")
metadataRecord<string, any>NoAdditional metadata about the document (documentType, etc.)

Returns

typescript
interface VerificationResult {
  success: boolean;
  error?: string;
  data?: {
    verificationToken: string;
    birthYear: number;
    documentType: string;
    ocrConfidence?: number;
    authenticity?: DocumentAuthenticity;
  };
}

Examples

typescript
// 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

typescript
await rebelVerify.generateProof(
  verificationToken: string,
  minAge: number
): Promise<VerificationResult>

Parameters

ParameterTypeRequiredDescription
verificationTokenstringYesToken received from submitID()
minAgenumberYesMinimum age requirement (e.g., 18, 21, 25)

Returns

typescript
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

typescript
// 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

typescript
await rebelVerify.exportCredential(
  proofId: string
): Promise<VerificationResult>

Parameters

ParameterTypeRequiredDescription
proofIdstringYesThe proof ID from generateProof() result

Returns

typescript
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

typescript
// 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

text
POST https://api.rebelverify.com/api/v1/verify

Authentication

Requires API key authentication via Bearer token in the Authorization header.

Request Body

json
{
  "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

json
// 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

typescript
// 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 CodeDescription
200Verification successful
400Invalid proof, missing fields, or duplicate proof
401Invalid or missing API key
429Rate limit exceeded
500Internal server error

GET /api/v1/usage

Get usage statistics for your API key, including total verifications, revenue, and recent activity.

Endpoint

text
GET https://api.rebelverify.com/api/v1/usage

Authentication

Requires API key authentication via Bearer token in the Authorization header.

Response

json
{
  "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

typescript
// 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.

tsx
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

typescript
// 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

typescript
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

typescript
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 MessageCauseSolution
SDK not initializedinitialize() not calledCall initialize() first
Failed to extract birth dateOCR failedUse clearer image or different document
does not meet minimum ageUser too youngInform user of age requirement
Verification token not foundInvalid or expired tokenRe-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

typescript
// 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();
  }
}