cannectors

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: orders

Options

propertytypedefaultdescription
typerequired
"httpPolling"Module type discriminator. Must be `httpPolling` for this module.
id
stringUnique identifier within the pipeline.
name
stringHuman-readable name.
description
string
enabled
booleantrueWhether module is active.
tags
array<string>
onError
string"fail"Default error action. Case-insensitive; normalized to lowercase by the runtime.
endpointrequired
stringHTTP 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
stringInline request body. Templates ({{record.field}}) are evaluated at runtime. The body is sent regardless of the HTTP method.
bodyTemplateFile
stringPath 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
integerRequest timeout in milliseconds. When omitted, each module applies its own runtime default.
schedule
stringOptional CRON expression for polling. Validated at runtime.
object
object
dataField
stringJSON 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_pages

Offset-based

pagination:
  type: offset
  param: offset
  limitParam: limit
  limit: 250
  totalField: meta.total

Cursor-based

pagination:
  type: cursor
  param: cursor
  limitParam: per_page
  limit: 100
  nextCursorField: meta.next_cursor

The 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-state

queryParam 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

examples/01-http-polling-basic-to-http-batch.yamlview source ↗
01-http-polling-basic-to-http-batch.yaml
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
      - 202
examples/02-http-polling-page-pagination.yamlview source ↗
02-http-polling-page-pagination.yaml
name: 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: batch
examples/03-http-polling-offset-pagination-state.yamlview source ↗
03-http-polling-offset-pagination-state.yaml
name: 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: batch
examples/04-http-polling-cursor-oauth2.yamlview source ↗
04-http-polling-cursor-oauth2.yaml
name: 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: batch
examples/28-http-polling-state-odata-filter.yamlview source ↗
28-http-polling-state-odata-filter.yaml
name: 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

Cross-references