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.
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
asyncdefault export, noimport, noevalornew 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:
| Name | Value |
|---|---|
PROJECT_ID | The project's ID |
PROJECT_URL | The project's API base URL, for example https://api.orbitnest.io/api/project/my-app |
JOB_NAME | The job's name |
TRIGGER_TYPE | scheduled or manual |
ORBITNEST_ANON_KEY, ANON_KEY | The 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.
| Expression | Runs |
|---|---|
*/5 * * * * | Every 5 minutes |
0 * * * * | Every hour, on the hour |
0 9 * * 1-5 | 09: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@dailyare rejected with400; write0 0 * * *instead of@daily. - The minimum interval is 1 minute. An expression whose next two runs are less than 60 seconds apart is rejected.
timezoneis an IANA name such asAmerica/New_YorkorEurope/Berlin. It defaults toUTC.- The job's
nextRunAtandlastRunAtfields 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.
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 }
}'| Field | Rules |
|---|---|
name | 3–50 characters: letters, digits, - and _. Unique per project, ignoring case |
description | Optional, up to 500 characters |
sourceCode | Up to 1 MB |
schedule | Cron expression, minimum interval 1 minute. See Schedules |
timezone | IANA time zone. Default UTC |
executionConfig.timeout | Milliseconds: 10000, 30000 (default), 60000 or 120000 |
executionConfig.memory | Isolate memory limit in MB, 16–1024. Default 128 |
executionConfig.enableLogs | Default true. Set to false to skip writing runs to the project logs |
status | Update only: active or inactive |
| Method and path | Purpose |
|---|---|
POST /api/projects/:projectId/jobs | Create a job. It is scheduled immediately |
GET /api/projects/:projectId/jobs | List jobs |
GET /api/projects/:projectId/jobs/:name | Get one job, including source code |
PUT /api/projects/:projectId/jobs/:name | Update description, sourceCode, schedule, timezone, status or executionConfig |
DELETE /api/projects/:projectId/jobs/:name | Delete a job and stop its schedule |
POST /api/projects/:projectId/jobs/:name/trigger | Run the job now |
GET /api/projects/:projectId/jobs/:name/runs?limit=50 | Run 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 useorbitnest_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
statustoinactiveoractivethrough the API ororbitnest_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/:nameororbitnest_update_job. Changes take effect immediately and the job is rescheduled. - Delete: select Delete in the job's menu, call
DELETE .../jobs/:name, or useorbitnest_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:
| Field | Description |
|---|---|
triggerType | scheduled or manual |
status | running, success, error, timeout or skipped |
startedAt, completedAt, durationMs | Timing |
errorMessage | The thrown error or timeout message, if any |
executionId | Links 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
skippedand the messageSkipped: previous run still in progress. - A manual trigger while the job is running fails with
400andJob is already running. Wait for it to complete. - A run still marked
runningmore than 10 minutes after it started (for example, because the server restarted mid-run) is markederrorwith the messagerun timed out / process diedthe 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
| Limit | Value |
|---|---|
| Jobs per project | 15 |
| Minimum interval | 1 minute |
| Timeout | 10, 30, 60 or 120 seconds. Default 30 |
| Memory | 16–1024 MB, 128 MB by default |
| Source code | 1 MB |
| Concurrent runs per job | 1 |