Background jobs
Use crowdinApp.cron.schedule(expression, task) to schedule background work. For work across installed accounts, forEachCrowdinConnection supplies an authenticated Crowdin API client for each stored installation, renews tokens automatically, and isolates installation failures.
Schedule a job
Section titled “Schedule a job”import { addCrowdinEndpoints, express } from '@crowdin/app-project-module';
const app = express();const crowdinApp = addCrowdinEndpoints(app, { name: 'Project cache', identifier: 'project-cache', description: 'Refresh project metadata hourly', clientId: process.env.CLIENT_ID!, clientSecret: process.env.CLIENT_SECRET!,});
crowdinApp.cron.schedule('0 * * * *', async () => { const result = await crowdinApp.forEachCrowdinConnection(async ({ client, credentials }) => { const projects = await client.projectsGroupsApi.withFetchAll().listProjects(); await crowdinApp.saveMetadata({ id: 'project-cache', crowdinId: credentials.id, metadata: JSON.stringify(projects.data.map(({ data }) => data)), }); }); console.log(`Updated ${result.processed}/${result.total} installations; ${result.failed.length} failed`);});
app.listen(3000);The scheduler defaults to Node.js cron. For serverless hosting, provide a custom Cron implementation and invoke it from the hosting platform’s scheduled handler; see Cloudflare Workers deployment. The helper itself does not schedule work and can also be awaited from an admin route or webhook handler.
Tolerance and overlap
Section titled “Tolerance and overlap”crowdinApp.cron.schedule applies resilient defaults to every task, so a heartbeat that is late — because of a CPU stall, synchronous IO, or a sibling task holding the loop — runs the slot late instead of silently skipping it:
- Missed-execution tolerance. A heartbeat that is late by less than the tolerance still runs its slot. The default is derived from the expression (half the gap between slots, capped at 10 minutes), which sits well above a few-second stall yet below the next slot. Override it for all tasks with
cronOptions.missedExecutionTolerance(ms) or theCRON_MISSED_EXECUTION_TOLERANCE_MSenvironment variable; the cap on the derived value iscronOptions.maxMissedExecutionTolerance/CRON_MAX_MISSED_EXECUTION_TOLERANCE_MS. A tolerance is always clamped below the slot gap, since a heartbeat a full gap late is the next slot. - Overlap prevention. A new run is blocked while the previous run of the same task is still in flight (
noOverlap, on by default). Disable withcronOptions.noOverlap: false. Because a tolerated late run can land close to the next slot, keep overlap prevention on for tasks that must not run concurrently. - Structured logging. Every missed or blocked run is logged with the task name and how late the heartbeat was (
lateBy=…ms), so a lost run is never silent. A dropped run (heartbeat later than the tolerance) is reported through the error channel — youronErrorhandler, if set — so it stays visible to monitoring and is not gated on the logger being enabled; an overlap-blocked run is logged at info level.
const crowdinApp = addCrowdinEndpoints(app, { // … cronOptions: { missedExecutionTolerance: 5 * 60 * 1000, // run a slot up to 5 minutes late instead of dropping it noOverlap: true, },});These options apply only to the built-in node-cron scheduler and are ignored when a custom cron is supplied.
Options and result
Section titled “Options and result”| Option | Default | Behavior |
|---|---|---|
concurrency | CRON_ORG_CONCURRENCY, or 5 | Positive safe integer; installations run in batches of at most this size. |
checkSubscription | true | Skip installations whose subscription is expired. Set to false for work that must also run for expired subscriptions, such as cleanup. |
The callback receives { client, credentials }. credentials is the original storage snapshot identifying the installation, not a source of current API tokens. Use client for API calls. The callback runs once per stored installation; enumerate projects inside it when needed.
The result contains total, processed, skippedExpired, and failed ({ crowdinId, error }[]). processed counts successfully completed callbacks; each installation has exactly one outcome. Client preparation and callback errors are logged and collected without aborting other installations. Empty storage returns zero counts and an empty failed array.
Invalid explicit concurrency rejects with RangeError; a storage listing failure rejects the whole run. Unauthorized configuration throws immediately. Subscription checks retain the SDK’s existing behavior: handled subscription-service HTTP errors allow processing to continue.
Authentication and execution limits
Section titled “Authentication and execution limits”Background token preparation uses the existing crowdin_app, authorization_code, and crowdin_agent flows. crowdin_app_with_code requires a code from a request JWT and is not supported for background iteration: each stored installation is reported in failed, and its callback is not called.
Concurrency applies to one invocation only. Separate invocations or app replicas can overlap; this helper adds no retries or distributed locking. Design callbacks to tolerate repeated execution where needed. Avoid logging whole credentials or result errors without the application’s normal error sanitization.