Skip to main content
AI/MLjeremylongshore

clari-rate-limits

'Handle Clari API rate limits with backoff and export job scheduling.

Stars
2,267
Source
jeremylongshore/claude-code-plugins-plus-skills
Updated
2026-05-31
Slug
jeremylongshore--claude-code-plugins-plus-skills--clari-rate-limits
View on GitHubRaw SKILL.md

// install — copy + paste into any project

mkdir -p .claude/skills && curl -fsSL https://raw.githubusercontent.com/jeremylongshore/claude-code-plugins-plus-skills/HEAD/plugins/saas-packs/clari-pack/skills/clari-rate-limits/SKILL.md -o .claude/skills/clari-rate-limits.md

Drops the SKILL.md into .claude/skills/clari-rate-limits.md. Works with Claude Code, Cursor, and any agent that loads SKILL.md files from .claude/skills/.

Clari Rate Limits

Overview

The Clari API enforces rate limits per API key. Export jobs are asynchronous and queued server-side, so the primary concern is polling frequency and concurrent export requests.

Rate Limit Behavior

Aspect Value
Scope Per API key
Response on limit HTTP 429
Export job queue Server-managed, async
Recommended polling 5-10 second intervals

Instructions

Exponential Backoff for Export Polling

import time
import requests

def poll_with_backoff(
    job_id: str,
    api_key: str,
    max_attempts: int = 60,
    base_delay: float = 5.0,
    max_delay: float = 60.0,
) -> dict:
    for attempt in range(max_attempts):
        resp = requests.get(
            f"https://api.clari.com/v4/export/jobs/{job_id}",
            headers={"apikey": api_key},
        )

        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", base_delay))
            time.sleep(retry_after)
            continue

        resp.raise_for_status()
        status = resp.json()

        if status["status"] in ("COMPLETED", "FAILED"):
            return status

        delay = min(base_delay * (1.5 ** attempt), max_delay)
        time.sleep(delay)

    raise TimeoutError(f"Job {job_id} did not complete in {max_attempts} attempts")

Sequential Export Scheduler

def export_all_periods(
    client,
    forecast_name: str,
    periods: list[str],
    delay_between: float = 10.0,
) -> list[dict]:
    results = []
    for period in periods:
        print(f"Exporting {period}...")
        job = client.export_forecast(forecast_name, period)
        result = poll_with_backoff(job["jobId"], client.config.api_key)
        results.append(result)
        time.sleep(delay_between)  # Avoid hitting rate limits
    return results

Error Handling

Scenario Detection Response
429 with Retry-After Check header Wait exact duration
429 without header Status code only Backoff from 5s
Job queue full Multiple pending jobs Wait for completion before new exports

Resources

Next Steps

For security configuration, see clari-security-basics.