Build
Background jobs
Run a function in the background for up to 15 minutes, with retries and delays. Queue it with one request and get the result later.
When to use a job
A function answers one request and has 30 seconds. A job runs the same function in the background, for up to 15 minutes, and Berth retries it if it fails. Use jobs for anything a user should not wait for: sending a batch of emails, resizing uploads, calling a slow AI model, syncing with another service, nightly cleanups.
| Function call | Job | |
|---|---|---|
| Who waits | the caller, for the response | nobody: you get a job id at once |
| Time limit | 30 seconds | 2 minutes on Free, 15 minutes on Pro and above |
| If it fails | the caller sees the error | retried with backoff, up to 10 attempts |
| Start later | no | yes, with delay_seconds or run_at |
Write the function
A job is an ordinary function. It receives a POST with your payload as the JSON body, and these headers: Berth-Key-Type: job, Berth-Job-Id, and Berth-Job-Attempt (1 for the first try). Answer with any 2xx status when the work is done. Any other status, a crash, or running out of time counts as a failure.
// functions/send-digest.ts
export default async (req: Request) => {
const { userId } = await req.json();
// ...build and send the email...
return Response.json({ sent: true }); // stored as the job's result
};berth functions deploy --app "$APP" send-digest functions/send-digest.tsQueue a job
Queue jobs from your server or from another function, with the secret key (functions have it as BERTH_SECRET_KEY).
curl -sS -X POST https://api.atberth.com/v1/apps/$APP/jobs \
-H "Authorization: Bearer $SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{"function":"send-digest","payload":{"userId":"42"},"max_attempts":3,"timeout_seconds":300,
"idempotency_key":"digest-42-2026-10-28"}'| Field | Default | Notes |
|---|---|---|
function | required, one of the app's functions | |
payload | null | any JSON, up to 256 KB |
delay_seconds | 0 | start later, up to 30 days |
run_at | or an exact ISO time | |
max_attempts | 3 | 1 to 10 |
timeout_seconds | 300 (120 on Free) | up to 900 on Pro and above, 120 on Free |
idempotency_key | queueing the same key again returns the first job instead of a duplicate |
Retries and limits
A failed attempt is queued again after 30 seconds, then 2 minutes, then 8 minutes, and so on (each wait four times the last), until max_attempts. The job keeps the last status code, result and error, so you can see why it failed. Jobs run two at a time across Berth and one at a time per app, so a burst of jobs waits its turn instead of slowing your app's requests. An app can have 10,000 jobs waiting. Finished jobs are kept for 30 days.
Check on jobs
curl -sS "https://api.atberth.com/v1/apps/$APP/jobs?status=failed" -H "Authorization: Bearer $SECRET_KEY"
curl -sS https://api.atberth.com/v1/apps/$APP/jobs/$JOB_ID -H "Authorization: Bearer $SECRET_KEY"
curl -sS -X DELETE https://api.atberth.com/v1/apps/$APP/jobs/$JOB_ID -H "Authorization: Bearer $SECRET_KEY" # cancel if not startedA job's status is queued, running, succeeded, failed or cancelled. The list also returns counts per status. Each run also appears in the function's logs.
What jobs are not
Jobs are not containers. They run your Deno function, with the same network rules and memory limit. A worker that must run all day, a service that holds open connections, or a program in another language does not fit on Berth today.