Triggers and inputs
Start runs on a schedule, from a webhook call or after another pipeline, and give a run its inputs.
Last updated
On this page
Description and operation
General
A trigger is a way to start a run. You can always start a run with the SDK, the CLI or the API. The triggers key of a definition adds three more ways. A run from a trigger uses the newest version at the time that it starts.
| Start | Key | The run has event | Inputs |
|---|---|---|---|
| A call | none | manual | The values of the call, else the defaults |
| A schedule | triggers.schedule | schedule | The defaults |
| A webhook call | triggers.webhook | webhook | The defaults, plus the data of the call in webhook.body |
| The end of another pipeline | triggers.after | pipeline | The defaults |
To turn a trigger off, remove its key and save the definition as a new version.
To do these tasks with an AI agent, see Agentic workflows – General.
Inputs
inputs declares the values that a run takes. Every value is text. A job reads an input as ${{ inputs.NAME }}.
| Key | Meaning |
|---|---|
default | The value when the start gives none. |
options | The only values that a start can give. The default must be one of them. |
required | A start that gives no value, with no default, is refused. |
An input name starts with a letter or an underscore, and has at most 40 letters, digits, underscores or hyphens. An input with options needs a default or required, because an empty value is not one of the options.
A start can give only declared inputs. A trigger gives no values, so every trigger run uses the defaults. For that reason, a definition with a trigger cannot have a required input with no default.
Schedule
triggers.schedule is a list of cron lines in UTC, with five fields: minute hour day-of-month month weekday. Each line that matches a minute starts one run in that minute. A minute in which the platform does not run is skipped, not run later.
When a line restricts both day-of-month and weekday, a minute matches if either field matches. Month names, weekday names and @daily are not accepted. Weekday 0 and 7 are Sunday. The full syntax is in Cron.
| Line | Starts a run |
|---|---|
0 2 * * * | At 02:00 UTC every day |
*/15 * * * * | Every 15 minutes |
30 6 * * 1-5 | At 06:30 UTC from Monday to Friday |
0 0 1 * * | At 00:00 UTC on the first day of each month |
Webhook
"webhook": true gives the pipeline a secret URL. A POST to the URL starts a run, with no token. The answer of the save that turns the webhook on shows the URL once. No later answer shows it, so store it as a secret of the calling system. A rotate makes a new URL and stops the old one at once. "webhook": false deletes the secret.
| Item | Rule |
|---|---|
| Body | Optional. A JSON body of at most 256 KiB is available as webhook.body. Any other body starts a run with no data. |
| Rate | 30 calls in one minute (UTC) for each pipeline. The next call answers 429. Each api replica counts its own calls, so the total can be higher. |
| Answer | 202 with the id of the run. |
| Wrong URL | 404 with no body, the same for a wrong secret, a wrong pipeline and a webhook that is off. A body over 256 KiB answers 413 before the URL is checked. |
Anyone with the URL can start a run, and that run gets the secrets of the pipeline. Keep the URL secret, and rotate it when it is lost or shared.
Read webhook data safely
The caller of a webhook controls the text of webhook.body. A job can read it only in env and in if. It cannot read it in run, cwd, image or packages. Put a value in a variable, then use the variable in quotes in the command.
| Definition | Result |
|---|---|
"env": {"PAGE": "${{ webhook.body.page }}"} and "run": "./reindex.sh \"$PAGE\"" | Accepted. The shell reads the value as data. |
"run": "./reindex.sh ${{ webhook.body.page }}" | Refused at save. The text of the caller would become part of the command. |
"run": "./reindex.sh ${{ env.PAGE }}", where PAGE holds webhook.body data | Refused at save, for the same reason. |
A field that is not in the body reads as the empty text. A field that is a number, an object or a list reads as its JSON text. Use dots for nested fields, for example webhook.body.repo.name.
After another pipeline
triggers.after starts a run when a run of another pipeline of the same owner ends. With result (success, failure or cancelled), only that end state starts a run. Without result, any end starts a run.
A pipeline cannot follow itself, and the pipeline that it follows must exist when you save. A chain of after triggers stops at a depth of 5. The finished run lists in triggered each run that it started, and the error of each start that was refused.
Tasks
Give a run its inputs
Reason for the task: Make one pipeline do different work for each run, for example on another data set.
Job set-up
- Access: you. For a team, an admin to change the definition, and an active member to start the run.
- References: Inputs, SDK
pipelines.run.
Procedure
Add the inputs to the definition, and read them in
env.{ "name": "refresh", "inputs": { "dataset": { "options": ["orders", "customers"], "default": "orders" }, "day": { "required": true } }, "jobs": { "load": { "steps": [ { "name": "load", "run": "echo \"loading $DATASET for $DAY\"", "env": { "DATASET": "${{ inputs.dataset }}", "DAY": "${{ inputs.day }}" } } ] } }}Create the pipeline from the file. See Create a pipeline.
Start a run with a value for each input.
run = kl.pipelines.run(owner="acme", pipeline="refresh", inputs={"dataset": "customers", "day": "2026-10-21"})print(run.inputs)const run = await kl.pipelines.run({ owner: "acme", pipeline: "refresh", inputs: { dataset: "customers", day: "2026-10-21" } });console.log(run.doc.inputs);run, err := kl.Pipelines.Run(kloudlite.RunPipeline{ Owner: "acme", Pipeline: "refresh", Inputs: map[string]string{"dataset": "customers", "day": "2026-10-21"},})if err != nil { log.Fatal(err)}fmt.Println(run.Inputs)run = kl.pipelines.run(owner: "acme", pipeline: "refresh", inputs: { "dataset" => "customers", "day" => "2026-10-21" })p run.inputsvar run = kl.pipelines().run(new Kloudlite.RunPipeline().owner("acme").pipeline("refresh") .input("dataset", "customers").input("day", "2026-10-21"));System.out.println(run.json());kloudlite pipelines run acme/refresh --input dataset=customers --input day=2026-10-21
Close-out
- Make sure that
inputsof the run shows both values.
Run a pipeline on a schedule
Reason for the task: Do recurring work, such as a nightly data refresh, with no person to start it.
Job set-up
- Access: you. For a team, an admin.
- Conditions: every input of the pipeline has a default, or is not required.
- References: Schedule, Cron.
Procedure
Add
triggers.scheduleto the definition. This line starts a run at 02:00 UTC every day.{ "name": "nightly-report", "triggers": { "schedule": ["0 2 * * *"] }, "jobs": { "fetch": { "packages": ["curl"], "steps": [{ "name": "download", "run": "curl -fsSL https://api.github.com/repos/nixos/nixpkgs -o data.json" }] } }}Send the definition as the next version. See Save a new version.
Close-out
- Read the pipeline. Make sure that
triggers.schedulelists the line. - Wait until after 02:00 UTC, then show the runs of the pipeline. Make sure that the newest run has
eventschedule.
Start a run from a webhook
Reason for the task: Let another system start a run with one HTTP call, for example when its content changes.
Job set-up
- Access: you. For a team, an admin.
- Conditions: every input of the pipeline has a default, or is not required.
- References: Webhook,
POST /v1/webhooks/pipelines/{owner}/{name}/{secret}.
Procedure
Set
triggers.webhooktotrue, and read the data of the call inenv.{ "name": "reindex", "triggers": { "webhook": true }, "jobs": { "index": { "if": "webhook.body.page != ''", "steps": [ { "name": "reindex", "run": "echo \"reindexing page $PAGE\"", "env": { "PAGE": "${{ webhook.body.page }}" } } ] } }}Create the pipeline, or send the definition as a new version. Read
webhookUrlin the answer.with open("reindex.json") as f: doc = kl.pipelines.create(owner="acme", definition=json.load(f))print(doc["webhookUrl"])const doc = await kl.pipelines.create({ owner: "acme", definition: JSON.parse(readFileSync("reindex.json", "utf8")) });console.log(doc.webhookUrl);raw, err := os.ReadFile("reindex.json")if err != nil { log.Fatal(err)}var d kloudlite.PipelineDefinitionif err := json.Unmarshal(raw, &d); err != nil { log.Fatal(err)}doc, err := kl.Pipelines.Create("acme", d)if err != nil { log.Fatal(err)}fmt.Println(doc.WebhookURL)doc = kl.pipelines.create(owner: "acme", definition: JSON.parse(File.read("reindex.json")))puts doc["webhookUrl"]System.out.println(kl.request(new Kloudlite.RawRequest().method("POST").path("/v1/pipelines/acme") .body(Files.readString(Path.of("reindex.json")))));kloudlite pipelines create --owner acme --file reindex.jsonKeep the URL as a secret of the calling system.
Send a call to the URL from that system.
curl -fsS -X POST -H 'Content-Type: application/json' -d '{"page": "pricing"}' "$WEBHOOK_URL"
Close-out
- Make sure that the call answered
202with theidof a run. - Read the log of the job
index. Make sure that it showsreindexing page pricing.
Make a new webhook URL
Reason for the task: Stop an old webhook URL that is lost or shared, and get a new one.
Job set-up
- Access: you. For a team, an admin.
- Conditions: the newest version sets
triggers.webhooktotrue. - References:
POST /v1/pipelines/{owner}/{name}/webhook/rotate, SDKpipelines.rotate_webhook.
The old URL stops at once. Calls from systems that still use it answer 404.
Procedure
Make a new webhook URL. The answer shows the new URL once.
print(kl.pipelines.rotate_webhook(owner="acme", name="reindex")["webhookUrl"])console.log((await kl.pipelines.rotateWebhook({ owner: "acme", name: "reindex" })).webhookUrl);url, err := kl.Pipelines.RotateWebhook("acme", "reindex")if err != nil { log.Fatal(err)}fmt.Println(url)puts kl.pipelines.rotate_webhook(owner: "acme", name: "reindex")["webhookUrl"]System.out.println(kl.pipelines().rotateWebhook("acme", "reindex"));kloudlite pipelines rotate-webhook acme/reindexReplace the URL in each system that calls it.
Close-out
- Send a call to the new URL. Make sure that it answers
202.
Start a pipeline after another pipeline
Reason for the task: Run follow-up work when earlier work succeeds, for example a release after tests.
Job set-up
- Access: you. For a team, an admin.
- Conditions: the pipeline
site-checkexists for the same owner. See Run jobs in parallel. - References: After another pipeline.
Procedure
Add
triggers.afterto the definition of the follow-up pipeline.{ "name": "publish-status", "triggers": { "after": { "pipeline": "site-check", "result": "success" } }, "jobs": { "publish": { "steps": [{ "name": "publish", "run": "echo \"all sites are up\"" }] } }}Create the pipeline from the file. See Create a pipeline.
Start a run of
site-checkand wait for it to end. See Start a run and follow it.
Close-out
- Read the finished run of
site-check. Make sure thattriggerednamespublish-statusand a run. - Show the runs of
publish-status. Make sure that the newest run haseventpipeline.
Troubleshooting
| Symptom | Possible cause | Corrective action |
|---|---|---|
422 with `inputs.day`: required with no default, so a schedule, webhook or after trigger could never start this pipeline | A trigger cannot give a value for a required input. | Give the input a default, or remove required. |
422 with unknown input `date` (declared: dataset, day) | The start names an input that the definition does not declare. | Use a declared name. |
422 with input `day` is required | The start gives no value for a required input with no default. | Give the value at the start. |
422 with input `dataset`: `users` is not one of orders, customers | The value is not in options. | Give a value from options. |
422 with `triggers.schedule`: `0 2 * *`: a schedule has five fields (minute hour day month weekday), not 4 | The cron line is not valid. | Correct the line. See Cron. |
422 with `run`: `webhook.body.*` is readable only in `env` and `if`; set it in `env` and read it in the script as "$NAME" | A command reads the webhook data. | Move the value to env. See Read webhook data safely. |
422 with `run`: `env.PAGE` carries the webhook body; read it in the script as "$PAGE", never as `${{ env.PAGE }}` | A command reads a variable that holds webhook data as an expression. | Use "$PAGE" in the command. |
422 with `triggers.after`: a pipeline cannot follow itself | The after trigger names its own pipeline. | Name another pipeline. |
422 with `triggers.after`: no pipeline site-check | The pipeline that after names does not exist. | Create it first, or correct the name. |
triggered shows the after chain is deeper than 5 | Six or more pipelines start each other in a chain. | Make the chain shorter. |
A webhook call answers 404 with no body | The URL is wrong or old, the pipeline is gone, or the webhook is off. | Check triggers.webhook. Rotate the URL if it is lost. |
A webhook call answers 413 | The body is larger than 256 KiB. | Send a smaller body, for example an id that the job reads. |
A webhook call answers 429 | The pipeline had 30 calls in this minute. | Wait for the next minute, or send fewer calls. |
409 with the webhook is off; turn it on in a new version | A rotate of a pipeline whose newest version has no webhook. | Set triggers.webhook to true and save a new version. |
| No run starts at the scheduled time | The newest version has no such line, or the platform did not run in that minute. | Read the pipeline and check triggers.schedule. |