Skip to main content

Cron API Endpoints

The Cron API provides scheduled job endpoints that are triggered by Vercel Cron, external schedulers, or the internal BackgroundJobManager. All cron endpoints require authentication via the CRON_SECRET environment variable using a Bearer token in the Authorization header.

Source directory: template/app/api/cron/


Authentication

Cron endpoints use a shared secret for authorization:

  • Production: The CRON_SECRET environment variable must be set. Requests must include Authorization: Bearer <CRON_SECRET>.
  • Development: If CRON_SECRET is not configured, access is allowed without authentication for a frictionless local development experience.
  • Security: All cron endpoints use crypto.timingSafeEqual() for constant-time comparison to prevent timing attacks.

Unauthorized response (401):

{
"success": false,
"message": "Unauthorized - Invalid or missing cron secret"
}

Vercel Cron Configuration

The cron schedule is defined in vercel.json:

{
"crons": [
{
"path": "/api/cron/sync",
"schedule": "0 3 * * *"
},
{
"path": "/api/cron/subscription-reminders",
"schedule": "0 9 * * *"
},
{
"path": "/api/cron/subscription-expiration",
"schedule": "0 0 * * *"
}
]
}
JobScheduleDescription
Content SyncDaily at 3:00 AM UTCSynchronizes content from the Git-based CMS
Subscription RemindersDaily at 9:00 AM UTCSends renewal reminder emails
Subscription ExpirationDaily at midnight UTCProcesses expired subscriptions

Content Sync

Triggers a content synchronization from the Git-based CMS repository.

PropertyValue
MethodGET
Path/api/cron/sync
AuthCRON_SECRET (Bearer token)
Sourcecron/sync/route.ts

Response

Status 200 -- Sync completed successfully.

{
"success": true,
"timestamp": "2024-01-20T03:00:05.123Z",
"duration": 5123,
"message": "Sync completed successfully"
}

Status 500 -- Sync failed.

{
"success": false,
"timestamp": "2024-01-20T03:00:10.456Z",
"duration": 10456,
"message": "Cron sync failed",
"details": "Error description"
}
FieldTypeDescription
successbooleanWhether the sync succeeded
timestampstring (ISO 8601)When the sync completed
durationnumberDuration in milliseconds
messagestringHuman-readable status message
detailsstring (optional)Additional details on failure

Response Headers

All responses include Cache-Control: no-cache, no-store, must-revalidate to prevent caching of sync results.

curl Example

curl -s http://localhost:3000/api/cron/sync \
-H "Authorization: Bearer your-cron-secret-here"

Subscription Expiration

Finds and processes expired subscriptions by updating their status from active to expired and sending notification emails.

PropertyValue
MethodsGET, POST
Path/api/cron/subscription-expiration
AuthCRON_SECRET (Bearer token)
Sourcecron/subscription-expiration/route.ts

Response

Status 200 -- Processed successfully.

{
"success": true,
"message": "Processed 3 expired subscriptions",
"data": {
"processed": 3,
"affectedUsers": [
{
"subscriptionId": "sub_abc123",
"userId": "user_456",
"planId": "standard"
}
],
"errors": [],
"timestamp": "2024-01-20T00:00:05.123Z"
}
}
FieldTypeDescription
data.processednumberNumber of subscriptions updated to expired
data.affectedUsersarrayList of affected subscriptions (no PII)
data.errorsstring[]Any non-fatal errors (e.g., email delivery failures)
data.timestampstringProcessing timestamp

Processing Steps

  1. Finds active subscriptions past their end date.
  2. Updates status from active to expired.
  3. Sends expiration notification emails via the email service.
  4. Returns a summary -- email failures do not cause the entire job to fail.

curl Example

# Via GET
curl -s http://localhost:3000/api/cron/subscription-expiration \
-H "Authorization: Bearer your-cron-secret-here"

# Via POST (also supported for manual triggers)
curl -s -X POST http://localhost:3000/api/cron/subscription-expiration \
-H "Authorization: Bearer your-cron-secret-here"

Subscription Reminders

Sends renewal reminder emails to users with subscriptions nearing their renewal date.

PropertyValue
MethodsGET, POST
Path/api/cron/subscription-reminders
AuthCRON_SECRET (Bearer token)
Sourcecron/subscription-reminders/route.ts

Response

Status 200 -- Job completed successfully.

{
"message": "Subscription reminder job completed",
"success": true,
"sent": 5,
"errors": []
}

Status 207 -- Job completed with partial errors (Multi-Status).

{
"error": "Job completed with errors",
"success": false,
"sent": 3,
"errors": ["Failed to send reminder to user_123"]
}

curl Example

curl -s http://localhost:3000/api/cron/subscription-reminders \
-H "Authorization: Bearer your-cron-secret-here"

Background Jobs Initialization

The background jobs module (cron/jobs/background-jobs-init.ts) is not an API endpoint but a singleton initialization module used to configure the scheduling mode on application startup.

Source: cron/jobs/background-jobs-init.ts

Scheduling Modes

ModeDescription
vercelJobs handled by Vercel Cron via /api/cron/* endpoints
localInternal scheduler (for self-hosted deployments)
trigger-devTrigger.dev integration for managed background jobs
disabledBackground sync disabled (DISABLE_AUTO_SYNC=true)

Usage

import { ensureBackgroundJobsInitialized } from '@/app/api/cron/jobs/background-jobs-init';

// Called once from layout.tsx -- safe to call multiple times
await ensureBackgroundJobsInitialized();

Key Features

  • Uses globalThis for singleton state, ensuring initialization runs only once per process.
  • Skips initialization during tests (NODE_ENV=test) and builds (NEXT_PHASE=phase-production-build).
  • Failed initialization resets state to allow automatic retry on the next call.

TypeScript Usage

// Trigger content sync programmatically
async function triggerSync(cronSecret: string): Promise<void> {
const res = await fetch('/api/cron/sync', {
headers: { Authorization: `Bearer ${cronSecret}` },
});
const data = await res.json();

if (data.success) {
console.log(`Sync completed in ${data.duration}ms`);
} else {
console.error('Sync failed:', data.message, data.details);
}
}

// Check subscription expiration
async function processExpirations(cronSecret: string): Promise<void> {
const res = await fetch('/api/cron/subscription-expiration', {
headers: { Authorization: `Bearer ${cronSecret}` },
});
const data = await res.json();
console.log(`Processed ${data.data.processed} expirations`);

if (data.data.errors.length > 0) {
console.warn('Non-fatal errors:', data.data.errors);
}
}

Environment Variables

VariableRequiredDescription
CRON_SECRETProduction: Yes, Dev: NoShared secret for cron endpoint authentication
DISABLE_AUTO_SYNCNoSet to true to disable automatic content sync