New to Kestra?
Use blueprints to kickstart your first workflows.
Poll REST APIs incrementally on schedule with Kestra. Uses persistent KV store watermark and boundary offsets to prevent duplicates without writing scripts.
Poll external REST APIs incrementally without duplicating records across runs and retries. This blueprint demonstrates high-watermark cursor ingestion against the GitHub Commits REST API using Kestra's native HTTP and Key-Value (KV) store tasks.
Idempotency ensures that executing the flow multiple times against the same upstream state yields the exact same state without double-counting records:
now() minus initial_lookback) instead of failing.since query parameter is inclusive (committer.date >= since). Advancing the high-watermark to maximum_committer_date + 1 second guarantees the boundary commit is never fetched again on subsequent polls.advance_watermark so the cursor advances only when records have committed. If an API request or downstream task fails, the watermark remains unchanged so the next run retries the exact same window.read_watermark (io.kestra.plugin.core.kv.Get) retrieves the current high-watermark key (watermark_{owner}_{repository}) from the namespace KV store. The errorOnMissing: false attribute allows clean cold-start execution.compute_since (io.kestra.plugin.core.output.OutputValues) computes the query timestamp: the existing KV watermark if present, or now() - initial_lookback hours in ISO-8601 UTC format.fetch_commits (io.kestra.plugin.core.http.Request) sends a GET request to https://api.github.com/repos/{owner}/{repo}/commits with the since query filter, GitHub headers, timeout, and automatic 3-attempt retry.process_records (io.kestra.plugin.core.flow.If) evaluates the response array:check_page_saturation halts the execution with an io.kestra.plugin.core.execution.Fail error so the watermark is not advanced over unread older commits. Otherwise, log_commit_summary logs the count and min/max timestamps, log_sample_commits outputs the latest 10 commits (SHA, date, message headline), and advance_watermark (io.kestra.plugin.core.kv.Set) saves max_committer_date + 1s back to the KV store. Any downstream ingestion tasks belong immediately before advance_watermark.log_no_new_records logs that no new records arrived since the watermark, and the KV store is left completely untouched.The GitHub Commits API limits single-page responses to at most 100 commits (per_page=100) and returns commits in reverse-chronological order (newest first).
records_count < 100, the result contains all commits within the polling window, guaranteeing that no commits are pending on subsequent pages.records_count == 100, the single-page limit has been reached. Because Git history traverses newest-to-oldest, older commits remain on subsequent pages. Advancing the watermark to the newest timestamp would permanently skip those older commits. To protect data integrity, the flow explicitly halts with an io.kestra.plugin.core.execution.Fail error and leaves the watermark untouched.watermark_{owner}_{repository} in the Kestra UI to a newer timestamp, acknowledging that the unread commits in that backlog will not be ingested.The flow enforces concurrency: { limit: 1, behavior: QUEUE }. This serializes overlapping runs (such as a scheduled execution running while a manual run is active), preventing race conditions where two executions read the same watermark and produce duplicate downstream events.
since parameter filters on committer.date. If older commits are merged via fast-forward or rebase while preserving historical committer dates older than the current watermark, they will not be returned by since.Kestra's integrated KV store eliminates the need for external state databases (like Redis or Postgres) simply to store pagination cursors. Combining native HTTP retries, expression-based date manipulation, concurrency queueing, and conditional branching allows production-grade ingestion using purely declarative YAML.
api.github.com.Authorization header to fetch_commits:headers:
Authorization: "Bearer {{ secret('GITHUB_TOKEN') }}"
owner: kestra-io, repository: kestra, initial_lookback: 6).log_commit_summary reports the commits found within the last 6 hours, and advance_watermark saves the new high-watermark to the KV store. If the first run finds no commits, no watermark is created; increase initial_lookback and run again.No new commits found..., and leaves the watermark untouched.poll_schedule trigger to run automated polling every 15 minutes.To clear the state and re-ingest from scratch:
company.team namespace (or Namespaces > company.team > KV Store), locate the key watermark_kestra-io_kestra, and click Delete.initial_lookback window.