Skip to content

Display Storage Files

Display and interact with stored files in your browser application. Since the browser client cannot directly query data (no API key), you’ll need your backend to provide the data.

Create an API endpoint to fetch file data:

// pages/api/videos.js (Next.js example)
import { createClient } from "zapdos-js";
export default async function handler(req, res) {
const client = createClient({
apiKey: process.env.ZAPDOS_API_KEY,
});
// Get all videos
const videos = await client.videos().fetch();
if (videos.error) {
return res.status(500).json(videos);
}
// Return simplified data for frontend
const videoData = videos.data.map((video) => ({
id: video.id,
name: video.metadata.file_name,
size: video.metadata.size,
contentType: video.metadata.content_type,
createdAt: video.created_at,
scenes: video.content?.scenes?.length || 0,
transcription: !!video.content?.transcription,
}));
res.json({ data: videoData });
}

Fetch and display files in your React/Vue/vanilla JS app:

// Fetch videos from your backend
async function fetchVideos() {
const response = await fetch("/api/videos");
const result = await response.json();
return result.data;
}
// Display videos
async function displayVideos() {
const videos = await fetchVideos();
const videoList = document.querySelector("#video-list");
videoList.innerHTML = videos
.map(
(video) => `
<div class="video-item">
<h3>${video.name}</h3>
<p>Size: ${formatFileSize(video.size)}</p>
<p>Scenes: ${video.scenes}</p>
<p>Has transcription: ${video.transcription ? "Yes" : "No"}</p>
<button onclick="downloadVideo('${video.id}')">Download</button>
</div>
`
)
.join("");
}
function formatFileSize(bytes) {
const sizes = ["Bytes", "KB", "MB", "GB"];
if (bytes === 0) return "0 Bytes";
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + " " + sizes[i];
}

Create a search interface for your videos:

pages/api/search.js
// Backend search endpoint
export default async function handler(req, res) {
const client = createClient({
apiKey: process.env.ZAPDOS_API_KEY,
});
const { query, limit } = req.body;
const results = await client.search(query, { limit });
res.json(results);
}
// Frontend search component
async function searchVideos(query) {
const response = await fetch("/api/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, limit: 10 }),
});
const results = await response.json();
if (results.data) {
displaySearchResults(results.data.items);
}
}
function displaySearchResults(items) {
const resultsDiv = document.querySelector("#search-results");
resultsDiv.innerHTML = items
.map(
(item) => `
<div class="search-result">
<h4>${item.metadata.object_original_name}</h4>
<p>Score: ${(item.score * 100).toFixed(1)}%</p>
<p>Time: ${formatTime(item.metadata.start_ms)} - ${formatTime(
item.metadata.end_ms
)}</p>
<button onclick="playClip('${item.metadata.object_id}', ${
item.metadata.start_ms
}, ${item.metadata.end_ms})">
Play Clip
</button>
</div>
`
)
.join("");
}
function formatTime(milliseconds) {
const seconds = Math.floor(milliseconds / 1000);
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
return `${minutes}:${remainingSeconds.toString().padStart(2, "0")}`;
}

Complete React component for file management:

import { useState, useEffect } from "react";
function VideoManager() {
const [videos, setVideos] = useState([]);
const [searchQuery, setSearchQuery] = useState("");
const [searchResults, setSearchResults] = useState([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchVideos();
}, []);
const fetchVideos = async () => {
const response = await fetch("/api/videos");
const result = await response.json();
setVideos(result.data);
};
const handleSearch = async (e) => {
e.preventDefault();
setLoading(true);
try {
const response = await fetch("/api/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: searchQuery, limit: 10 }),
});
const results = await response.json();
setSearchResults(results.data?.items || []);
} catch (error) {
console.error("Search failed:", error);
} finally {
setLoading(false);
}
};
const downloadVideo = async (videoId) => {
// Call backend endpoint to get download URL
const response = await fetch(`/api/download/${videoId}`);
const result = await response.json();
if (result.data?.url) {
window.open(result.data.url, "_blank");
}
};
return (
<div>
<div>
<h2>Search Videos</h2>
<form onSubmit={handleSearch}>
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search for moments in videos..."
/>
<button type="submit" disabled={loading}>
{loading ? "Searching..." : "Search"}
</button>
</form>
{searchResults.length > 0 && (
<div>
<h3>Search Results</h3>
{searchResults.map((result) => (
<div key={result.id} className="search-result">
<h4>{result.metadata.object_original_name}</h4>
<p>Confidence: {(result.score * 100).toFixed(1)}%</p>
<p>
Time: {Math.floor(result.metadata.start_ms / 1000)}s -
{Math.floor(result.metadata.end_ms / 1000)}s
</p>
</div>
))}
</div>
)}
</div>
<div>
<h2>All Videos</h2>
{videos.map((video) => (
<div key={video.id} className="video-item">
<h3>{video.name}</h3>
<p>Size: {formatFileSize(video.size)}</p>
<p>Scenes: {video.scenes}</p>
<button onClick={() => downloadVideo(video.id)}>Download</button>
</div>
))}
</div>
</div>
);
}
function formatFileSize(bytes) {
const sizes = ["Bytes", "KB", "MB", "GB"];
if (bytes === 0) return "0 Bytes";
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + " " + sizes[i];
}
  • Proxy API calls - Never expose your API key to browsers
  • Implement pagination - For large video libraries
  • Cache results - Store frequently accessed data
  • Optimize thumbnails - Use scene images for video previews
  • Handle errors - Gracefully handle API failures