REST API sources¶
REST API source¶
Use when reading JSON records from a REST API, with or without pagination.
For request-shape fields, see Connection settings by endpoint type
and source.configure
in the Metadata reference.
{
"name": "orders_api",
"connection_type": "api",
"format": "api",
"configure": {
"base_url": "https://api.example.com/v1",
"auth_type": "bearer",
"auth_token": "DC_ORDERS_API_TOKEN",
"timeout": 30,
"default_headers": { "Accept": "application/json" }
},
"secrets_ref": {
"env:": ["auth_token"]
}
}
Put request shape and pagination on the source, not on the connection:
"source": {
"connection_name": "orders_api",
"table": "orders",
"watermark_columns": ["updated_at"],
"configure": {
"endpoint": "/orders",
"method": "GET",
"params": { "status": "open" },
"pagination_type": "offset",
"page_size": 200,
"max_pages": 1000,
"total_path": "meta.total",
"data_path": "data.items"
}
}
This example expects a response shaped like
{"meta":{"total":2},"data":{"items":[{"id":1,"updated_at":"2026-01-01T00:00:00Z"},{"id":2,"updated_at":"2026-01-02T00:00:00Z"}]}}.
Check data_path against a real response: a missing path produces zero records,
not a path-validation error. Omit pagination_type and total_path for a
single-response endpoint. watermark_columns alone filters rows after
fetching; it does not make the API return only changed records. Use
watermark push-down when the endpoint accepts
incremental parameters. The API source also applies source.filter_expression
locally to the returned DataFrame before transforms; it does not add the
predicate to request parameters or body.
If pagination_type is present, it must be offset, cursor, or next_link.
An omitted or null value means one response. A typo or unsupported value fails
before credentials are resolved or an API request is sent, so an incremental
run cannot advance its watermark without a known completion contract.
With total_path, offset pages are fetched concurrently (four workers by
default), and rate_limit_delay is not applied. max_pages is a safety
budget: if the response still requires another page at that limit, the read
fails instead of returning a partial result. Match page_size, max_pages, and
offset_max_workers to the provider's contract and rate limit. For sequential
offset requests, omit total_path; then rate_limit_delay applies between
pages. See pagination contracts.
| Connection-level key | Purpose |
|---|---|
base_url |
Required root URL |
auth_type |
bearer, basic, api_key, oauth2_client_credentials, aws_sigv4 |
auth_token / username / password / api_key_* |
Auth credentials |
token_url, client_id, client_secret |
OAuth2 client-credentials flow |
default_headers, timeout |
Shared request defaults |
| Source-level key | Purpose |
|---|---|
endpoint |
Path appended to base_url |
method, params, body |
Request shape |
pagination_type |
offset, cursor, or next_link |
page_size, max_pages, data_path |
Response traversal and size |
total_path, offset_max_workers |
Parallel offset pagination |
rate_limit_delay, max_retries |
Delay between sequential pages; retry HTTP 429 responses |
range_param_mapping |
Canonical per-field lower/upper API bindings, operators, format, residual response field, and state meaning |
watermark_to_param_timezone |
Source override for generated API upper bounds and watermark advancement |
watermark_range_interval_unit, watermark_range_interval_amount, watermark_range_start, watermark_range_max_workers |
Canonical incremental range splitting and its first-run/worker controls |
source.filter_expression is deliberately absent from the request table. Put
server-side filters in the endpoint-specific params or body when the API
supports them, and use the source predicate for a framework-side safety filter
over the returned columns.
The table above is the basic API shape. Use Connections for authentication and the API source configuration section below for all request, pagination, timezone, and split-range variants. For a combined example see Incremental API with pagination.
For API incremental loading, choose between local filtering, request watermark push-down, and split ranges. The complete field combinations, timezone handling, and inclusive-boundary rules are documented in API source configuration, especially Push down and split watermark ranges.
API source configuration¶
This section owns the request, response, pagination, and API watermark fields
that belong to source.configure. Connections
owns the base URL, authentication, and secrets. The exact source field types
and allowed values are in the Source reference
and source.configure.
An authenticated source can use any supported pagination and watermark mode. The examples below are source fragments, not complete metadata documents.
| Decision | Choose | Where to configure |
|---|---|---|
| Response | Root JSON list, nested list, or one nested object | source.configure.data_path when not a root list |
| Pagination | One response, record offset, cursor, or next-link | source.configure.pagination_type and its matching path/parameter keys |
| Incremental read | Fetch then filter, API push-down, or bounded split ranges | source.watermark_columns plus the applicable source.configure keys |
Match the request and response shape¶
source.configure.method is an HTTP method string (GET by default), not a
DataCoolie enum. params becomes URL query parameters and body becomes a
JSON request body. For example, a POST endpoint that returns one JSON object
under result can use:
{
"source": {
"connection_name": "orders_api_oauth",
"table": "order_summary",
"configure": {
"endpoint": "/reports/orders",
"method": "POST",
"params": {"region": "west"},
"body": {"status": "open"},
"data_path": "result"
}
}
}
The response {"result":{"count":12}} becomes one record. With no
data_path, the response must be a root-level JSON list. A wrong path yields
zero records. These keys belong to the source because different endpoints on
one connection may use different request and response shapes.
Choose the pagination contract¶
Pagination settings belong to source.configure because different endpoints
on the same API may return different response shapes. For every paginated
mode, choose max_pages large enough for the intended result. Reaching the cap
while another page is required raises a source error instead of returning
partial data. Use a provider-defined stable sort or snapshot when available,
especially for concurrent offset requests against changing data.
One response (no pagination)¶
Omit pagination_type for an endpoint that returns all records in one
response. For a root-level list, omit data_path too; for a nested list, set
data_path to its dot-separated path. The reader makes one request and does
not follow a cursor, link, or offset unless a pagination mode is configured.
Offset pagination with provider-specific parameter names¶
{
"source": {
"connection_name": "orders_api_key",
"table": "orders",
"configure": {
"endpoint": "/orders",
"pagination_type": "offset",
"page_size": 200,
"offset_param": "skip",
"limit_param": "per_page",
"total_path": "meta.total",
"data_path": "data.items",
"offset_max_workers": 4
}
}
}
With total_path, the first response provides the total and remaining pages
can be fetched concurrently. Without it, offset pages are fetched
sequentially until a short page or empty response is returned. The reader
sends record offsets (0, 200, 400, ... in this example), not page
numbers. A provider whose page parameter expects 1, 2, 3, ... does not
fit this built-in offset mode; do not merely rename offset_param to page.
The first response above must contain a numeric meta.total and an array at
data.items. A wrong data_path is treated as an empty result, so test it
against a real response. If total_path is missing or non-numeric, the
concurrent path fails. Setting total_path also enables up to four concurrent
offset workers by default; rate_limit_delay only delays sequential page
requests. max_pages defaults to 1,000 and is a safety budget: the reader
fails when the declared total needs more pages than that budget or when the
aggregate fetched count does not equal the declared total. max_retries
retries HTTP 429 responses, not arbitrary HTTP failures.
Cursor pagination¶
{
"source": {
"connection_name": "orders_api_oauth",
"table": "orders",
"configure": {
"endpoint": "/orders",
"pagination_type": "cursor",
"page_size": 100,
"cursor_path": "paging.next_cursor",
"cursor_param": "after",
"data_path": "results"
}
}
}
The reader sends the returned cursor under cursor_param. The default response
path is next_cursor and the default request parameter is cursor. The
authored request options are grouped under
source.configure.
Next-link pagination¶
{
"source": {
"connection_name": "orders_api_basic",
"table": "orders",
"configure": {
"endpoint": "/orders",
"pagination_type": "next_link",
"next_link_path": "paging.next",
"data_path": "data"
}
}
}
When the response contains an absolute next-link URL, the reader follows it
verbatim and uses the link's parameters. The continuation token is opaque: the
reader does not decode it or append the original query bounds by default. Set
next_link_bound_mode: "repeat_query_bounds" only for an endpoint whose
contract explicitly requires the original range parameters on every
continuation; matching values are checked and conflicting/duplicate values
fail before the request. It stops when the configured path is empty.
Relative links are resolved against the current page URL. A continuation must
stay on the configured HTTP(S) origin (scheme, host, and effective port); a
foreign host or port, scheme downgrade, userinfo, or non-HTTP(S) link fails
before the next request. Connection credentials therefore remain scoped to the
configured origin. Redirect following is disabled for the API client, so a
redirect cannot bypass the continuation check. Providers that require
cross-origin continuation need an explicit credential-scoping policy outside
this built-in mode.
Push down and split watermark ranges¶
For a bounded replay or another source-owned range, use the explicit
range_param_mapping contract. Each field declares its lower and upper API
parameter, the wire format, the response field used for residual filtering,
and how a successful read advances state:
{
"source": {
"connection_name": "orders_api",
"table": "orders",
"watermark_columns": ["updated_at"],
"configure": {
"endpoint": "/orders",
"range_param_mapping": {
"updated_at": {
"lower": {"name": "created_from", "operator": ">="},
"upper": {"name": "created_before", "operator": "<"},
"format": "iso",
"response_column": "updated_at",
"watermark_value": "observed_max"
}
},
"pagination_type": "next_link",
"next_link_path": "paging.next"
}
}
}
watermark_value: "observed_max" saves the maximum response value after a
successful read. watermark_value: "request_end" saves the exclusive upper
bound that the endpoint confirmed. Omitted incremental lower operators resolve
to > for observed_max and >= for request_end; active fields with mixed
semantics are rejected before HTTP. A bounded replay uses [start, end) and
requires endpoint operators and a response_column that can enforce any
boundary the endpoint cannot enforce itself. If the endpoint uses an inclusive
upper operator (<=), keep that operator in the binding and apply the exact
residual < end check to response_column; do not subtract an epsilon.
A tracked observed_max binding must have a non-null response_column; the
reader rejects a missing column mapping before HTTP, even when watermark saving
is disabled. A request_end binding can omit it when the endpoint enforces the
requested operators exactly and guarantees complete interval pagination.
New bindings encode explicit bounds without rounding. iso retains the
datetime offset and microseconds; nonzero ISO fractional digits beyond six
cannot be represented and are rejected. ISO timezone offsets that Python
cannot parse without precision loss are also rejected. date requires midnight, datetime
requires whole seconds, and datetime_ms / timestamp_ms require millisecond
alignment. timestamp preserves fractional Unix seconds using exact decimal
encoding. For example, 12:30:00.123456 cannot use datetime_ms; use iso or
supply a bound that is already aligned. Validation happens before HTTP for
both incremental and bounded reads. String lower/upper shorthand and the
schema-supported integer aliases (integer, int, native_integer, and
number) remain valid. Declare format in the binding; do not use a
runtime-only alias.
When the reader generates an incremental upper bound from the current time,
it chooses a ceiling compatible with the binding's precision and logical
date/datetime type before creating requests. Split requests and request_end
state use that same covered ceiling. Explicit bounds and stored lower values
are never rounded to make them fit.
The mapped field does not have to be one of source.watermark_columns when it
is used only for bounded selection. For example, set chunk_column="created_at"
and define range_param_mapping.created_at while keeping
watermark_columns: ["updated_at"]. Replay sends the created_at bounds and
the API reader continues to calculate/persist state only for the configured
watermark columns. A maximum observed from that slice must not be treated as
proof that the complete updated_at history was read.
Set a binding's location to params for query parameters or body for a
top-level JSON body field. Nested body paths are outside this generic mapping
contract and require a source-specific adapter.
Canonical incremental splitting uses the same range_param_mapping as bounded
reads and replay. Keep the split controls together with a complete lower and
upper binding for the active field:
{
"source": {
"connection_name": "orders_api_oauth",
"watermark_columns": ["updated_at"],
"configure": {
"endpoint": "/orders",
"range_param_mapping": {
"updated_at": {
"lower": "updated_since",
"upper": {"name": "updated_before", "operator": "<"},
"format": "iso",
"response_column": "updated_at",
"watermark_value": "observed_max"
}
},
"watermark_to_param_timezone": "Asia/Ho_Chi_Minh",
"watermark_range_interval_unit": "day",
"watermark_range_interval_amount": 1,
"watermark_range_start": "2026-01-01T00:00:00+07:00",
"watermark_range_max_workers": 4
}
}
}
watermark_range_interval_unit supports hour, day, month, and year.
The first split run needs either a stored lower watermark or
watermark_range_start. watermark_range_interval_amount controls the size
of each interval and watermark_range_max_workers controls concurrent range
requests. These controls apply to canonical incremental splitting; explicit
bounded reads and replay receive their [start, end) range from the source
contract and do not need them.
watermark_to_param_timezone accepts an IANA name such as
Asia/Ho_Chi_Minh or a UTC offset such as +07:00. A source-level value wins
over the same connection-level default. The range endpoint and the stored
watermark must use a precision and timezone that the API understands.
range_param_mapping.<field>.format controls how a bound is encoded:
| Format | Example output | Behavior |
|---|---|---|
iso |
2026-01-02T03:04:05+00:00 |
ISO-8601 output; preserves the datetime's timezone state |
date |
2026-01-02 |
Calendar date only |
timestamp |
1767323045.0 |
Unix seconds; naive datetimes are treated as UTC |
timestamp_ms |
1767323045000 |
Unix milliseconds; naive datetimes are treated as UTC |
datetime |
2026-01-02T03:04:05 |
Naive ISO datetime at whole-second precision |
datetime_ms |
2026-01-02T03:04:05.123 |
Naive ISO datetime retaining millisecond precision |
integer, int, native_integer, number |
42 |
Integer aliases for native numeric API bounds |
String lower/upper shorthand is equivalent to an object with name and the
default location/operator. Use an object when the endpoint needs body or an
explicit operator. Bounds that cannot be represented at the declared precision
fail before HTTP; no lossy truncation is used.
Avoid invalid combinations¶
- Do not put pagination keys on the connection; they belong to the source.
- For the unreleased
0.2.0cutover, the removed source nameswatermark_param_mapping,watermark_to_param,watermark_param_location,watermark_param_format, andwatermark_range_to_exclusive_offsetare rejected even when their values are null or empty. Replace them manually withrange_param_mapping; no automatic migration is performed. - Do not use
cursor_paramornext_link_pathwith the wrongpagination_type. - Do not use a custom or misspelled
pagination_type; onlyoffset,cursor, andnext_linkare supported, and an omitted value means one response. - Put auth settings and secrets on the connection.
- Do not let the API response omit a pushed-down watermark column without
understanding the reader's
nowadvancement behavior.
table is optional for APIs
The API reader uses connection.configure.base_url plus
source.configure.endpoint. source.table is best treated as a logical
label for your metadata or logging, not as the actual HTTP path. Keep a
stable table when shared schema hints
must match this source by connection/table identity.