Connectors / APIs & web

REST

source

REST API source with pagination, auth, transforms

Install

cargo install faucet-cli --features source-rest
faucet init --source rest
# Cargo.toml — use it as a library
cargo add faucet-source-rest

Starter config

source:
  type: rest
  config:
    base_url: ""    # REQUIRED — Base URL of the API, e.g.
    # path: ""    # URL path, relative to `base_url`.
    # method: "GET"    # HTTP method for the data request.
    # auth: { type: none }    # one of: none, bearer, basic, api_key, api_key_query, oauth2, token_endpoint, custom
      # --- Alternative variants — replace the block above with one of these ---
      # type: bearer
      # config:
        # token: ""    # REQUIRED
      #
      # type: basic
      # config:
        # username: ""    # REQUIRED
        # password: ""    # REQUIRED
      #
      # type: api_key
      # config:
        # header: ""    # REQUIRED
        # value: ""    # REQUIRED
      #
      # type: api_key_query
      # config:
        # param: ""    # REQUIRED
        # value: ""    # REQUIRED
      #
      # type: oauth2
      # config:
        # token_url: ""    # REQUIRED
        # client_id: ""    # REQUIRED
        # client_secret: ""    # REQUIRED
        # scopes: []    # REQUIRED
        # expiry_ratio: 0.9
      #
      # type: token_endpoint
      # config:
        # url: ""    # REQUIRED
        # method: ""    # REQUIRED
        # body: null
        # token_path: ""    # REQUIRED
        # expiry_path: ""
        # expiry_ratio: 0.9
        # encoding: null
      #
      # type: custom
      # config:
        # headers: {}    # REQUIRED
    # headers: {}    # Static request headers sent on **every** request (data pages, async-job submit/poll/fetch requests, and OData `$metadata` discovery probes).
    # trusted_hosts: []    # Extra hosts that may receive this source's credentials when a server-given URL (a next-page link, an async-job URL, a discovery listing's next link) points at them, or a redirect leads to them.
    # query_params: {}    # Static query-string parameters, rendered as `?k=v`.
    # query_params_multi: {}    # Repeated / array-valued query params (#536), rendered as repeated keys — e.g.
    # body: null    # JSON request body, for `POST`/`PUT`-style reads.
    # pagination: { type: None }    # one of: None, Cursor, CursorInBody, LinkHeader, NextLinkInBody, PageNumber, Offset, OffsetInBody, RecordFieldCursor
      # --- Alternative variants — replace the block above with one of these ---
      # type: Cursor
      # next_token_path: ""    # REQUIRED
      # param_name: ""    # REQUIRED
      #
      # type: CursorInBody
      # next_token_path: ""    # REQUIRED
      # body_cursor_field: ""    # REQUIRED
      #
      # type: LinkHeader
      #
      # type: NextLinkInBody
      # next_link_path: ""    # REQUIRED
      #
      # type: PageNumber
      # param_name: ""    # REQUIRED
      # start_page: 0    # REQUIRED
      # page_size: 0
      # page_size_param: ""
      #
      # type: Offset
      # offset_param: ""    # REQUIRED
      # limit_param: ""    # REQUIRED
      # limit: 0    # REQUIRED
      # total_path: ""
      # rows_path: ""
      # stop_when_short: false
      #
      # type: OffsetInBody
      # offset_field: ""    # REQUIRED
      # limit_field: ""    # REQUIRED
      # limit: 0    # REQUIRED
      # stop_when_short: false
      # rows_path: ""
      # total_path: ""
      #
      # type: RecordFieldCursor
      # field: ""    # REQUIRED
      # into: null
      # param: ""    # REQUIRED
      # agg: null
      # stop_when_short: true
      # page_size: 0    # REQUIRED
      # inclusive: false
    # records_path: null    # JSONPath to the array of records inside the response body, e.g.
    # allow_missing_records_path: false    # Accept a response in which `records_path` resolves to nothing (the key is absent) as an empty page.
    # max_pages: null    # Optional cap on pages fetched per pass, across every pagination style.
    # allow_repeated_pages: false    # Keep paging when a `PageNumber` / `Offset` / `OffsetInBody` page holds the same records as an earlier, non-adjacent page of the pass, instead of failing the run.
    # assume_ordered: false    # The feed returns records in ascending `replication_key` order, so an incremental pass that `max_pages` cuts short may persist the largest key it read and resume from it next run.
    # request_delay: null    # Fixed delay between page requests, in seconds — a politeness knob for APIs that rate-limit by request frequency.
    # timeout: 30    # Per-request timeout in seconds.
    # max_retries: 3    # Number of retries (after the first attempt) for transient request failures.
    # retry_backoff: 1    # Base exponential-backoff delay between retries.
    # tolerated_http_errors: []    # HTTP status codes that should **not** cause an error.
    # retry_on_response: []    # Responses to treat as throttling and retry, for APIs that signal a rate limit with a 4xx other than 429 plus an error code in the body (e.g.
    # replication_method: { type: FullTable }    # one of: FullTable, Incremental
      # --- Alternative variants — replace the block above with one of these ---
      # type: Incremental
    # replication_key: null    # Field used for incremental replication bookmarking: a top-level field name (`updated_at`), a dot path into nested objects (`fields.updated`; numeric segments index arrays, and a literal top-level field of that exact name wins), or a JSON Pointer (`/fields/updated`) for field names that contain dots.
    # on_missing_key: null    # What to do with a record whose replication key is missing or `null` (#747).
    # start_replication_value: null    # Bookmark value: records where `record[replication_key] <= start_replication_value` are filtered out when `replication_me…
    # state_key: null    # Opt-in identifier used by `Pipeline::with_state_store` to persist this stream's bookmark across runs.
    # name: null    # Human-readable stream name (used in logging and Singer SCHEMA messages).
    # primary_keys: []    # Field names that uniquely identify a record (Singer `key_properties`).
    # schema: null    # JSON Schema describing the structure of each record.
    # schema_sample_size: 100    # Maximum number of records to sample when inferring the schema via `crate::stream::RestStream::infer_schema`.
    # requests: []    # `requests:` — one entry per request: a context map whose values are substituted into `path` placeholders.
    # request_concurrency: null    # `request_concurrency:` — maximum number of `requests` entries fetched concurrently (`partition_concurrency:` is still accepted).
    # tls: null    # Optional client-certificate (mutual TLS) config.
    # response_format: null    # How to parse the response body into records.
    # csv_delimiter: 44    # CSV field delimiter byte (default `,`).
    # csv_has_headers: true    # Whether the first CSV row is a header row supplying field names (default `true`).
    # csv_quote: 34    # CSV quote character (default `"`).
    # csv_null_values: []    # CSV fields equal to any of these strings decode as JSON `null` instead of a string (default: none).
    # excel_sheet: null    # Excel worksheet to read: a sheet name, or a 0-based index as a string.
    # excel_header_row: 0    # 0-based index of the Excel header row (default `0`).
    # replication_bind: null    # Bind the stored bookmark into the outgoing request (query param / header / body field / path) so the server returns only new rows.
    # odata: null    # Speak the OData protocol: `@odata.nextLink` paging, the `$.value` envelope, `$select`/`$filter`/`$expand`/`$orderby` sugar, and `$metadata` (EDMX) → schema discovery.
    # drop_key_prefixes: []    # Drop per-record keys starting with any of these prefixes (#654 M24).
    # decode: []    # Decode the response body before record extraction: a chain of `extract` (JSONPath) / `base64` / `gunzip` / `unzip` / `parse` (json|csv|xlsx|xml) steps.
    # async_job: null    # Run a submit→poll→fetch job lifecycle instead of a single GET, for bulk/export/report-run APIs (bulk export jobs, report runs, …).
    # window: null    # Bound each request to a rolling `[start, end)` window between the stored bookmark and `now`, iterating the windows within one run (each `step` wide) with per-window bookmark durability.
    # record_ancestors: {}    # When `records_path` selects a **nested** array element (e.g.
    # records_multi: []    # Emit several record arrays from one response in a single page (sharing one pagination advance), each stamped with a user-defined op marker under `op_field`.
    # op_field: ""    # Field name each `records_multi` record is stamped with its spec's `op` value.
    # records_route: null    # Route each record to a named stream by its GID type or a discriminator field, stamping the stream into `stream_field` — so one bulk job's parents and children reach separate sinks.
    # persist_cursor: false    # Persist the terminal pagination cursor as this run's bookmark (riding the existing `StreamPage.bookmark` / `StateStore` path — no core trait change) and, on resume, seed the stored bookmark back into the first request (query param for `cursor`, request body field for `cursor_in_body`) before paging.
    # discovery: null    # Generic, vendor-neutral discovery recipe: declare which API calls to make and how to extract datasets from their JSON responses (reusing this source's `auth` / `base_url` / `headers`).

Capabilities

Replay Non-deterministic replay
Exactly-once replay No
Incremental Yes
Dataset discovery Yes
Reports lag No
Compression No

Config fields

FieldTypeDefaultDescription
base_url
required
string — Base URL of the API, e.g. https://api.example.com/v2. Required. Joined with path; any trailing slash is handled either way.
path string "" URL path, relative to base_url. May contain {key} placeholders that are substituted per-partition (e.g. "/orgs/{org_id}/users").
method string GET HTTP method for the data request. Defaults to GET; use POST for search-style APIs that take a query body.
auth object type: none Authentication: either inline ({ type, config }) or a { ref: <name> } pointer to a shared provider in the CLI's top-level auth: catalog.
headers object — Static request headers sent on **every** request (data pages, async-job submit/poll/fetch requests, and OData $metadata discovery probes). Applied *before* the auth provider's header placements, so an auth header of the same name always wins on a clash. Values honor ${env:} / ${param.*} load-time interpolation and pass through the secrets/redaction boundary like other config strings. Invalid header names/values are rejected at config load (FaucetError::Config), never a mid-run panic.
trusted_hosts array — Extra hosts that may receive this source's credentials when a server-given URL (a next-page link, an async-job URL, a discovery listing's next link) points at them, or a redirect leads to them. Credentials — the auth block and a shared provider's headers — are otherwise sent only to the scheme, host and port of base_url; a link or redirect elsewhere is fetched without them and with only standard headers (Accept, User-Agent, …). An entry is a host on any port (files.example.com), a host and port (files.example.com:8443, that port only), or a *.example.com wildcard; a link and a redirect follow the same rule, and neither keeps credentials when it changes the scheme. An https base never follows a link to plain http.
query_params object — Static query-string parameters, rendered as ?k=v. Values honor {placeholder} context substitution for child sources. Empty by default.
query_params_multi object — Repeated / array-valued query params (#536), rendered as repeated keys — e.g. { "group_by[]": ["api_key_id", "model"] } → ?group_by[]=api_key_id&group_by[]=model. Applied alongside (in addition to) query_params; use this for APIs that need a key to appear more than once (group_by[], repeated expand/fields). Values honor {placeholder} context substitution for child sources, like query_params. Empty by default. Query parameters that may repeat, e.g. { "fields": ["id", "name"] } renders ?fields=id&fields=name. Use this rather than query_params when the API expects a key more than once.
body any — JSON request body, for POST/PUT-style reads. Ignored for GET.
pagination object type: None How to walk pages. Defaults to none (a single request). Each style carries its own termination guard — see the crate README's pagination table.
records_path string — JSONPath to the array of records inside the response body, e.g. $.data.items. When unset the body is expected to *be* the array (or a single object, which is emitted as one record).
allow_missing_records_path boolean — Accept a response in which records_path resolves to nothing (the key is absent) as an empty page. Off by default: a missing key usually means an error body served with HTTP 200 or a renamed field, and treating it as "no more records" ends the run green with rows unread. Enable it only for APIs that omit the records key on an empty result.
max_pages integer — Optional cap on pages fetched per pass, across every pagination style. Unset by default: each pagination style already stops on its own end signal and loop guards. When the cap cuts a pass short, only a persisted cursor bookmark (persist_cursor) is kept. An incremental or windowed pass the cap cuts short fails the run instead — its record-derived or window bookmark would skip the unread pages, and keeping none would re-read the same pages forever — unless assume_ordered is set.
allow_repeated_pages boolean — Keep paging when a PageNumber / Offset / OffsetInBody page holds the same records as an earlier, non-adjacent page of the pass, instead of failing the run. For feeds whose pages can legitimately repeat (a sparse report returning [{"count": 0}] on several pages). Requires max_pages, which then bounds a server that cycles pages. A page identical to the page just before it still ends paging.
assume_ordered boolean — The feed returns records in ascending replication_key order, so an incremental pass that max_pages cuts short may persist the largest key it read and resume from it next run. Without it such a pass fails the run. Requires a replication_bind when max_pages is set: only a bookmark the server filters on moves the next run past the pages already read. The bind must compare **inclusively** (>=, e.g. a gte|${bookmark} template): rows sharing the last key read may sit on the first unread page, and an exclusive (>) server filter would skip them; the client-side filter is inclusive, so the re-read rows at that key arrive again (pair with an upsert sink). Ignored for windowed passes, with persist_cursor, and across requests: entries (a truncated entry fails the run).
request_delay integer — Fixed delay between page requests, in seconds — a politeness knob for APIs that rate-limit by request frequency. Applied *in addition* to any Retry-After honoured by the retry path.
timeout integer 30 Per-request timeout in seconds. Covers one HTTP request, not the whole run, so a paginated extract is bounded per page. A streamed async_job result download is bounded per connect and per idle read instead.
max_retries integer 3 Number of retries (after the first attempt) for transient request failures. Default 3.
retry_backoff integer 1 Base exponential-backoff delay between retries. Default 1s. Shares the legacy-field precedence rule documented on max_retries.
tolerated_http_errors array [] HTTP status codes that should **not** cause an error. Responses with these codes are treated as empty pages (no records, no further pages).
retry_on_response array [] Responses to treat as throttling and retry, for APIs that signal a rate limit with a 4xx other than 429 plus an error code in the body (e.g. HTTP 400 with error.code 17 / 80004), or inside a 2xx (match_success). A rule's backoff_from reads the wait from the response (a reset header, a JSON header, the body). A matching response counts as a rate-limit response (faucet_source_throttled_total, faucet_source_retries_total{class="rate_limited"}) and is retried up to max_retries times in a row before the original error is surfaced. Checked before tolerated_http_errors. Applies to data pages, async_job submit/poll requests and discovery requests.
replication_method object type: FullTable full_table (default) re-reads everything each run; incremental filters on replication_key and emits a bookmark so the next run resumes.
replication_key string — Field used for incremental replication bookmarking: a top-level field name (updated_at), a dot path into nested objects (fields.updated; numeric segments index arrays, and a literal top-level field of that exact name wins), or a JSON Pointer (/fields/updated) for field names that contain dots. Not a JSONPath: it must resolve to one value.
on_missing_key object keep What to do with a record whose replication_key is missing or null once a start bookmark exists: keep (default — kept, counted in faucet_source_replication_key_missing_total, warned once per run), drop (counted and warned), or fail the run.
start_replication_value any — Bookmark value: records where record[replication_key] <= start_replication_value are filtered out when replication_method is Incremental.
state_key string — Opt-in identifier used by Pipeline::with_state_store to persist this stream's bookmark across runs. When set, the pipeline will load any previously-stored bookmark before fetching and write the new bookmark only after the sink confirms the batch.
name string — Human-readable stream name (used in logging and Singer SCHEMA messages).
primary_keys array [] Field names that uniquely identify a record (Singer key_properties).
schema any — JSON Schema describing the structure of each record.
schema_sample_size integer 100 Maximum number of records to sample when inferring the schema via crate::stream::RestStream::infer_schema. 0 means sample all available records (up to max_pages, when set). Defaults to 100.
requests array [] requests: — one entry per request: a context map whose values are substituted into path placeholders. The stream is executed once per entry and results are concatenated. Empty means run once with no substitution. The pre-#670 spelling partitions: is still accepted.
request_concurrency integer — request_concurrency: — maximum number of requests entries fetched concurrently (partition_concurrency: is still accepted). None (and 0/1) means sequential processing — the default.
tls object — Optional client-certificate (mutual TLS) config. When set, the source presents a client certificate on **every** request — data requests and any inline auth token request (both go through the same HTTP client). Requires the crate's mtls feature; a tls block on a build without it is a load-time error rather than being silently ignored.
response_format object json How to parse the response body. json (default) uses JSONPath extraction (records_path); csv / excel parse a tabular **file** body into records — for authenticated file endpoints such as a cloud-drive …/content download or any signed export URL. In file mode a single response is fetched (pagination must be none) and records_path does not apply. excel requires the crate's excel feature.
csv_delimiter integer 44 CSV field delimiter byte (default ,). Used only when response_format: csv.
csv_has_headers boolean true Whether the first CSV row is a header row supplying field names (default true). When false, fields are named column_0, column_1, …
csv_quote integer 34 CSV quote character (default "). Set it when the export quotes with something else — without this knob a '-quoted export parsed as literal quote characters inside every field (#670 L29). response_format: csv and the parse: csv decode step both honour it.
csv_null_values array [] CSV fields equal to any of these strings decode as JSON null instead of a string (default: none). Some bulk-export APIs write a null as an empty field, so their templates set [""]; other APIs list sentinels such as "NULL" or "#N/A". Headers are never mapped, and a quoted empty field ("") is mapped like an unquoted one — the CSV reader does not report quoting per field. response_format: csv only.
excel_sheet string — Excel worksheet to read: a sheet name, or a 0-based index as a string. When omitted, the first worksheet is used. response_format: excel only.
excel_header_row integer 0 0-based index of the Excel header row (default 0). Rows above it are skipped; the header row supplies field names. response_format: excel only.
replication_bind object — Bind the stored bookmark into the outgoing request (query param / header / body field / path) so the server returns only new rows. Composes with the existing replication_key client-side filter, which stays active as a safety net. Requires replication_method: incremental + replication_key.
odata object — Speak the OData protocol: @odata.nextLink paging, the $.value envelope, $select/$filter/$expand/$orderby sugar, and $metadata (EDMX) → schema discovery. When set, it derives the pagination, records_path, query params, and Prefer header at load time (explicit values still win). See ODataConfig.
drop_key_prefixes array — Drop per-record keys starting with any of these prefixes (#654 M24).
decode array — Decode the response body before record extraction: a chain of extract (JSONPath) / base64 / gunzip / unzip / parse (json|csv|xlsx|xml) steps. Lets a source consume base64/compressed/file payloads (e.g. a base64 XLSX inside a SOAP body, or a gzipped-CSV export). When set, it replaces the response_format body parsing, and pagination must be none. See DecodeStep.
async_job object — Run a submit→poll→fetch job lifecycle instead of a single GET, for bulk/export/report-run APIs (bulk export jobs, report runs, …). The fetched result flows through decode: / response_format. When set, pagination must be none. See AsyncJobConfig.
window object — Bound each request to a rolling [start, end) window between the stored bookmark and now, iterating the windows within one run (each step wide) with per-window bookmark durability. For APIs that require — or cap — a bounded date range (analytics/ads/reporting feeds). Parity with Airbyte's DatetimeBasedCursor. Requires replication_method: incremental + replication_key, and a start bookmark (from state, or start_replication_value). See WindowSpec.
record_ancestors object — When records_path selects a **nested** array element (e.g. $.data[*].data.object), copy fields from the enclosing [*] array-element ancestor onto each emitted record. The map is dest_field: ancestor_relative_path — for each matched leaf, the source walks up to the array-element ancestor and copies the named path onto the record under dest_field. Absent ⇒ records are emitted unchanged.
records_multi array — Emit several record arrays from one response in a single page (sharing one pagination advance), each stamped with a user-defined op marker under op_field. Composes with a downstream write_mode: upsert + delete_marker so added/modified/removed feeds route correctly. Mutually exclusive with records_path / record_ancestors, and requires response_format: json with no decode: pipeline.
op_field string — Field name each records_multi record is stamped with its spec's op value. Defaults to _op when omitted.
records_route object — Route each record to a named stream by its GID type or a discriminator field, stamping the stream into stream_field — so one bulk job's parents and children reach separate sinks. See RecordsRoute.
persist_cursor boolean false Persist the terminal pagination cursor as this run's bookmark (riding the existing StreamPage.bookmark / StateStore path — no core trait change) and, on resume, seed the stored bookmark back into the first request (query param for cursor, request body field for cursor_in_body) before paging. Only meaningful with pagination: cursor / cursor_in_body; mutually exclusive with window slicing. Default false.
discovery object — Generic, vendor-neutral discovery recipe: declare which API calls to make and how to extract datasets from their JSON responses (reusing this source's auth / base_url / headers). Powers faucet discover and run-time fan-out for any REST API with a listing / describe shape, with no connector-specific code. See DiscoverySpec.
Get started

Your first pipeline runs in five minutes.

Install the CLI, scaffold a config, and move real data — nothing external to stand up.

curl -LsSf https://github.com/faucet-hq/faucet-stream/releases/latest/download/faucet-cli-installer.sh | sh
brew install faucet-hq/faucet-stream/faucet-cli