Circle V2 API Docs
    Preparing search index...

    Jobs and Crons

    Circle V2 uses Vercel Queues for async background jobs and Vercel Cron Jobs for scheduled tasks. Both are configured via apps/web/vercel.json.

    Background jobs are built on the @repo/vqs package, which wraps Vercel Queues with deduplication, status tracking, and retry logic. See packages/vqs/README.md for the full API.

    Producer                        Vercel Queue              Consumer
    ───────── ──────────── ────────
    job.performLater(params) --> topic: "sync-patient" --> POST /api/queue/sync-patient
    └── createQueueHandler(SyncPatientJobbable)
    └── job.runTracked(params, metadata)
    • Producers can be any server-side code (tRPC procedures, cron handlers, other jobs)
    • Consumers are Next.js route handlers wired up via createQueueHandler
    • Each topic maps to exactly one route file (Vercel constraint)
    apps/web/
    shared/jobs/
    sync-patient.jobbable.ts # Job class (extends Jobbable)
    app/api/queue/
    sync-patient/route.ts # Route handler (one per topic)

    packages/vqs/ # Shared queue infrastructure

    apps/web/vercel.json # Queue + cron config
    1. Create apps/web/shared/jobs/<name>.jobbable.ts extending Jobbable
    2. Create apps/web/app/api/queue/<topic>/route.ts with createQueueHandler
    3. Add an experimentalTriggers entry in apps/web/vercel.json

    See the @repo/vqs README for a step-by-step example with code.

    Failed jobs are retried with exponential backoff ((10 * attempt)^2 seconds) up to the maxAttempts defined in the Jobbable's config. After exhausting retries, the message is acknowledged and deleted.

    All job executions are tracked in the job_executions database table with statuses: running, success, failed.

    Cron jobs are Next.js GET route handlers that Vercel calls on a schedule.

    Cron routes live under apps/web/app/api/cron/ and are registered in vercel.json:

    {
    "crons": [
    {
    "path": "/api/cron/sync-patients",
    "schedule": "0 * * * *"
    }
    ]
    }

    All cron routes must be wrapped with withCronSecret from @repo/auth-next/api:

    // app/api/cron/sync-patients/route.ts
    import { withCronSecret } from "@repo/auth-next/api";

    export const GET = withCronSecret(async (_request: Request) => {
    // Cron logic here -- typically enqueues jobs
    return new Response("OK", { status: 200 });
    });

    withCronSecret validates the Authorization: Bearer <CRON_SECRET> header that Vercel sends automatically. In local dev (IS_LOCAL_DEV=true + localhost), the check is bypassed.

    The typical pattern is for crons to enqueue jobs rather than doing heavy work inline. This gives you retry logic, status tracking, and timeout isolation for free:

    export const GET = withCronSecret(async () => {
    const job = new SyncPatientJobbable();
    await job.performLater({ patientId: 123 });
    return new Response("OK", { status: 200 });
    });
    1. Create apps/web/app/api/cron/<name>/route.ts with a GET handler wrapped in withCronSecret
    2. Add a crons entry in apps/web/vercel.json with the path and schedule
    3. Set CRON_SECRET in your Vercel project environment variables (Vercel sends this automatically)

    Standard cron expressions:

    Expression Meaning
    0 * * * * Every hour
    */15 * * * * Every 15 minutes
    0 0 * * * Daily at midnight UTC
    0 9 * * 1 Every Monday at 9am UTC

    Vercel cron jobs run in UTC. Minimum interval on the Pro plan is 1 minute.


    Next: Authentication and Sessions