Skip to main content

Jobs

A job defines a container invocation that runs on your app service's VM. Each execution is called an invocation. Cron jobs fire automatically; one-shot jobs fire when you call the /run endpoint.

Prerequisites

  • A running app service. Jobs require the service to be in Running status.
  • The app service ID. Available from GET /app-services or the dashboard.

Create a job

curl -u admin:password -X POST \
https://api.foundrydb.com/app-services/{service-id}/jobs \
-H "Content-Type: application/json" \
-d '{
"name": "nightly-cleanup",
"schedule_cron": "0 2 * * *",
"timezone": "Europe/Stockholm",
"command": ["/app/bin/cleanup", "--dry-run=false"],
"env": {"BATCH_SIZE": "500"},
"max_runtime_seconds": 1800,
"max_retries": 2,
"retry_backoff_seconds": 60
}'
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"service_id": "a3f9e812-...",
"name": "nightly-cleanup",
"schedule_cron": "0 2 * * *",
"timezone": "Europe/Stockholm",
"enabled": true,
"command": ["/app/bin/cleanup", "--dry-run=false"],
"env": {"BATCH_SIZE": "500"},
"max_retries": 2,
"retry_backoff_seconds": 60,
"max_runtime_seconds": 1800,
"concurrency_cap": 1,
"overlap_policy": "skip",
"next_run_at": "2026-06-23T00:02:00Z",
"created_at": "2026-06-22T14:31:00Z",
"updated_at": "2026-06-22T14:31:00Z"
}

One-shot job (no schedule)

Omit schedule_cron to create a job that only runs when you explicitly trigger it:

curl -u admin:password -X POST \
https://api.foundrydb.com/app-services/{service-id}/jobs \
-H "Content-Type: application/json" \
-d '{
"name": "db-migrate",
"command": ["/app/bin/migrate", "--up"],
"max_runtime_seconds": 300
}'

Request fields

FieldTypeDefaultDescription
namestringrequiredLowercase alphanumerics and hyphens, max 63 characters
schedule_cronstringnoneFive-field cron expression or descriptor (@daily, @hourly)
timezonestringUTCIANA timezone name for the cron schedule
enabledbooltrueWhether the schedule fires automatically
image_refstringapp imageOverride the OCI image for this job only
commandstring[]app defaultExec-form argv override; no shell expansion
envobjectnoneKey-value pairs layered over the app's environment
max_retriesint0Retry attempts on failure, 0 to 5
retry_backoff_secondsint60Seconds between retries, 10 to 3600
max_runtime_secondsint3600Hard wall-clock limit, 10 to 21600 (6 hours)
concurrency_capint1Maximum simultaneous invocations, 1 to 5

List jobs

curl -u admin:password \
https://api.foundrydb.com/app-services/{service-id}/jobs
{
"jobs": [
{
"id": "550e8400-...",
"name": "nightly-cleanup",
"schedule_cron": "0 2 * * *",
"enabled": true,
"next_run_at": "2026-06-23T00:02:00Z",
"last_run_at": "2026-06-22T00:02:05Z"
}
]
}

Get a job

curl -u admin:password \
https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id}

Update a job

PATCH merges changes; unspecified fields are unchanged.

# Disable the schedule without deleting the job
curl -u admin:password -X PATCH \
https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id} \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
# Replace the schedule
curl -u admin:password -X PATCH \
https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id} \
-H "Content-Type: application/json" \
-d '{"schedule_cron": "0 3 * * *", "timezone": "Europe/Helsinki"}'
# Remove the schedule entirely (make the job on-demand only)
curl -u admin:password -X PATCH \
https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id} \
-H "Content-Type: application/json" \
-d '{"clear_schedule": true}'

clear_schedule and schedule_cron are mutually exclusive. The same logic applies to image_ref and clear_image_ref.

Run a job on demand

Triggers an immediate invocation regardless of the schedule. The service must be Running.

curl -u admin:password -X POST \
https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id}/run

Returns 202 Accepted with the new invocation:

{
"id": "inv-uuid",
"job_id": "550e8400-...",
"service_id": "a3f9e812-...",
"status": "queued",
"attempt": 1,
"triggered_by": "manual",
"triggered_by_user_id": "user-uuid",
"queued_at": "2026-06-22T14:45:00Z"
}

If the job's concurrency_cap is already reached, the API returns 409 Conflict. Wait for a running invocation to finish before retrying.

Invocations

Each fire of a job, whether from the schedule or a manual trigger, creates an invocation row.

List invocations

curl -u admin:password \
"https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id}/invocations?limit=20&offset=0"

Default limit is 50, maximum is 200. Results are ordered newest-first.

{
"invocations": [
{
"id": "inv-uuid",
"job_id": "550e8400-...",
"status": "succeeded",
"attempt": 1,
"triggered_by": "schedule",
"scheduled_for": "2026-06-22T00:02:00Z",
"queued_at": "2026-06-22T00:02:00Z",
"started_at": "2026-06-22T00:02:01Z",
"finished_at": "2026-06-22T00:02:47Z",
"duration_ms": 46210,
"exit_code": 0
}
]
}

Invocation statuses

StatusMeaning
queuedDispatched to the agent, not yet started
runningContainer is executing
succeededContainer exited with code 0
failedContainer exited with a non-zero code
timed_outContainer hit max_runtime_seconds and was stopped
skippedCron fired while concurrency cap was full; no container ran

Get one invocation

curl -u admin:password \
https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id}/invocations/{invocation-id}

The response includes log_tail (last 40 lines) once the invocation is terminal.

Logs

Logs are fetched asynchronously: first request the fetch, then poll for the result.

Request log fetch

curl -u admin:password -X POST \
"https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id}/invocations/{invocation-id}/logs?lines=200"

Returns 202 Accepted with a task ID:

{"task_id": "task-uuid"}

lines is optional, default 200, maximum 1000.

Poll for logs

curl -u admin:password \
"https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id}/invocations/{invocation-id}/logs?task_id=task-uuid"

Returns the log output once the fetch task completes. An invocation that never started (status skipped or queued with no unit name) returns 400.

Delete a job

curl -u admin:password -X DELETE \
https://api.foundrydb.com/app-services/{service-id}/jobs/{job-id}

Returns 204 No Content. Invocation history is deleted alongside the job definition. Running containers finish on the VM but their results are no longer accessible through the API.

Cron expressions

The parser accepts five-field expressions (minute, hour, day-of-month, month, day-of-week) and descriptors:

ExpressionFires
0 2 * * *Daily at 02:00 in the job's timezone
*/15 * * * *Every 15 minutes
0 9 * * 1Every Monday at 09:00
@dailyEquivalent to 0 0 * * *
@hourlyEquivalent to 0 * * * *

The minimum resolution is one minute. The timezone field takes any IANA zone name (Europe/Stockholm, America/New_York, UTC). When omitted, the schedule fires in UTC.