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
Runningstatus. - The app service ID. Available from
GET /app-servicesor 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
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Lowercase alphanumerics and hyphens, max 63 characters |
schedule_cron | string | none | Five-field cron expression or descriptor (@daily, @hourly) |
timezone | string | UTC | IANA timezone name for the cron schedule |
enabled | bool | true | Whether the schedule fires automatically |
image_ref | string | app image | Override the OCI image for this job only |
command | string[] | app default | Exec-form argv override; no shell expansion |
env | object | none | Key-value pairs layered over the app's environment |
max_retries | int | 0 | Retry attempts on failure, 0 to 5 |
retry_backoff_seconds | int | 60 | Seconds between retries, 10 to 3600 |
max_runtime_seconds | int | 3600 | Hard wall-clock limit, 10 to 21600 (6 hours) |
concurrency_cap | int | 1 | Maximum 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
| Status | Meaning |
|---|---|
queued | Dispatched to the agent, not yet started |
running | Container is executing |
succeeded | Container exited with code 0 |
failed | Container exited with a non-zero code |
timed_out | Container hit max_runtime_seconds and was stopped |
skipped | Cron 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:
| Expression | Fires |
|---|---|
0 2 * * * | Daily at 02:00 in the job's timezone |
*/15 * * * * | Every 15 minutes |
0 9 * * 1 | Every Monday at 09:00 |
@daily | Equivalent to 0 0 * * * |
@hourly | Equivalent 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.