Skip to content

HTTP Jobs

An HTTP job calls a URL on a schedule. It is the default job_type.

Terminal window
curl -X POST https://<your-host>/api/jobs \
-H 'Content-Type: application/json' \
-d '{
"name": "nightly-sync",
"job_type": "http",
"schedule_type": "cron",
"cron_expression": "0 2 * * *",
"http_method": "POST",
"url": "https://example.com/api/sync",
"headers": {"X-Request-Source": "zilean"},
"body": {"mode": "incremental"},
"exec_timeout_ms": 60000,
"auth_type": "bearer",
"auth_config": {"token": "{{SYNC_TOKEN}}"}
}'

In order:

  1. Method — http_method, defaulting to GET.
  2. Query params — query_params, with {{variable}} substitution applied to both keys and values.
  3. URL — url with substitution applied, then query params merged in. A param overrides a same-named param already present in the raw URL.
  4. Body — body (raw JSON) as a string, with substitution applied, sent verbatim.
  5. Headers — headers, keys and values substituted.
  6. Content-Type — set to application/json only if there’s a body and you didn’t set Content-Type yourself.
  7. Auth — applied last, so it overrides a manually-set header of the same name.
auth_type auth_config Effect
none (default) ignored Nothing.
bearer {"token": "..."} Authorization: Bearer <token>. Errors if token is empty.
basic {"username": "...", "password": "..."} HTTP Basic. Errors if username is empty; an empty password is allowed.
api_key {"api_key": "...", "header_name": "..."} Sets header_name: <api_key>. header_name defaults to X-API-Key. Errors if api_key is empty.

Every field of auth_config gets {{variable}} substitution before use, so credentials can live in global variables rather than inline.

Success is strictly 200 ≤ status < 300. Everything else fails:

endpoint returned status 404: <first 500 chars of body>

Also failures: transport errors, and errors reading the response body. A response body larger than 4 MiB fails the attempt too (and is retried like any other failure) rather than being truncated, since a cut-off payload can’t be trusted for loop_response_path.

A configuration error is different. An unparseable URL, an unknown auth_type, or a missing required auth field cancels the job rather than retrying it — it can never succeed, so it doesn’t burn the retry budget.

A failed attempt keeps its real status code and duration, but response snippets and error text are redacted before they’re recorded, so a body that echoes back a credential reads REDACTED in call history, River UI, and failure notifications. Only the recorded copy is affected: the request itself sends the real values, and loop_response_path is evaluated against the real response, so looping is unaffected.

exec_timeout_ms bounds one attempt.

  • Default: 60 000 ms (60 s) if unset or 0.
  • Maximum: 1 800 000 ms (30 min). Higher is rejected at write time.

Above that sits River’s 35-minute safety net, which exists only to kill a job that somehow outlives its own timeout — not to enforce per-job limits.

With pool_count > 1, each parallel call receives a pool_idx query parameter (0 … pool_count − 1), which overwrites any pool_idx you set yourself.

At pool_count: 1 the parameter is not added at all, so a non-pooled job’s request is byte-identical to what it would have been before pooling existed.

See Loop, retry & pooling.

Any text field supports {{name}} placeholders — URL, query params, body, headers and every auth_config field. Whitespace inside the braces is tolerated ({{ name }}).

Names may contain letters, digits and underscores.

Variables are loaded fresh on every attempt, so changing a variable’s value takes effect on the next attempt with no job edit. Manage them under Settings → Global variables, or via /api/global-variables. A variable marked secret is write-only: after saving, the UI shows “Value hidden” and the API returns an empty value, so you can replace a secret but not read it back. Secret values are AES-256-GCM-encrypted at rest with SETTINGS_ENCRYPTION_KEY, masked in the audit log, and redacted from what Zilean records about a call: the recorded URL, the response snippet, the error text, and the failure notification all show REDACTED in place of the value. A job’s resolved auth credentials are redacted the same way, whether or not they came from a variable.

Set loop_enabled and point loop_response_path at a count of remaining work. The job re-calls itself on success while that number stays positive:

{
"loop_enabled": true,
"loop_response_path": "result.remaining",
"loop_delay_ms": 2000,
"loop_jitter_ms": 300,
"loop_max_iterations": 100
}

Your endpoint would return something like {"result": {"remaining": 42}}, and 0 when done. The path is gjson syntax, and only a strictly positive number continues the loop.

Terminal window
curl https://<your-host>/api/jobs/<id>/calls

Each entry carries the queue state, attempt number, timestamps, last error, pool index, loop iteration, and a last_call object with the method, URL, status code, duration and a 500-character response snippet.

See Monitoring jobs.