httpPolling
GET an HTTP endpoint on a CRON schedule, with pagination and state.
The httpPolling input GETs a JSON endpoint, extracts an array of
records from the response, and emits each batch into the filter chain.
With a schedule, it runs on CRON. Without one, it runs once.
Minimal example
input:
type: httpPolling
schedule: "*/15 * * * *"
endpoint: https://source.example.com/api/orders
dataField: ordersOptions
| property | type | default | description |
|---|---|---|---|
typerequired | "httpPolling" | — | Module type discriminator. Must be `httpPolling` for this module. |
id | string | — | Unique identifier within the pipeline. |
name | string | — | Human-readable name. |
description | string | — | — |
enabled | boolean | true | Whether module is active. |
tags | array<string> | — | — |
onError | string | "fail" | Default error action. Case-insensitive; normalized to lowercase by the runtime. |
endpointrequired | string | — | HTTP endpoint URL. Templates are accepted; the final URL is validated at runtime after template resolution. |
method | object | — | — |
headers | map<string, string> | — | — |
queryParams | map<string, string> | — | Query parameters. |
body | string | — | Inline request body. Templates ({{record.field}}) are evaluated at runtime. The body is sent regardless of the HTTP method. |
bodyTemplateFile | string | — | Path to an external template file used as the request body. Mutually exclusive with `body` is not enforced; if both are set, the inline body wins. |
| union (4 variants) | — | — | |
timeoutMs | integer | — | Request timeout in milliseconds. When omitted, each module applies its own runtime default. |
schedule | string | — | Optional CRON expression for polling. Validated at runtime. |
| object | — | — | |
| object | — | — | |
dataField | string | — | JSON field path containing the array of records to extract from the response. |
| object | — | — |
Query parameters
queryParams adds static parameters to every request, without spelling them
into the endpoint:
input:
type: httpPolling
endpoint: https://api.example.com/v1/orders
queryParams:
status: open
limit: "50"Values are percent-encoded, so a parameter carrying & or a space cannot break
out and invent another parameter.
A parameter given here replaces one already written in the endpoint's query string, which is what lets the URL carry a default that the module overrides. Parameters derived from state persistence are computed for the run at hand, so they win over both.
They apply to every request the input makes, including each page of a paginated fetch.
Pagination
httpPolling supports three pagination styles. Each requires a
different combination of fields.
Page-based
pagination:
type: page
param: page # query param name
limitParam: per_page
limit: 100
totalPagesField: meta.total_pagesOffset-based
pagination:
type: offset
param: offset
limitParam: limit
limit: 250
totalField: meta.totalCursor-based
pagination:
type: cursor
param: cursor
limitParam: per_page
limit: 100
nextCursorField: meta.next_cursorThe runtime keeps requesting pages until the source signals there are no more.
limit only reaches the source when limitParam names the query
parameter to carry it. limit: 100 on its own is silently ignored —
always set both, for all three styles.
Reading nested fields
dataField, nextCursorField, totalPagesField and totalField all
accept a dot-separated path, resolved segment by segment from the
response root — meta.next_cursor reads next_cursor inside the
meta object. There is no escaping, so a response key that literally
contains a dot can't be addressed.
A nextCursorField may point at a JSON string or a JSON number; a
number is used as-is. Anything else — a boolean, an object, null —
reads as "no cursor" and ends the pagination, so don't point the field
at a flag like has_more.
State persistence
To resume from where you left off between runs, configure
statePersistence. The state file is written after every successful
batch and read on startup.
statePersistence:
timestamp:
enabled: true
queryParam: updated_after
id:
enabled: true
field: id
queryParam: after_id
storagePath: ./.cannectors-statequeryParam is the shortcut for the common case. When the source filters
incrementally by another route — an OData $filter, a path segment, an
If-Modified-Since header, a POST search body — read the state directly:
{{ state.lastTimestamp }} and {{ state.lastId }} are available in the
endpoint, in headers, in queryParams values and in the body.
endpoint: https://source.example.com/api/orders?$filter=LastModifiedDate gt {{ state.lastTimestamp }}
headers:
X-Since: "{{ state.lastTimestamp }}"If-Modified-Since is the one header to be careful with: RFC 9110 requires an
HTTP-date there (Sat, 15 Mar 2026 08:30:00 GMT) and recipients must ignore a
malformed value, so sending the RFC3339 watermark into it silently does nothing.
See State persistence for the full mental model and per-environment storage recommendations, and reading the state yourself for the variables and their first-run values.
Authentication
authentication accepts the standard four schemes — api-key,
bearer, basic, oauth2. See
Authentication for examples.
Examples
name: http-polling-basic-to-http-batch
version: 1.0.0
description: Poll a JSON array from an HTTP API and send it as one batch request.
tags:
- http-polling
- http-output
input:
type: httpPolling
schedule: "*/15 * * * *"
endpoint: https://source.example.com/api/orders
headers:
Accept: application/json
dataField: orders
filters: []
output:
type: httpRequest
endpoint: https://destination.example.com/api/orders/import
method: POST
requestMode: batch
headers:
Content-Type: application/json
success:
statusCodes:
- 200
- 201
- 202name: http-polling-page-pagination
version: 1.0.0
description: Poll page-number pagination and normalize fields before output.
tags:
- http-polling
- pagination
input:
type: httpPolling
schedule: "0 */1 * * *"
endpoint: https://source.example.com/api/customers
dataField: data
pagination:
type: page
param: page
limitParam: per_page
limit: 100
totalPagesField: total_pages
filters:
- type: mapping
mappings:
- source: id
target: customer.id
transforms:
- op: toString
- source: email
target: customer.email
transforms:
- op: trim
- op: lowercase
- source: name
target: customer.name
onMissing: useDefault
defaultValue: Unknown
output:
type: httpRequest
endpoint: https://destination.example.com/api/customers
method: POST
requestMode: batchname: http-polling-offset-pagination-state
version: 1.0.0
description: Poll offset pagination with timestamp and ID state persistence.
tags:
- http-polling
- state
input:
type: httpPolling
schedule: "*/10 * * * *"
endpoint: https://source.example.com/api/events
dataField: events
pagination:
type: offset
param: offset
limitParam: limit
limit: 250
totalField: total
statePersistence:
timestamp:
enabled: true
queryParam: updated_after
id:
enabled: true
field: event.id
queryParam: after_id
storagePath: ./.cannectors-state
filters:
- type: set
target: metadata.source
value: events-api
- type: remove
target:
- debug
- internal.notes
output:
type: httpRequest
endpoint: https://destination.example.com/api/events
method: POST
requestMode: batchname: http-polling-cursor-oauth2
version: 1.0.0
description: Poll cursor pagination with OAuth2 client credentials.
tags:
- http-polling
- oauth2
input:
type: httpPolling
schedule: "0 */6 * * *"
endpoint: https://source.example.com/api/invoices
dataField: items
authentication:
type: oauth2
credentials:
tokenUrl: https://source.example.com/oauth/token
clientId: ${SOURCE_CLIENT_ID}
clientSecret: ${SOURCE_CLIENT_SECRET}
scope: invoices.read customers.read
pagination:
type: cursor
param: cursor
limitParam: limit
limit: 100
nextCursorField: next_cursor
filters:
- type: condition
expression: status == "paid"
else:
- type: drop
output:
type: httpRequest
endpoint: https://destination.example.com/api/invoices
method: POST
requestMode: batchname: http-polling-state-odata-filter
version: 1.0.0
description: Incremental polling through an OData filter, with the watermark rendered into the endpoint rather than a query parameter.
tags:
- http-polling
- state-persistence
- templating
input:
type: httpPolling
schedule: "*/10 * * * *"
# The persisted watermark reaches any part of the request, not only the query
# parameter statePersistence names:
#
# state.lastTimestamp RFC3339, the epoch before anything is persisted
# state.lastId absent until an ID has been persisted
#
# Spaces belong to the OData grammar — the runtime percent-encodes them.
endpoint: https://source.example.com/api/orders?$filter=LastModifiedDate gt {{ state.lastTimestamp }}
method: GET
dataField: value
headers:
# Available in headers too. A custom header rather than If-Modified-Since:
# RFC 9110 requires an HTTP-date there ("Sat, 15 Mar 2026 08:30:00 GMT") and
# mandates that a malformed value be ignored, so sending the RFC3339
# watermark would silently do nothing.
X-Since: "{{ state.lastTimestamp }}"
queryParams:
# And in queryParams values.
$orderby: LastModifiedDate asc
$top: "200"
statePersistence:
timestamp:
enabled: true
id:
enabled: true
field: Id
storagePath: ./.cannectors-state
filters: []
output:
type: httpRequest
endpoint: https://destination.example.com/api/orders/import
method: POST
requestMode: batch