Claude-code-plugins-plus-skills juicebox-rate-limits

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/juicebox-pack/skills/juicebox-rate-limits" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-juicebox-rate-limits && rm -rf "$T"
manifest: plugins/saas-packs/juicebox-pack/skills/juicebox-rate-limits/SKILL.md
source content

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

EndpointLimitWindowScope
Dataset upload20 req1 minutePer API key
Analysis trigger30 req1 minutePer API key
Result retrieval120 req1 minutePer API key
Data enrichment15 req1 minutePer API key
Export download10 req1 minutePer 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, Math.pow(2, attempt) * 2000));
      continue;
    }
    throw new Error(`Juicebox API ${res.status}: ${await res.text()}`);
  }
  throw new Error("Max retries exceeded");
}

Batch Processing

async function batchAnalyzeDatasets(datasetIds: string[], query: string, batchSize = 5) {
  const results: any[] = [];
  for (let i = 0; i < datasetIds.length; i += batchSize) {
    const batch = datasetIds.slice(i, i + batchSize);
    const batchResults = await Promise.all(
      batch.map(id => juiceboxRetry(analysisLimiter, () =>
        fetch(`${BASE}/api/v1/datasets/${id}/analyze`, {
          method: "POST", headers,
          body: JSON.stringify({ query }),
        })
      ))
    );
    results.push(...batchResults);
    if (i + batchSize < datasetIds.length) await new Promise(r => setTimeout(r, 8000));
  }
  return results;
}

Error Handling

IssueCauseFix
429 on analysis triggerExceeded 30 req/min analysis capQueue analyses, space 3s apart
429 on enrichmentEnrichment limit (15/min) is lowestBatch enrichments separately with wider spacing
Upload timeoutDataset exceeds 50MBCompress CSV, use chunked upload endpoint
Analysis still processingComplex query on large datasetPoll status every 10s, timeout at 10 min
403 on exportPlan does not include export featureVerify plan tier supports data export

Resources

Next Steps

See

juicebox-performance-tuning
.