404 Dataset not found |
Stale dat
Optimize Juicebox costs.
ReadWriteEditGrep
Juicebox Cost Tuning
Cost Factors
| Feature |
Cost Driver |
| Search |
Per query |
| Enrichment |
Per profile |
| Contact data |
Per lookup |
| Outreach |
Per message |
Reduction Strategies
- Cache search results (avoid duplicate queries)
- Use filters (fewer wasted enrichments)
- Only enrich top-scored candidates
- Only get contacts for final candidates
Quota Monitoring
const quota = await client.account.getQuota();
console.log(`Searches: ${quota.searches.used}/${quota.searches.limit}`);
if (quota.searches.used > quota.searches.limit * 0.8) console.warn('80% quota used');
Resources
Next Steps
See juicebox-reference-architecture.
Juicebox data privacy and GDPR.
ReadWriteEditGrep
Juicebox Data Handling
Overview
Juicebox AI processes people datasets for talent intelligence and analysis workflows. Data types include people search results, enriched profile records (employment history, skills, social links), analysis exports, and outreach logs. Profile data often contains personal information governed by GDPR, CCPA, and recruitment privacy regulations. All enrichment results must be handled with consent tracking, purpose limitation, and right-to-deletion support. Contact data requires field-level encryption and strict access controls to prevent unauthorized disclosure.
Data Classification
| Data Type |
Sensitivity |
Retention |
Encryption |
| Search results |
Low |
Session only (ephemeral) |
TLS in transit |
| Enriched profiles |
High (PII) |
Per data policy, max 1 year |
AES-256 at rest |
| Contact data (email/phone) |
High (PII) |
Until candidate objects or deletion |
Field-level encryption |
| Analysis exports |
Medium |
90 days |
AES-256 at rest |
| Outreach logs |
Medium |
6 months |
AES-256 at rest |
Data Import
interface JuiceboxProfile {
id: string; name: string; email?: string; phone?: string;
company: string; title: string; skills: string[];
source: string; enrichedAt: string;
}
async function importPeopleDataset(query: string, maxResults = 100): Promise<JuiceboxProfile[]> {
const profiles: JuiceboxProfile[] = [];
let offset = 0;
do {
const res = await fetch(`https://api.juicebox.ai/v1/search`, {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.JUICEBOX_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query, limit: 50, offset }),
});
const data = await res.json();
if (!data.results?.length) break;
for (const p of data.results) {
if (!p.id || !p.name) throw new Error(`Invalid profile: missing required fields`);
profiles.push(p);
}
offset += 50;
} while (profiles.length < maxResults);
return profiles;
}
Data Export
async function exportAnalysisResults(profiles: JuiceboxProfile[], format: 'csv' | 'json') {
// Strip direct contact info from exports unless explicitly authorized
const sanitized = profiles.map(({ email, phone, ...rest }) => ({
...rest,
email: email ? '[CONSENT_REQUIRED]' : undefined,
phone: phone ? '[CONSENT_REQUIRED]' : undefined,
}));
if (format === 'csv') {
const header = Object.keys(sanitized[0]).join(',');
const rows = sanitized.map(r => Object.values(r).join(','));
ret
Collect Juicebox debug evidence.
ReadBash(curl:*)Grep
Juicebox Debug Bundle
Overview
Collect Juicebox API connectivity status, dataset health, analysis quota usage, and rate limit state into a single diagnostic archive. This bundle helps troubleshoot failed dataset uploads, stalled AI analysis runs, quota exhaustion, and API authentication problems.
Debug Collection Script
#!/bin/bash
set -euo pipefail
BUNDLE="debug-juicebox-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$BUNDLE"
# Environment check
echo "=== Juicebox Debug Bundle ===" | tee "$BUNDLE/summary.txt"
echo "Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$BUNDLE/summary.txt"
echo "JUICEBOX_API_KEY: ${JUICEBOX_API_KEY:+[SET]}" >> "$BUNDLE/summary.txt"
# API connectivity — health endpoint
HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: Bearer ${JUICEBOX_API_KEY}" \
https://api.juicebox.ai/v1/health 2>/dev/null || echo "000")
echo "API Status: HTTP $HTTP" >> "$BUNDLE/summary.txt"
# Account quota
curl -s -H "Authorization: Bearer ${JUICEBOX_API_KEY}" \
"https://api.juicebox.ai/v1/account/quota" \
> "$BUNDLE/quota.json" 2>&1 || true
# Datasets and recent analyses
curl -s -H "Authorization: Bearer ${JUICEBOX_API_KEY}" \
"https://api.juicebox.ai/v1/datasets?limit=10" > "$BUNDLE/datasets.json" 2>&1 || true
curl -s -H "Authorization: Bearer ${JUICEBOX_API_KEY}" \
"https://api.juicebox.ai/v1/analyses?limit=5" > "$BUNDLE/recent-analyses.json" 2>&1 || true
# Rate limit headers
curl -s -D "$BUNDLE/rate-headers.txt" -o /dev/null \
-H "Authorization: Bearer ${JUICEBOX_API_KEY}" https://api.juicebox.ai/v1/health 2>/dev/null || true
tar -czf "$BUNDLE.tar.gz" "$BUNDLE" && rm -rf "$BUNDLE"
echo "Bundle: $BUNDLE.tar.gz"
Analyzing the Bundle
tar -xzf debug-juicebox-*.tar.gz
cat debug-juicebox-*/summary.txt # Auth + API health
jq '{used, limit, remaining}' debug-juicebox-*/quota.json # Quota usage
jq '.[] | {id, name, row_count}' debug-juicebox-*/datasets.json # Dataset inventory
jq '.[] | {id, status, created_at}' debug-juicebox-*/recent-analyses.json
Common Issues
| Symptom |
Check in Bundle |
Fix |
| API returns 401 |
summary.txt shows HTTP 401 |
Regenerate API key in Juicebox Settings > API Keys |
| Dataset upload stuck |
datasets.json shows processing status for >10 min |
Check file format (CSV/JSON required); reduce file size below 100MB |
Analysis quota ex
Deploy Juicebox integrations.
ReadWriteEditBash(npm:*)Grep
Juicebox Deploy Integration
Overview
Deploy a containerized Juicebox AI analysis integration service with Docker. This skill covers building a production image that connects to the Juicebox API for managing datasets, running AI-powered analyses, and retrieving structured insights. Includes environment configuration for dataset access and analysis pipelines, health checks that verify API connectivity and dataset availability, and rolling update strategies for zero-downtime deployments serving real-time analysis results.
Docker Configuration
FROM node:20-slim AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build
FROM node:20-slim
RUN addgroup --system app && adduser --system --ingroup app app
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package*.json ./
USER app
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
Environment Variables
export JUICEBOX_API_KEY="jb_live_xxxxxxxxxxxx"
export JUICEBOX_BASE_URL="https://api.juicebox.ai/v1"
export JUICEBOX_WORKSPACE_ID="ws_xxxxxxxxxxxx"
export LOG_LEVEL="info"
export PORT="3000"
export NODE_ENV="production"
Health Check Endpoint
import express from 'express';
const app = express();
app.get('/health', async (req, res) => {
try {
const response = await fetch(`${process.env.JUICEBOX_BASE_URL}/datasets`, {
headers: { 'Authorization': `Bearer ${process.env.JUICEBOX_API_KEY}` },
});
if (!response.ok) throw new Error(`Juicebox API returned ${response.status}`);
res.json({ status: 'healthy', service: 'juicebox-integration', timestamp: new Date().toISOString() });
} catch (error) {
res.status(503).json({ status: 'unhealthy', error: (error as Error).message });
}
});
Deployment Steps
Step 1: Build
docker build -t juicebox-integration:latest .
Step 2: Run
docker run -d --name juicebox-integration \
-p 3000:3000 \
-e JUICEBOX_API_KEY -e JUICEBOX_BASE_URL -e JUICEBOX_WORKSPACE_ID \
juicebox-integration:latest
Step 3: Verify
curl -s http://localhost:3000/health | jq .
Step 4: Rolling Update
docker build -t juicebox-integration:v2 . && \
docker stop juicebox-integration && \
docker rm juicebox-integration && \
docker run -d --name juicebox-integration -p 3000:3000 \
-e JUICEBOX_API_KEY -e JUICEBOX_BASE_URL -e JUIC
Configure Juicebox team access.
ReadWriteEditGrep
Juicebox Enterprise RBAC
Overview
Juicebox provides AI-powered people search and analysis for recruiting and sales teams. Enterprise RBAC controls who can search candidate databases, enrich profiles with contact info, trigger outreach sequences, and export data. Workspace admins manage team seats and API usage limits. Analysts run searches but may be restricted from exporting PII. Viewers can review saved searches without accessing raw contact data. SOC 2 compliance requires audit logging on all data enrichment and export actions.
Role Hierarchy
| Role |
Permissions |
Scope |
| Workspace Admin |
Manage seats, billing, API keys, configure integrations |
Entire workspace |
| Recruiter |
Search, enrich, access contact info, run outreach campaigns |
All datasets |
| Analyst |
Search and enrich profiles, view analytics dashboards |
Assigned datasets |
| Sourcer |
Search and enrich only, no contact reveal or outreach |
Assigned datasets |
| Viewer |
View saved searches and reports, no data access or export |
Read-only |
Permission Check
async function checkJuiceboxAccess(userId: string, action: string, datasetId: string): Promise<boolean> {
const response = await fetch(`${JUICEBOX_API}/v1/workspaces/${WORKSPACE_ID}/permissions`, {
headers: { Authorization: `Bearer ${JUICEBOX_API_KEY}`, 'Content-Type': 'application/json' },
});
const perms = await response.json();
const user = perms.members.find((m: any) => m.id === userId);
if (!user) return false;
const rolePerms = ROLE_PERMISSIONS[user.role];
return rolePerms?.[action] && (user.datasets.includes(datasetId) || user.role === 'admin');
}
Role Assignment
async function assignWorkspaceRole(email: string, role: string, datasets: string[]): Promise<void> {
await fetch(`${JUICEBOX_API}/v1/workspaces/${WORKSPACE_ID}/members`, {
method: 'POST',
headers: { Authorization: `Bearer ${JUICEBOX_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ email, role, datasetAccess: datasets }),
});
}
async function revokeAccess(email: string): Promise<void> {
await fetch(`${JUICEBOX_API}/v1/workspaces/${WORKSPACE_ID}/members/${email}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${JUICEBOX_API_KEY}` },
});
}
Audit Logging
interface JuiceboxAuditEntry {
timestamp: string; userId: string; role: string;
action: 'search' | 'enrich' | 'contact_reveal' | 'export' | 'outreach' | 'role_change';
datasetId: str
Create a minimal Juicebox people search example.
ReadWriteEditBash(npm:*)Grep
Juicebox Hello World
Overview
Three examples: natural language people search, profile enrichment, and contact data from 800M+ profiles.
Instructions
Example 1: Natural Language Search
import { JuiceboxClient } from '@juicebox/sdk';
const client = new JuiceboxClient({ apiKey: process.env.JUICEBOX_API_KEY });
const results = await client.search({
query: 'senior ML engineer at FAANG with PhD in Bay Area',
limit: 10,
filters: { experience_years: { min: 5 } }
});
results.profiles.forEach(p =>
console.log(`${p.name} | ${p.title} at ${p.company} | ${p.location}`)
);
Example 2: Profile Enrichment
const enriched = await client.enrich({
linkedin_url: 'https://linkedin.com/in/example',
fields: ['skills', 'experience', 'education', 'contact']
});
console.log(`Skills: ${enriched.skills.join(', ')}`);
if (enriched.tech_profile?.github) {
console.log(`GitHub: ${enriched.tech_profile.github.repos} repos`);
}
Example 3: Contact Data (Python)
results = client.search(query='PM fintech NYC', limit=5, include_contact=True)
for p in results.profiles:
email = p.contact.email if p.contact else 'N/A'
print(f"{p.name} | {p.title} | {email}")
Error Handling
| Error |
Cause |
Solution |
| Empty results |
Query too narrow |
Broaden terms or remove filters |
| Partial contact |
Limited coverage |
Not all profiles have contact data |
Resources
Next Steps
Explore juicebox-sdk-patterns for production code.
Juicebox incident response.
ReadBash(curl:*)Grep
Juicebox Incident Runbook
Overview
Incident response procedures for Juicebox AI analysis platform integration failures. Covers analysis timeouts, dataset corruption, quota exhaustion, and export failures. Juicebox powers AI-driven people search and candidate analysis, so incidents disrupt recruiting pipelines, talent intelligence workflows, and automated sourcing. Classify severity immediately using the matrix below and follow the corresponding playbook.
Severity Levels
| Level |
Definition |
Response Time |
Example |
| P1 - Critical |
Full API outage or dataset corruption |
15 min |
Health endpoint returns 5xx, analysis results missing |
| P2 - High |
Analysis timeouts or export failures |
30 min |
Search queries hang beyond 30s, CSV exports fail |
| P3 - Medium |
Quota exhaustion or rate limiting |
2 hours |
429 responses, account quota at 100% usage |
| P4 - Low |
Partial data or degraded result quality |
8 hours |
Search returns fewer results than expected |
Diagnostic Steps
# Check API health
curl -s -o /dev/null -w "HTTP %{http_code}\n" \
-H "Authorization: Bearer $JUICEBOX_API_KEY" \
https://api.juicebox.ai/v1/health
# Check account quota usage
curl -s -H "Authorization: Bearer $JUICEBOX_API_KEY" \
https://api.juicebox.ai/v1/account/quota | jq '.used, .limit, .remaining'
# Test a minimal search request
curl -s -w "\nHTTP %{http_code}\n" \
-H "Authorization: Bearer $JUICEBOX_API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.juicebox.ai/v1/search \
-d '{"query": "software engineer", "limit": 1}'
Incident Playbooks
API Outage
- Confirm via health endpoint and status.juicebox.ai
- Activate fallback mode — serve cached search results to active users
- Pause any automated sourcing pipelines to avoid wasting quota on retries
- Notify recruiting team that live search is temporarily unavailable
- Monitor status page and resume operations once health check passes
Authentication Failure
- Verify API key is set:
echo $JUICEBOXAPIKEY | wc -c
- Test with health endpoint (see diagnostics above)
- If 401: API key may be revoked — regenerate in Juicebox dashboard
- If 403: check account tier permissions for the requested endpoint
- Deploy new key and verify search requests succeed
Data Sync Failure
- Check if recent analysis results are returning stale or incomplete data
- Verify export endpoints are responding — test a small CSV export
Install and configure Juicebox PeopleGPT API authentication.
ReadWriteEditBash(npm:*)Bash(pip:*)Grep
Juicebox Install & Auth
Overview
Set up Juicebox PeopleGPT API for AI-powered people search across 800M+ professional profiles.
Prerequisites
- Juicebox account at app.juicebox.ai
- API key from Dashboard > Settings > API Keys
- Node.js 18+ or Python 3.8+
Instructions
Step 1: Install SDK
npm install @juicebox/sdk
# or: pip install juicebox-sdk
Step 2: Configure Authentication
export JUICEBOX_API_KEY="jb_live_..."
echo 'JUICEBOX_API_KEY=jb_live_your-key' >> .env
Step 3: Verify Connection
import { JuiceboxClient } from '@juicebox/sdk';
const client = new JuiceboxClient({ apiKey: process.env.JUICEBOX_API_KEY });
const results = await client.search({ query: 'engineer', limit: 1 });
console.log(`Connected! ${results.total} profiles available`);
from juicebox import JuiceboxClient
client = JuiceboxClient(api_key=os.environ['JUICEBOX_API_KEY'])
results = client.search(query='engineer', limit=1)
print(f'Connected! {results.total} profiles')
Error Handling
| Error |
Code |
Solution |
| Invalid API key |
401 |
Verify at app.juicebox.ai/settings |
| Plan limit exceeded |
403 |
Upgrade plan or check quota |
| Rate limited |
429 |
Check Retry-After header |
Resources
Next Steps
After auth, proceed to juicebox-hello-world.
Configure Juicebox local dev workflow.
ReadWriteEditBash(npm:*)Grep
Juicebox Local Dev Loop
Overview
Local development workflow for Juicebox AI-powered people analysis and recruiting API integration. Provides a fast feedback loop with mock profile search results and candidate enrichment data so you can build talent pipeline tools without consuming live API credits. Toggle between mock mode for rapid iteration and sandbox mode for validating against the real Juicebox API.
Environment Setup
cp .env.example .env
# Set your credentials:
# JUICEBOX_API_KEY=jb_live_xxxxxxxxxxxx
# JUICEBOX_BASE_URL=https://api.juicebox.work/v1
# MOCK_MODE=true
npm install express axios dotenv tsx typescript @types/node
npm install -D vitest supertest @types/express
Dev Server
// src/dev/server.ts
import express from "express";
import { createProxyMiddleware } from "http-proxy-middleware";
const app = express();
app.use(express.json());
const MOCK = process.env.MOCK_MODE === "true";
if (!MOCK) {
app.use("/v1", createProxyMiddleware({
target: process.env.JUICEBOX_BASE_URL,
changeOrigin: true,
headers: { Authorization: `Bearer ${process.env.JUICEBOX_API_KEY}` },
}));
} else {
const { mountMockRoutes } = require("./mocks");
mountMockRoutes(app);
}
app.listen(3004, () => console.log(`Juicebox dev server on :3004 [mock=${MOCK}]`));
Mock Mode
// src/dev/mocks.ts — realistic people search and enrichment responses
export function mountMockRoutes(app: any) {
app.post("/v1/search", (req: any, res: any) => res.json({
total: 150,
profiles: [
{ id: "prof_1", name: "Jane Smith", title: "Senior Engineer", company: "Google", location: "San Francisco, CA", skills: ["TypeScript", "React", "GCP"] },
{ id: "prof_2", name: "Alex Chen", title: "Staff ML Engineer", company: "Meta", location: "New York, NY", skills: ["Python", "PyTorch", "MLOps"] },
],
}));
app.get("/v1/profiles/:id", (req: any, res: any) => res.json({
id: req.params.id, name: "Jane Smith", title: "Senior Engineer", company: "Google",
experience: [{ role: "Senior Engineer", company: "Google", years: 3 }],
education: [{ school: "MIT", degree: "BS Computer Science" }],
}));
app.get("/v1/usage", (_req: any, res: any) => res.json({ creditsUsed: 12, creditsRemaining: 488, plan: "starter" }));
}
Testing Workflow
npm run dev:mock & # Start mock server in background
npm run test # Unit tests with vitest
npm run test -- --watch
Migrate to Juicebox from other tools.
ReadWriteEditGrep
Juicebox Migration Deep Dive
Comparison
| Feature |
LinkedIn Recruiter |
Juicebox |
| Search |
Boolean only |
Natural language (PeopleGPT) |
| Contact data |
InMail only |
Email + phone |
| ATS integration |
Limited |
41+ systems |
| AI features |
Basic |
AI Skills Map, research profiles |
Migration Steps
- Export saved searches from current tool
- Translate boolean queries to natural language
- Re-create talent pools in Juicebox
- Configure ATS integration
- Set up outreach sequences
Query Translation
# Boolean: ("software engineer" OR "SWE") AND "Python" AND "San Francisco"
# PeopleGPT: software engineer with Python experience in San Francisco
Resources
Next Steps
Start with juicebox-install-auth.
Configure Juicebox multi-environment.
ReadWriteEditGrep
Juicebox Multi-Environment Setup
Overview
Juicebox AI analysis requires environment separation to enforce workspace isolation, control data access, and prevent accidental exports of sensitive datasets. Development works with sample datasets and strict result limits for fast iteration, staging connects to full production data but disables export functionality, and production enables all features with full export capabilities. Each environment uses isolated workspaces so analysis experiments in dev never affect production workspace state or user-facing reports.
Environment Configuration
const juiceboxConfig = (env: string) => ({
development: {
apiKey: process.env.JB_KEY_DEV!, baseUrl: "https://api.dev.juicebox.work/v1",
workspaceId: process.env.JB_WORKSPACE_DEV!, resultLimit: 5, exportEnabled: false,
},
staging: {
apiKey: process.env.JB_KEY_STG!, baseUrl: "https://api.staging.juicebox.work/v1",
workspaceId: process.env.JB_WORKSPACE_STG!, resultLimit: 20, exportEnabled: false,
},
production: {
apiKey: process.env.JB_KEY_PROD!, baseUrl: "https://api.juicebox.work/v1",
workspaceId: process.env.JB_WORKSPACE_PROD!, resultLimit: 50, exportEnabled: true,
},
}[env]);
Environment Files
# Per-env files: .env.development, .env.staging, .env.production
JB_KEY_{DEV|STG|PROD}=<api-key>
JB_WORKSPACE_{DEV|STG|PROD}=<workspace-id>
JB_BASE_URL=https://api.{dev.|staging.|""}juicebox.work/v1
JB_EXPORT_ENABLED={false|false|true}
JB_DATASET_SCOPE={sample|full|full}
Environment Validation
function validateJuiceboxEnv(env: string): void {
const suffix = { development: "_DEV", staging: "_STG", production: "_PROD" }[env];
const required = [`JB_KEY${suffix}`, `JB_WORKSPACE${suffix}`, "JB_BASE_URL"];
const missing = required.filter((k) => !process.env[k]);
if (missing.length) throw new Error(`Missing Juicebox vars for ${env}: ${missing.join(", ")}`);
}
Promotion Workflow
# 1. Run analysis queries against sample data in dev
curl -X POST "$JB_BASE_URL/analyze" \
-H "Authorization: Bearer $JB_KEY_DEV" -d @test-query.json
# 2. Validate same queries against full dataset in staging (exports blocked)
curl -X POST "$JB_BASE_URL/analyze" \
-H "Authorization: Bearer $JB_KEY_STG" -d @test-query.json | jq '.resultCount'
# 3. Verify workspace isolation — staging results don't appear in prod
curl "$JB_BASE_URL/workspaces/$JB_WORKSPACE_PROD/reports" \
-H "Authorization: Bearer $JB_KEY_PROD" | jq 'length'
# 4. Deploy to production with export capabilities enabled
JB_EXPORT_ENABLED=true npm run deploy -- --env production
<
Set up Juicebox monitoring.
ReadWriteEditGrep
Juicebox Observability
Overview
Juicebox provides AI-powered people search and analysis where query performance, dataset ingestion rates, and quota consumption are the primary observability concerns. Monitor analysis completion times to ensure interactive UX, track ingestion pipeline health for data freshness, and watch quota usage to prevent mid-workflow cutoffs. Slow queries or failed ingestions degrade recruiter productivity and data accuracy.
Key Metrics
| Metric |
Type |
Target |
Alert Threshold |
| Search latency p95 |
Histogram |
< 2s |
> 5s |
| Analysis completion time |
Histogram |
< 10s |
> 30s |
| Dataset ingestion rate |
Gauge |
> 100 records/s |
< 50 records/s |
| API error rate |
Gauge |
< 1% |
> 5% |
| Quota usage (daily) |
Gauge |
< 70% |
> 85% |
| Query result relevance |
Gauge |
> 80% precision |
< 60% |
Instrumentation
async function trackJuiceboxCall(operation: string, fn: () => Promise<any>) {
const start = Date.now();
try {
const result = await fn();
metrics.histogram('juicebox.api.latency', Date.now() - start, { operation });
metrics.increment('juicebox.api.calls', { operation, status: 'ok' });
return result;
} catch (err) {
metrics.increment('juicebox.api.errors', { operation, error: err.code });
throw err;
}
}
Health Check Dashboard
async function juiceboxHealth(): Promise<Record<string, string>> {
const searchP95 = await metrics.query('juicebox.api.latency', 'p95', '5m');
const errorRate = await metrics.query('juicebox.api.error_rate', 'avg', '5m');
const quota = await juiceboxAdmin.getQuotaUsage();
return {
search_latency: searchP95 < 2000 ? 'healthy' : 'slow',
error_rate: errorRate < 0.01 ? 'healthy' : 'degraded',
quota: quota.pct < 0.7 ? 'healthy' : 'at_risk',
};
}
Alerting Rules
const alerts = [
{ metric: 'juicebox.search.latency_p95', condition: '> 5s', window: '10m', severity: 'warning' },
{ metric: 'juicebox.api.error_rate', condition: '> 0.05', window: '5m', severity: 'critical' },
{ metric: 'juicebox.quota.daily_pct', condition: '> 0.85', window: '1h', severity: 'warning' },
{ metric: 'juicebox.ingestion.rate', condition: '< 50/s', window: '15m', severity: 'critical' },
Optimize Juicebox performance.
ReadWriteEditGrep
Juicebox Performance Tuning
Overview
Juicebox's AI analysis API handles dataset uploads, analysis queue wait times, and result pagination. Large dataset uploads (100K+ rows) can block the analysis pipeline, while queue contention during peak hours increases wait times. Result sets from broad queries return thousands of profiles requiring efficient pagination. Caching search results, batching enrichment calls, and managing upload chunking reduces end-to-end analysis time by 40-60% and keeps interactive searches responsive.
Caching Strategy
const cache = new Map<string, { data: any; expiry: number }>();
const TTL = { search: 300_000, profile: 600_000, analysis: 900_000 };
async function cached(key: string, ttlKey: keyof typeof TTL, fn: () => Promise<any>) {
const entry = cache.get(key);
if (entry && entry.expiry > Date.now()) return entry.data;
const data = await fn();
cache.set(key, { data, expiry: Date.now() + TTL[ttlKey] });
return data;
}
// Analysis results are expensive — cache 15 min. Searches expire at 5 min.
Batch Operations
async function enrichBatch(client: any, profileIds: string[], batchSize = 50) {
const results = [];
for (let i = 0; i < profileIds.length; i += batchSize) {
const batch = profileIds.slice(i, i + batchSize);
const res = await client.enrichBatch({ profile_ids: batch, fields: ['skills_map', 'contact'] });
results.push(...res.profiles);
if (i + batchSize < profileIds.length) await new Promise(r => setTimeout(r, 300));
}
return results;
}
Connection Pooling
import { Agent } from 'https';
const agent = new Agent({ keepAlive: true, maxSockets: 8, maxFreeSockets: 4, timeout: 60_000 });
// Longer timeout for dataset uploads and analysis queue responses
Rate Limit Management
async function withRateLimit(fn: () => Promise<any>): Promise<any> {
try { return await fn(); }
catch (err: any) {
if (err.status === 429) {
const backoff = parseInt(err.headers?.['retry-after'] || '10') * 1000;
await new Promise(r => setTimeout(r, backoff));
return fn();
}
throw err;
}
}
Monitoring
const metrics = { searches: 0, enrichments: 0, cacheHits: 0, queueWaitMs: 0, errors: 0 };
function track(op: 'search' | 'enrich', startMs: number, cached: boolean) {
metrics[op === 'search' ? 'searches' : 'enrichments']++;
metrics.queueWaitMs += Date.now() - startMs;
if (cached) metrics.cacheHits++;
}
Performance Checklist
- [ ] Use specific filters (location, skills, title) to narrow search scope
- [ ] Cache search results w
Execute Juicebox production checklist.
ReadBash(curl:*)Grep
Juicebox Production Checklist
Overview
Juicebox provides AI-powered people search and analysis, enabling dataset creation, candidate discovery, and structured analysis across professional profiles. A production integration queries datasets, retrieves analysis results, and powers talent intelligence workflows. Failures mean missed candidates, stale analysis data, or quota exhaustion that blocks time-sensitive searches.
Authentication & Secrets
- [ ]
JUICEBOXAPIKEY stored in secrets manager (not config files)
- [ ] API key scoped to production workspace only
- [ ] Key rotation schedule documented (90-day cycle)
- [ ] Separate credentials for dev/staging/prod environments
- [ ] Candidate data access restricted to authorized roles
API Integration
- [ ] Production base URL configured (
https://api.juicebox.ai/v1)
- [ ] Rate limiting configured per plan tier
- [ ] Dataset creation and query endpoints tested end-to-end
- [ ] Analysis result pagination implemented for large datasets
- [ ] Search query optimization validated (precision vs recall tradeoffs)
- [ ] Bulk analysis requests batched to avoid rate limits
- [ ] Result caching configured for repeated queries
Error Handling & Resilience
- [ ] Circuit breaker configured for Juicebox API outages
- [ ] Retry with exponential backoff for 429/5xx responses
- [ ] Candidate data encrypted at rest in downstream storage
- [ ] GDPR/CCPA retention policy enforced on stored profiles
- [ ] Empty result sets handled gracefully (no silent failures)
- [ ] Quota exhaustion detected before critical searches fail
Monitoring & Alerting
- [ ] API latency tracked per endpoint (search, analysis, datasets)
- [ ] Error rate alerts set (threshold: >5% over 5 minutes)
- [ ] Quota usage monitored with alert at 80% consumption
- [ ] Analysis completion rate tracked for reliability metrics
- [ ] Daily digest of search volumes and result quality
Validation Script
async function checkJuiceboxReadiness(): Promise<void> {
const checks: { name: string; pass: boolean; detail: string }[] = [];
// API connectivity
try {
const res = await fetch('https://api.juicebox.ai/v1/search', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.JUICEBOX_API_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query: 'test', limit: 1 }),
});
checks.push({ name: 'Juicebox API', pass: res.ok, detail: res.ok ? 'Connected' : `HTTP ${res.status}` });
} catch (e: any) { checks.push({ name: 'Juicebox API', pass: false, detail: e.message }); }
// Credentials present
checks.push({ nam
Implement Juicebox rate limiting.
ReadWriteEdit
Juicebox Rate Limits
Overview
Juicebox's AI-powered data analysis API enforces plan-tiered rate limits across dataset uploads, analysis triggers, and result retrieval. Heavy analytical workloads like running comparative analyses across multiple datasets or batch-processing survey results hit the analysis trigger limit first. The enrichment endpoints for augmenting datasets with external data sources have separate, lower caps, making it essential to prioritize enrichment calls and batch analysis runs during off-peak windows.
Rate Limit Reference
| Endpoint |
Limit |
Window |
Scope |
| Dataset upload |
20 req |
1 minute |
Per API key |
| Analysis trigger |
30 req |
1 minute |
Per API key |
| Result retrieval |
120 req |
1 minute |
Per API key |
| Data enrichment |
15 req |
1 minute |
Per API key |
| Export download |
10 req |
1 minute |
Per API key |
Rate Limiter Implementation
class JuiceboxRateLimiter {
private tokens: number;
private lastRefill: number;
private readonly max: number;
private readonly refillRate: number;
private queue: Array<{ resolve: () => void }> = [];
constructor(maxPerMinute: number) {
this.max = maxPerMinute;
this.tokens = maxPerMinute;
this.lastRefill = Date.now();
this.refillRate = maxPerMinute / 60_000;
}
async acquire(): Promise<void> {
this.refill();
if (this.tokens >= 1) { this.tokens -= 1; return; }
return new Promise(resolve => this.queue.push({ resolve }));
}
private refill() {
const now = Date.now();
this.tokens = Math.min(this.max, this.tokens + (now - this.lastRefill) * this.refillRate);
this.lastRefill = now;
while (this.tokens >= 1 && this.queue.length) {
this.tokens -= 1;
this.queue.shift()!.resolve();
}
}
}
const analysisLimiter = new JuiceboxRateLimiter(25);
const enrichLimiter = new JuiceboxRateLimiter(12);
Retry Strategy
async function juiceboxRetry<T>(
limiter: JuiceboxRateLimiter, fn: () => Promise<Response>, maxRetries = 3
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
await limiter.acquire();
const res = await fn();
if (res.ok) return res.json();
if (res.status === 429) {
const retryAfter = parseInt(res.headers.get("Retry-After") || "15", 10);
const jitter = Math.random() * 3000;
await new Promise(r => setTimeout(r, retryAfter * 1000 + jitter));
continue;
}
if (res.status >= 500 && attempt < maxRetries) {
await new Promise(r => setTimeout(r, Ma
Implement Juicebox reference architecture.
ReadWriteEditGrep
Juicebox Reference Architecture
Overview
Production architecture for AI-powered candidate analysis integrations with Juicebox. Designed for recruiting teams needing automated dataset ingestion from job descriptions, intelligent candidate scoring and ranking, result caching for repeated searches, and seamless export to ATS platforms like Greenhouse and Lever. Key design drivers: search result freshness, candidate deduplication across sources, outreach sequencing, and analysis pipeline throughput for high-volume hiring.
Architecture Diagram
Recruiter Dashboard ──→ Search Service ──→ Cache (Redis) ──→ Juicebox API
↓ /search
Queue (Bull) ──→ Analysis Worker /profiles
↓ /outreach
ATS Export Service ──→ Greenhouse/Lever
↓
Webhook Handler ←── Juicebox Events
Service Layer
class CandidateSearchService {
constructor(private juicebox: JuiceboxClient, private cache: CacheLayer) {}
async findAndRank(criteria: SearchCriteria): Promise<RankedCandidate[]> {
const cacheKey = `search:${this.hashCriteria(criteria)}`;
const cached = await this.cache.get(cacheKey);
if (cached) return cached;
const results = await this.juicebox.search(criteria);
const ranked = results.profiles.map(p => ({ ...p, score: this.scoreCandidate(p, criteria) }))
.sort((a, b) => b.score - a.score);
await this.cache.set(cacheKey, ranked, CACHE_CONFIG.searchResults.ttl);
return ranked;
}
async exportToATS(candidates: string[], jobId: string, ats: 'greenhouse' | 'lever'): Promise<ExportResult> {
const deduped = await this.deduplicateAgainstATS(candidates, jobId, ats);
return this.juicebox.export({ profiles: deduped, destination: ats, job_id: jobId });
}
}
Caching Strategy
const CACHE_CONFIG = {
searchResults: { ttl: 1800, prefix: 'search' }, // 30 min — candidate pools shift slowly
profiles: { ttl: 3600, prefix: 'profile' }, // 1 hr — profile data stable short-term
analysisRuns: { ttl: 7200, prefix: 'analysis' }, // 2 hr — analysis results are expensive to recompute
atsState: { ttl: 300, prefix: 'ats' }, // 5 min — ATS pipeline freshness for dedup
outreach: { ttl: 60, prefix: 'outreach' }, // 1 min — sequence status changes frequently
};
// New search invalidates matching cached results; ATS export clears ats cache for that job
Event Pipeline
class RecruitingPipeline {
private queue = new Bull('juicebox-events', { redis: process.env.REDIS_URL });
async onSearchComplete(searchI
Apply production Juicebox SDK patterns.
ReadWriteEdit
Juicebox SDK Patterns
Overview
Production-ready patterns for the Juicebox AI-powered people search API. Juicebox provides REST endpoints for searching professional profiles and enriching candidate data. The API authenticates via JUICEBOXAPIKEY and returns structured profile objects with LinkedIn URLs as natural dedup keys. A singleton client centralizes rate-limit handling across search and enrich endpoints.
Singleton Client
const JUICEBOX_BASE = 'https://api.juicebox.work/v1';
let _client: JuiceboxClient | null = null;
export function getClient(): JuiceboxClient {
if (!_client) {
const apiKey = process.env.JUICEBOX_API_KEY;
if (!apiKey) throw new Error('JUICEBOX_API_KEY must be set — get it from juicebox.work/settings');
_client = new JuiceboxClient(apiKey);
}
return _client;
}
class JuiceboxClient {
private headers: Record<string, string>;
constructor(apiKey: string) { this.headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' }; }
async search(query: string, limit = 20): Promise<SearchResponse> {
const res = await fetch(`${JUICEBOX_BASE}/search`, {
method: 'POST', headers: this.headers, body: JSON.stringify({ query, limit }) });
if (!res.ok) throw new JuiceboxError(res.status, await res.text()); return res.json();
}
async enrich(linkedinUrl: string): Promise<Profile> {
const res = await fetch(`${JUICEBOX_BASE}/enrich`, {
method: 'POST', headers: this.headers, body: JSON.stringify({ linkedin_url: linkedinUrl }) });
if (!res.ok) throw new JuiceboxError(res.status, await res.text()); return res.json();
}
}
Error Wrapper
export class JuiceboxError extends Error {
constructor(public status: number, message: string) { super(message); this.name = 'JuiceboxError'; }
}
export async function safeCall<T>(operation: string, fn: () => Promise<T>): Promise<T> {
try { return await fn(); }
catch (err: any) {
if (err instanceof JuiceboxError && err.status === 429) { await new Promise(r => setTimeout(r, 5000)); return fn(); }
if (err instanceof JuiceboxError && err.status === 401) throw new JuiceboxError(401, 'Invalid JUICEBOX_API_KEY');
throw new JuiceboxError(err.status ?? 0, `${operation} failed: ${err.message}`);
}
}
Request Builder
class JuiceboxSearchBuilder {
private body: Record<string, any> = {};
query(q: string) { this.body.query = q; return this; }
limit(n: number) { this.body.limit = Math.min(n, 100); return this; }
location(loc: string) { this.body.location = loc; return this; }
title(t: string) { this.body.title_filter = t; return this; }
company(c: string) { this.body.company_filter = c; retu
Apply Juicebox security best practices.
ReadWriteGrep
Juicebox Security Basics
Overview
Juicebox provides AI-powered people search and analysis, processing datasets containing professional profiles, contact enrichment data, and query results. Security concerns include API key protection, GDPR/CCPA compliance for candidate and contact data, data retention policy enforcement, and ensuring enriched contact information (emails, phone numbers) is not leaked through logs or unencrypted storage. A compromised API key grants access to people search and enrichment capabilities.
API Key Management
function createJuiceboxClient(): { apiKey: string; baseUrl: string } {
const apiKey = process.env.JUICEBOX_API_KEY;
if (!apiKey) {
throw new Error("Missing JUICEBOX_API_KEY — store in secrets manager, never in code");
}
// Juicebox keys access people data — treat as PII-adjacent
console.log("Juicebox client initialized (key suffix:", apiKey.slice(-4), ")");
return { apiKey, baseUrl: "https://api.juicebox.ai/v1" };
}
Webhook Signature Verification
import crypto from "crypto";
import { Request, Response, NextFunction } from "express";
function verifyJuiceboxWebhook(req: Request, res: Response, next: NextFunction): void {
const signature = req.headers["x-juicebox-signature"] as string;
const secret = process.env.JUICEBOX_WEBHOOK_SECRET!;
const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
if (!signature || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
res.status(401).send("Invalid signature");
return;
}
next();
}
Input Validation
import { z } from "zod";
const PeopleSearchSchema = z.object({
query: z.string().min(1).max(500),
filters: z.object({
location: z.string().optional(),
company: z.string().optional(),
title: z.string().optional(),
industry: z.string().optional(),
}).optional(),
max_results: z.number().int().min(1).max(100).default(25),
enrich_contacts: z.boolean().default(false),
});
function validateSearchQuery(data: unknown) {
return PeopleSearchSchema.parse(data);
}
Data Protection
const JUICEBOX_PII_FIELDS = ["personal_email", "phone_number", "social_profiles", "home_address", "enrichment_data"];
function redactJuiceboxLog(record: Record<string, unknown>): Record<string, unknown> {
const redacted = { ...record };
for (const field of JUICEBOX_PII_FIELDS) {
if (field in redacted) redacted[field] = "[REDACTED]";
}
return redacted;
}
Security Checklist
- [ ] API keys stored in secrets manager, separate keys per environment
Plan Juicebox SDK upgrades.
ReadWriteEditBash(npm:*)
Juicebox Upgrade & Migration
Overview
Juicebox is an AI-powered people search and analysis platform used for recruiting and market research. The API provides endpoints for dataset management, people searches, and AI-generated analyses. Tracking API versions is essential because Juicebox evolves its search query syntax, dataset schema, and analysis output format — upgrading without testing can break saved search filters, corrupt dataset imports, and change the structure of AI-generated candidate profiles that downstream systems consume.
Version Detection
const JUICEBOX_BASE = "https://api.juicebox.work/v1";
async function detectJuiceboxVersion(apiKey: string): Promise<void> {
const res = await fetch(`${JUICEBOX_BASE}/datasets`, {
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
});
const version = res.headers.get("x-juicebox-api-version") ?? "v1";
console.log(`Juicebox API version: ${version}`);
// Check for deprecated search parameters
const searchRes = await fetch(`${JUICEBOX_BASE}/search`, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify({ query: "test", limit: 1 }),
});
const deprecation = searchRes.headers.get("x-deprecated-params");
if (deprecation) console.warn(`Deprecated search params: ${deprecation}`);
}
Migration Checklist
- [ ] Review Juicebox changelog for API breaking changes
- [ ] Audit codebase for hardcoded dataset field names
- [ ] Verify search query syntax — filter operators may have changed
- [ ] Check analysis output format for new or renamed fields
- [ ] Update dataset import schema if column mapping changed
- [ ] Test people search result structure (profile fields, enrichment data)
- [ ] Validate pagination — cursor-based vs. offset may have changed
- [ ] Update SDK version in
package.json and verify type compatibility
- [ ] Check webhook payloads for analysis completion events
- [ ] Run integration tests with sample dataset to verify search quality
Schema Migration
// Juicebox search results evolved: flat profile → enriched profile with sources
interface OldSearchResult {
id: string;
name: string;
title: string;
company: string;
email?: string;
linkedin_url?: string;
}
interface NewSearchResult {
id: string;
profile: {
full_name: string;
current_title: string;
current_company: { name: string; domain: string };
emails: Array<{ address: string; type: "work" | "personal"; verified: boolean }>;
social: { linkedin?: string; twitter?: string };
};
match_score: number;
enrichment_sources: s
Handle Juicebox webhooks and events.
ReadWriteEditGrep
Juicebox Webhooks & Events
Overview
Juicebox delivers webhook notifications for AI-powered people search and analysis workflows. Subscribe to events for completed analyses, updated datasets, ready exports, and quota warnings to build automated pipelines that react to Juicebox intelligence in real time without polling the API.
Webhook Registration
const response = await fetch("https://api.juicebox.ai/v1/webhooks", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.JUICEBOX_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://yourapp.com/webhooks/juicebox",
events: ["analysis.completed", "dataset.updated", "export.ready", "quota.warning"],
secret: process.env.JUICEBOX_WEBHOOK_SECRET,
}),
});
Signature Verification
import crypto from "crypto";
import { Request, Response, NextFunction } from "express";
function verifyJuiceboxSignature(req: Request, res: Response, next: NextFunction) {
const signature = req.headers["x-juicebox-signature"] as string;
const expected = crypto.createHmac("sha256", process.env.JUICEBOX_WEBHOOK_SECRET!)
.update(req.body).digest("hex");
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.status(401).json({ error: "Invalid signature" });
}
next();
}
Event Handler
import express from "express";
const app = express();
app.post("/webhooks/juicebox", express.raw({ type: "application/json" }), verifyJuiceboxSignature, (req, res) => {
const event = JSON.parse(req.body.toString());
res.status(200).json({ received: true });
switch (event.type) {
case "analysis.completed":
fetchResults(event.data.analysis_id, event.data.result_count); break;
case "dataset.updated":
refreshDashboard(event.data.dataset_id, event.data.records_added); break;
case "export.ready":
downloadExport(event.data.export_id, event.data.download_url); break;
case "quota.warning":
notifyAdmin(event.data.usage_percent, event.data.reset_date); break;
}
});
Event Types
| Event |
Payload Fields |
Use Case |
analysis.completed |
analysisid, resultcount, query |
Fetch and process search results |
dataset.updated |
datasetid, recordsadded, total_records |
Refresh downstream dashboards |
export.ready
Ready to use juicebox-pack?
|
|
|