← Verify AIAPI Documentation
Dashboard

Verify AI API

Submit photos for AI-powered compliance verification. Get structured results with confidence scores, violation details, and user feedback.

Base URL: https://verify.switchlabs.dev/api/v1

Quick Start

Get up and running in under 5 minutes. Choose your integration path:

1. Install the React Native SDK

bash
npx expo install @switchlabs/verify-ai-react-native expo-camera @react-native-async-storage/async-storage

2. Get Your API Key

Sign up for a plan, then find your key in Dashboard → Settings.

3. Verify a Photo (Minimal Example)

tsx
import { useVerifyAI } from '@switchlabs/verify-ai-react-native';

function ParkingScreen() {
  const { verify, loading, lastResult } = useVerifyAI({
    apiKey: 'vai_your_api_key',
  });

  const handlePhoto = async (base64Image: string) => {
    const result = await verify({
      image: base64Image,
      policy: 'scooter_parking',
    });
    if (result?.is_compliant) {
      console.log('Parking approved!');
    }
  };

  return <>{/* your camera UI */}</>;
}

4. Full Camera Integration (5 lines)

tsx
import { useVerifyAI } from '@switchlabs/verify-ai-react-native';
import { VerifyAIScanner } from '@switchlabs/verify-ai-react-native/scanner';

function ScannerScreen() {
  const { verify } = useVerifyAI({ apiKey: 'vai_your_api_key' });

  return (
    <VerifyAIScanner
      onCapture={(base64) => verify({ image: base64, policy: 'scooter_parking' })}
      onResult={(result) => console.log(result.is_compliant ? 'PASS' : 'FAIL')}
      overlay={{ title: 'Parking Check', instructions: 'Center the scooter', showGuideFrame: true }}
    />
  );
}

5. REST API (No SDK)

Not using React Native? Call the API directly from any language:

bash
curl -X POST https://verify.switchlabs.dev/api/v1/verify \
  -H "X-API-Key: vai_your_api_key" \
  -F "image=@photo.jpg" \
  -F "policy=scooter_parking"

# Response:
# { "is_compliant": false, "confidence": 0.98,
#   "violation_reasons": ["blocking_sidewalk"],
#   "feedback": "Please move away from the walkway." }

Authentication

All API requests require an API key passed via the X-API-Key header.

bash
curl https://verify.switchlabs.dev/api/v1/verifications \
  -H "X-API-Key: vai_your_api_key_here"

Get your API key from the sign-up page or your dashboard settings.

Submit Verification

Submit a photo for AI analysis. Accepts multipart form-data or JSON with base64 image.

Request Parameters

ParameterTypeRequiredDescription
imagefile | base64YesThe image to verify (JPEG, PNG, WebP, max 10MB)
policystringYesPolicy ID (e.g., scooter_parking, damage_detection, delivery_pod)
metadataobjectNoArbitrary metadata to attach (device_id, user_id, gps, etc.)
providerstringNoAI provider override: "openai", "anthropic", or "gemini"

Example: Multipart Form-Data

bash
curl -X POST https://verify.switchlabs.dev/api/v1/verify \
  -H "X-API-Key: vai_your_api_key" \
  -F "image=@photo.jpg" \
  -F "policy=scooter_parking" \
  -F 'metadata={"device_id":"dev_123","gps":{"lat":37.77,"lng":-122.42}}'

Example: JSON with Base64

bash
curl -X POST https://verify.switchlabs.dev/api/v1/verify \
  -H "X-API-Key: vai_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "data:image/jpeg;base64,/9j/4AAQ...",
    "policy": "scooter_parking",
    "metadata": {"device_id": "dev_123"}
  }'

Response

json
{
  "id": "ver_8x92m4k9",
  "created_at": "2026-01-10T14:30:00Z",
  "status": "success",
  "is_compliant": false,
  "confidence": 0.98,
  "policy": "scooter_parking",
  "violation_reasons": ["blocking_sidewalk", "kickstand_up"],
  "feedback": "Please deploy the kickstand and move away from the walkway.",
  "metadata": {
    "device_id": "dev_123",
    "gps": {"lat": 37.77, "lng": -122.42}
  },
  "image_url": "https://...signed-url..."
}

List Verifications

Retrieve a paginated list of past verifications. Results are scoped to your API key.

Query Parameters

ParameterTypeRequiredDescription
limitintegerNoResults per page (default 20, max 100)
cursorstringNoPagination cursor (created_at of last item)
policystringNoFilter by policy ID
statusstringNoFilter by status (success, error)
is_compliantbooleanNoFilter by compliance result
start_dateISO dateNoFilter: created after this date
end_dateISO dateNoFilter: created before this date

Response

json
{
  "data": [
    {
      "id": "ver_8x92m4k9",
      "created_at": "2026-01-10T14:30:00Z",
      "status": "success",
      "is_compliant": false,
      "confidence": 0.98,
      "policy": "scooter_parking",
      "violation_reasons": ["blocking_sidewalk"],
      "feedback": "...",
      "metadata": {},
      "image_url": "https://...signed-url..."
    }
  ],
  "has_more": true,
  "next_cursor": "2026-01-10T14:30:00Z"
}

Get Verification

Retrieve full details of a specific verification, including a fresh signed image URL.

Example

bash
curl https://verify.switchlabs.dev/api/v1/verifications/ver_8x92m4k9 \
  -H "X-API-Key: vai_your_api_key"

Webhooks

Configure webhooks to receive queued notifications when verifications complete. Deliveries are processed every 5 minutes from your dashboard settings.

Payload Format

json
{
  "event": "verification.completed",
  "data": {
    "id": "ver_8x92m4k9",
    "created_at": "2026-01-10T14:30:00Z",
    "status": "success",
    "is_compliant": false,
    "confidence": 0.98,
    "policy": "scooter_parking",
    "violation_reasons": ["blocking_sidewalk"],
    "feedback": "Please move away from the walkway.",
    "metadata": {},
    "image_url": "https://...signed-url..."
  }
}

Signature Verification

Each delivery includes an X-VerifyAI-Signature header with format t=timestamp,v1=signature.

javascript
// Verify webhook signature (Node.js)
const crypto = require('crypto');

function verifySignature(payload, header, secret) {
  const [tPart, sigPart] = header.split(',');
  const timestamp = tPart.split('=')[1];
  const signature = sigPart.split('=')[1];

  const expected = crypto
    .createHmac('sha256', secret)
    .update(timestamp + '.' + payload)
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

Retry Behavior

Failed deliveries are retried up to 3 times: after 1 minute, 5 minutes, and 30 minutes. A delivery is considered successful if your endpoint returns a 2xx status code within 5 seconds.

Policies

Available verification policies:

scooter_parkingScooter Parking Compliance

Verifies electric scooter/bike parking meets city regulations. Configurable detection parameters: vehicle in frame, upright, kickstand deployed, sidewalk clearance, entrance clearance, designated area, roadway detection, and bike rack attachment.

no_vehicle_visiblevehicle_fallenkickstand_upblocking_sidewalkblocking_entrancenot_designated_areain_roadwaynot_secured_to_rackimage_unclear
damage_detectionVehicle Damage Detection

Detects and documents damage to vehicles or equipment. Identifies type and severity of damage.

scratchesdentscracksbroken_partsflat_tiremissing_componentsstructural_damageelectrical_damage
delivery_podDelivery Proof of Delivery

Verifies package delivery placement and condition. Checks: package visible, at door, protected, upright, accessible.

no_package_visiblenot_at_doorexposed_to_weatherpackage_damagedblocking_pathimage_unclear

React Native SDK

The official SDK for React Native / Expo apps. Install with:

bash
npm install @switchlabs/verify-ai-react-native

VerifyAIClient

Low-level client for direct API access. Use this if you don't need React hooks.

Constructor Config

ParameterTypeRequiredDescription
apiKeystringYesYour Verify AI API key (vai_...)
baseUrlstringNoAPI base URL (default: https://verify.switchlabs.dev/api/v1)
timeoutnumberNoRequest timeout in ms (default: 30000)
offlineModebooleanNoEnable offline queue (requires AsyncStorage)

Methods

MethodReturnsDescription
verify(request)Promise<VerificationResult>Submit a photo for verification
listVerifications(params?)Promise<VerificationListResponse>List past verifications with filters
getVerification(id)Promise<VerificationResult>Get a single verification by ID

VerifyAIRequestError

Thrown on non-2xx responses. Includes helper getters:

typescript
try {
  const result = await client.verify({ image, policy: 'scooter_parking' });
} catch (err) {
  if (err instanceof VerifyAIRequestError) {
    err.status;         // HTTP status code
    err.body;           // { error, status, current_usage?, limit?, upgrade_url? }
    err.isRateLimited;  // true if 429
    err.isUnauthorized; // true if 401
    err.upgradeUrl;     // URL to upgrade plan (if 403)
  }
}

useVerifyAI Hook

React hook that wraps the client with loading/error state and optional offline queue.

Config

ParameterTypeRequiredDescription
apiKeystringYesYour Verify AI API key
baseUrlstringNoAPI base URL override
timeoutnumberNoRequest timeout in ms
offlineModebooleanNoEnable offline queue — queues failed requests and processes on app foreground

Return Values

ParameterTypeRequiredDescription
verify(request) => Promise<Result | null>NoSubmit a verification. Returns null if queued offline.
listVerifications(params?) => Promise<ListResponse>NoList past verifications
getVerification(id) => Promise<Result>NoGet a single verification
loadingbooleanNoTrue while a verify() call is in progress
errorError | nullNoMost recent error (null on success)
lastResultVerificationResult | nullNoMost recent successful result
queueSizenumberNoNumber of items in offline queue
processQueue() => Promise<void>NoManually trigger offline queue processing
clientVerifyAIClientNoThe underlying client instance
offlineQueueOfflineQueue | nullNoQueue instance (null if offlineMode is off)
tsx
const {
  verify, loading, error, lastResult, queueSize
} = useVerifyAI({ apiKey: 'vai_...', offlineMode: true });

// verify() auto-queues on network failure when offlineMode is on
const result = await verify({ image: base64, policy: 'scooter_parking' });
// result is null if queued, VerificationResult if successful

VerifyAIScanner Component

Drop-in camera component with capture button, processing overlay, and pass/fail display.

Props

ParameterTypeRequiredDescription
onCapture(base64) => Promise<Result | null>YesCalled with base64 image data when user captures a photo
onResult(result) => voidNoCalled when verification completes successfully
onError(error) => voidNoCalled when an error occurs
overlayScannerOverlayConfigNoOverlay config: title, instructions, showGuideFrame, guideFrameAspectRatio
styleViewStyleNoCustom container style
showCaptureButtonbooleanNoShow default capture button (default: true)
captureRefMutableRefObjectNoRef to trigger capture programmatically from parent

Scanner Status Flow

idle → capturing → processing → success | error → idle

Overlay Config

ParameterTypeRequiredDescription
titlestringNoTitle text shown at top of camera view
instructionsstringNoInstruction text shown when idle
showGuideFramebooleanNoShow dashed guide rectangle
guideFrameAspectRationumberNoGuide frame aspect ratio (default: 4/3)

Offline Queue

When offlineMode: true is set, failed verification requests are automatically saved to AsyncStorage and retried when the app returns to the foreground.

MethodDescription
enqueue(request)Manually add a request to the queue
getQueue()Get all queued items
getQueueSize()Get number of queued items
remove(id)Remove a specific item
clear()Clear all queued items
processQueue(onResult?, maxRetries?)Process all items, returns { processed, failed, remaining }

Items are stored individually in AsyncStorage to avoid Android's per-key size limit. The queue auto-processes on app foreground via the useVerifyAI hook.

TypeScript Types

typescript
import type {
  VerifyAIConfig,         // { apiKey, baseUrl?, timeout?, offlineMode? }
  VerificationRequest,    // { image: string, policy: string, metadata?, provider? }
  VerificationResult,     // { id, created_at, status, is_compliant, confidence, ... }
  VerificationListResponse,  // { data: Result[], has_more, next_cursor }
  VerificationListParams, // { limit?, cursor?, policy?, status?, is_compliant?, ... }
  QueueItem,              // { id, request, createdAt, retryCount }
  VerifyAIError,          // { error, status, current_usage?, limit?, upgrade_url? }
  ScannerStatus,          // 'idle' | 'capturing' | 'processing' | 'success' | 'error'
  ScannerOverlayConfig,   // { title?, instructions?, showGuideFrame?, guideFrameAspectRatio? }
} from '@switchlabs/verify-ai-react-native';

Expo Configuration

Add the camera plugin to your app.json:

json
{
  "expo": {
    "plugins": [
      [
        "expo-camera",
        {
          "cameraPermission": "Allow $(PRODUCT_NAME) to access the camera for photo verification."
        }
      ]
    ]
  }
}

Code Examples

cURL

bash
curl -X POST https://verify.switchlabs.dev/api/v1/verify \
  -H "X-API-Key: vai_your_api_key" \
  -F "image=@photo.jpg" \
  -F "policy=scooter_parking"

JavaScript (fetch)

javascript
const formData = new FormData();
formData.append('image', fileInput.files[0]);
formData.append('policy', 'scooter_parking');
formData.append('metadata', JSON.stringify({
  device_id: 'dev_123',
  user_id: 'usr_456',
}));

const response = await fetch(
  'https://verify.switchlabs.dev/api/v1/verify',
  {
    method: 'POST',
    headers: { 'X-API-Key': 'vai_your_api_key' },
    body: formData,
  }
);

const result = await response.json();
console.log(result.is_compliant); // true or false
console.log(result.feedback);     // User-friendly message

Python (requests)

python
import requests
import json

response = requests.post(
    'https://verify.switchlabs.dev/api/v1/verify',
    headers={'X-API-Key': 'vai_your_api_key'},
    files={'image': open('photo.jpg', 'rb')},
    data={
        'policy': 'scooter_parking',
        'metadata': json.dumps({
            'device_id': 'dev_123',
            'user_id': 'usr_456',
        }),
    },
)

result = response.json()
print(f"Compliant: {result['is_compliant']}")
print(f"Feedback: {result['feedback']}")

React Native (SDK)

typescript
import { VerifyAIClient } from '@switchlabs/verify-ai-react-native';

const client = new VerifyAIClient({ apiKey: 'vai_your_api_key' });

// Verify a photo
const result = await client.verify({
  image: base64ImageData,
  policy: 'scooter_parking',
  metadata: { device_id: 'dev_123' },
});

// List recent verifications
const { data, has_more } = await client.listVerifications({
  limit: 10,
  policy: 'scooter_parking',
});

// Get a specific verification
const detail = await client.getVerification('ver_8x92m4k9');

Error Handling (SDK)

typescript
import { VerifyAIRequestError } from '@switchlabs/verify-ai-react-native';

try {
  const result = await client.verify({ image, policy: 'scooter_parking' });
} catch (err) {
  if (err instanceof VerifyAIRequestError) {
    if (err.isRateLimited) {
      // Back off and retry
    } else if (err.isUnauthorized) {
      // Invalid API key
    } else if (err.upgradeUrl) {
      // Plan limit reached — show upgrade prompt
      Linking.openURL(err.upgradeUrl);
    }
  }
}

Offline Mode (SDK)

tsx
const { verify, queueSize, processQueue } = useVerifyAI({
  apiKey: 'vai_your_api_key',
  offlineMode: true, // queues failed requests automatically
});

// verify() returns null when queued offline
const result = await verify({ image: base64, policy: 'scooter_parking' });

// Queue processes automatically on app foreground, or manually:
await processQueue();

// Show pending count to user
<Text>{queueSize} photos waiting to upload</Text>

Errors & Rate Limits

HTTP Status Codes

CodeMeaning
200Success
400Bad request — missing required fields or invalid image format
401Unauthorized — invalid or missing API key
403Forbidden — no active subscription or wrong product
404Verification not found
429Rate limit exceeded
500Server error — AI processing failure

Rate Limits

LimitValue
Requests per minute60
Requests per hour1,000
Max image size10 MB

When rate limited, the response includes a Retry-After header with the number of seconds to wait.