Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Capture SDK

The Capture SDK (@succinctlabs/react-native-zcam1) takes photos and produces C2PA-signed assets bound to hardware-backed integrity signals.

Installation

npm i @succinctlabs/react-native-zcam1
 
# iOS
cd ios && pod install
# Android: JNI libraries are downloaded automatically during the Gradle build

Core Concepts

The Capture SDK:

  • Renders a native camera preview via ZCamera (AVFoundation on iOS, CameraX on Android)
  • Generates hardware-backed assertions over photo hashes
  • Embeds C2PA manifests with succinct.bindings assertion
  • Returns signed photos ready for proof generation

API Reference

initCapture(settings)

Initializes device keys and attestation. Call once on app startup. Requires network access.

import { initCapture, CaptureInfo, Settings } from "@succinctlabs/react-native-zcam1";
 
const settings: Settings = {
  appId: "TEAM_ID.com.example.app",
  production: false,
};
 
const captureInfo: CaptureInfo = await initCapture(settings);
Parameters:
  • settings.appId - Your Team ID + Bundle ID
  • settings.production - false for development, true for production

Returns: CaptureInfo containing:

  • appId - The app identifier
  • deviceKeyId - App Attest device key ID
  • contentPublicKey - Secure Enclave content key
  • contentKeyId - Derived content key identifier
  • attestation - App Attest attestation blob

ZCamera

React component that renders the native camera preview.

import { useRef } from "react";
import { ZCamera, CaptureInfo } from "@succinctlabs/react-native-zcam1";
 
function CameraScreen({ captureInfo }: { captureInfo: CaptureInfo }) {
  const camera = useRef<ZCamera>(null);
 
  return (
    <ZCamera
      ref={camera}
      captureInfo={captureInfo}
      position="back"        // "front" | "back"
      isActive={true}        // Enable/disable camera
      style={{ flex: 1 }}
    />
  );
}
Props:
  • captureInfo (required) - From initCapture()
  • position - Camera to use: "front" or "back" (default: "back")
  • isActive - Whether camera is running (default: true)
  • style - Style for the camera view

ZCamera.takePhoto()

Captures a photo and returns a signed ZPhoto.

const photo = await camera.current?.takePhoto({
  format: "jpeg",
  flash: "off",
  includeDepthData: false,
  aspectRatio: "4:3",
  orientation: "auto",
});
 
console.log(photo.originalPath); // Raw capture path
console.log(photo.path);         // C2PA-signed photo path
Parameters:
  • options.format - "jpeg" or "dng" (default: "jpeg")
  • options.flash - "off", "on", or "auto" (default: "off")
  • options.includeDepthData - Include depth data when supported (default: false). Android: always returns false; depth capture is not exposed via CameraX.
  • options.aspectRatio - "4:3", "16:9", or "1:1" (default: "4:3")
  • options.orientation - "auto", "portrait", or "landscape" (default: "auto")

Returns: ZPhoto with:

  • originalPath - Path to the raw captured image
  • path - Path to the C2PA-signed image with succinct.bindings

Complete Example

import { useEffect, useRef, useState } from "react";
import { View, Button, Text, StyleSheet } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { initCapture, ZCamera, CaptureInfo } from "@succinctlabs/react-native-zcam1";
import { FileSystem, Dirs } from "react-native-file-access";
 
export function CaptureScreen() {
  const camera = useRef<ZCamera>(null);
  const [captureInfo, setCaptureInfo] = useState<CaptureInfo>();
  const [lastPhoto, setLastPhoto] = useState<string>();
 
  useEffect(() => {
    const settings = {
      appId: process.env.EXPO_PUBLIC_APP_ID!,
      production: false,
    };
    initCapture(settings).then(setCaptureInfo);
  }, []);
 
  const handleCapture = async () => {
    const photo = await camera.current?.takePhoto();
    if (!photo) return;
 
    // Save to app's document directory
    const destPath = `${Dirs.DocumentDir}/photos/${Date.now()}.jpg`;
    await FileSystem.mkdir(`${Dirs.DocumentDir}/photos`);
    await FileSystem.cp(photo.path, destPath);
 
    setLastPhoto(destPath);
  };
 
  if (!captureInfo) {
    return (
      <View style={styles.centered}>
        <Text>Initializing camera...</Text>
      </View>
    );
  }
 
  return (
    <SafeAreaView style={styles.container}>
      <ZCamera ref={camera} captureInfo={captureInfo} style={styles.camera} />
      <View style={styles.controls}>
        <Button title="Capture" onPress={handleCapture} />
        {lastPhoto && <Text>Saved: {lastPhoto}</Text>}
      </View>
    </SafeAreaView>
  );
}
 
const styles = StyleSheet.create({
  container: { flex: 1 },
  camera: { flex: 1 },
  controls: { padding: 16 },
  centered: { flex: 1, justifyContent: "center", alignItems: "center" },
});

Notes

  • initCapture() generates Secure Enclave keys on first call; subsequent calls load existing keys
  • initCapture() requires network access
  • App Attest only works on physical iOS devices; the SDK uses mock values in simulators. On Android emulators, the SDK generates a test gradient image in place of a real camera capture.
  • The captured photo at photo.path contains a C2PA manifest with succinct.bindings—this is required for proof generation

Android Limitations

The following features are not available on Android due to CameraX API constraints:

FeatureiOSAndroid
Depth data (includeDepthData)SupportedAlways false
Ultra-wide camera detection (hasUltraWideCamera)Native detectionAlways false
Zoom switch-over factors (getSwitchOverZoomFactors)SupportedReturns empty array
Hardware attestation (initCapture)Apple App AttestPlanned (Android Key Attestation + Play Integrity)