Platform

Background jobs

Run TypeScript on a cron schedule in the same sandboxed runtime as edge functions, trigger runs by hand, pause jobs and review their run history.

A background job is a named piece of code plus a cron schedule. It runs in the same V8 isolate runtime as edge functions, with the same globals, limits and project environment variables, but the scheduler starts it instead of an HTTP request.

Background jobs are available on every plan.

Write a job handler

A job's default export is an async function that receives a single context argument. There is no request and no user.

nightly-digest.ts
type JobCtx = {
  project: { id: string; slug: string; url: string };
  executionId: string;
  env: Record<string, string> & { get(name: string): string | null };
  secrets: { get(name: string): string | null };
};

export default async function handler(ctx: JobCtx): Promise<void> {
  console.log(`digest run (${Deno.env.get('TRIGGER_TYPE')}) for ${ctx.project.slug}`);

  // Jobs often hand the real work to an edge function.
  const res = await fetch(
    `https://api.orbitnest.io/api/projects/${ctx.project.slug}/functions/v1/send-digest`,
    {
      method: 'POST',
      headers: { apikey: ctx.secrets.get('ORBITNEST_ANON_KEY') ?? '' },
    },
  );

  if (!res.ok) {
    throw new Error(`send-digest failed with ${res.status}`);
  }
}
  • Throw to mark the run as failed. The error message is saved with the run.
  • The return value is ignored.
  • Code rules are the same as for functions: a single file with an async default export, no import, no eval or new Function. See Write a handler and Runtime globals.

Jobs are called as handler(ctx), not handler(req, ctx) like functions. Studio's new-job template uses the handler(ctx) signature.

Environment

Jobs read the project's environment variables like functions do. Each run also gets:

NameValue
PROJECT_IDThe project's ID
PROJECT_URLThe project's API base URL, for example https://api.orbitnest.io/api/project/my-app
JOB_NAMEThe job's name
TRIGGER_TYPEscheduled or manual
ORBITNEST_ANON_KEY, ANON_KEYThe project's anon (public) API key

The service role key is not injected. Add it as a project environment variable if a job needs to bypass row-level security.

Schedules

Schedules use standard five-field cron syntax: minute hour day-of-month month day-of-week.

ExpressionRuns
*/5 * * * *Every 5 minutes
0 * * * *Every hour, on the hour
0 9 * * 1-509:00 on weekdays
0 0 1 * *Midnight on the 1st of every month
30 */2 * * *Every 2 hours at minute 30
  • Expressions are checked against the scheduler's own parser. Numbers, *, ranges (1-5), lists (1,15), steps (*/5) and month and weekday names work. L, ? and shortcuts such as @daily are rejected with 400; write 0 0 * * * instead of @daily.
  • The minimum interval is 1 minute. An expression whose next two runs are less than 60 seconds apart is rejected.
  • timezone is an IANA name such as America/New_York or Europe/Berlin. It defaults to UTC.
  • The job's nextRunAt and lastRunAt fields show when it will run next and when it last ran.

Create a job

In Studio

Open the project, select Functions, then Background Jobs, and create a job:

  • Job name and an optional description.
  • Schedule: pick a preset (every minute, every 5 or 15 minutes, every hour, every 6 hours, daily at midnight, weekly on Sunday) or enter a custom cron expression.
  • Timezone: UTC or one of the listed time zones (New York, Chicago, Denver, Los Angeles, London, Berlin, Tokyo, Shanghai, Sydney). Use the API or MCP for any other IANA zone.
  • Job code and a Timeout of 10 s, 30 s, 60 s or 2 minutes.

With the MCP server

orbitnest_create_job takes name, sourceCode, schedule, and optionally description, timezone and timeout (10s, 30s, 60s or 120s). Besides cron expressions, schedule accepts the presets every-minute, every-5-minutes, every-15-minutes, every-hour, every-6-hours, daily (0 0 * * *) and weekly (0 0 * * 0).

With the REST API

The jobs API takes a Studio admin access token, not a project API key, and identifies the project by ID.

Terminal
curl -X POST "https://api.orbitnest.io/api/projects/$PROJECT_ID/jobs" \
  -H "Authorization: Bearer $ADMIN_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "nightly-digest",
    "schedule": "0 2 * * *",
    "timezone": "America/New_York",
    "sourceCode": "export default async function handler(ctx) { console.log(\"tick\"); }",
    "executionConfig": { "timeout": 60000 }
  }'
FieldRules
name3–50 characters: letters, digits, - and _. Unique per project, ignoring case
descriptionOptional, up to 500 characters
sourceCodeUp to 1 MB
scheduleCron expression, minimum interval 1 minute. See Schedules
timezoneIANA time zone. Default UTC
executionConfig.timeoutMilliseconds: 10000, 30000 (default), 60000 or 120000
executionConfig.memoryIsolate memory limit in MB, 16–1024. Default 128
executionConfig.enableLogsDefault true. Set to false to skip writing runs to the project logs
statusUpdate only: active or inactive
Method and pathPurpose
POST /api/projects/:projectId/jobsCreate a job. It is scheduled immediately
GET /api/projects/:projectId/jobsList jobs
GET /api/projects/:projectId/jobs/:nameGet one job, including source code
PUT /api/projects/:projectId/jobs/:nameUpdate description, sourceCode, schedule, timezone, status or executionConfig
DELETE /api/projects/:projectId/jobs/:nameDelete a job and stop its schedule
POST /api/projects/:projectId/jobs/:name/triggerRun the job now
GET /api/projects/:projectId/jobs/:name/runs?limit=50Run history, newest first

Note

The SDKs' jobs clients are deprecated. The jobs API needs a Studio admin access token, not a project API key, so in the JavaScript and Node.js SDKs every jobs method returns an ADMIN_ONLY error without making a request, and in the Flutter SDK every call fails with 401. Manage jobs from Studio, the MCP server or the REST API with an admin access token.

Run, pause and edit jobs

  • Run now: select Run Now in the job's menu or details panel, call POST .../jobs/:name/trigger, or use orbitnest_trigger_job. The request waits for the run to finish and returns the run record. Manual triggers are rate-limited.
  • Pause and resume: select Pause Job or Activate Job in the job's menu, or update status to inactive or active through the API or orbitnest_update_job. Paused jobs keep their history and can still be run manually.
  • Edit: the Code tab in Studio is read-only. Change code, schedule, time zone or timeout with PUT .../jobs/:name or orbitnest_update_job. Changes take effect immediately and the job is rescheduled.
  • Delete: select Delete in the job's menu, call DELETE .../jobs/:name, or use orbitnest_delete_job.

executionConfig is replaced as a whole on update, so send every field you want to keep.

Run history

Every run is recorded with:

FieldDescription
triggerTypescheduled or manual
statusrunning, success, error, timeout or skipped
startedAt, completedAt, durationMsTiming
errorMessageThe thrown error or timeout message, if any
executionIdLinks the run to its console output in the project logs
  • Studio: open the job and select the Runs tab to see the 20 most recent runs.
  • REST: GET /api/projects/:projectId/jobs/:name/runs?limit=50 (1–200, default 50).
  • MCP: orbitnest_get_job_runs.

Console output and the run's status are also written to the project logs as edge_function events, unless enableLogs is false. See Logs & observability.

Overlapping runs

A job runs at most once at a time:

  • If a scheduled run is due while the previous run is still going, the new run is not started. It is recorded with status skipped and the message Skipped: previous run still in progress.
  • A manual trigger while the job is running fails with 400 and Job is already running. Wait for it to complete.
  • A run still marked running more than 10 minutes after it started (for example, because the server restarted mid-run) is marked error with the message run timed out / process died the next time the job is due or triggered. A dead run therefore blocks the job for at most about 10 minutes.
  • The check and the start of a new run happen under a lock, so two API servers can't start the same job at once.

Limits

LimitValue
Jobs per project15
Minimum interval1 minute
Timeout10, 30, 60 or 120 seconds. Default 30
Memory16–1024 MB, 128 MB by default
Source code1 MB
Concurrent runs per job1