REST API source

Point at any JSON endpoint, walk into the array of rows, and we'll detect the schema and sync on a schedule.

What we expect from the response

The connector needs an array of objects. Each object becomes a row; each key a column. Two response shapes are supported:

Flat array

[
  { "id": 1, "email": "[email protected]" },
  { "id": 2, "email": "[email protected]" }
]

Nested under a path

{
  "results": [ ... ],
  "meta": { "total": 200 }
}

For the nested shape, set data_path to results (or data.items, etc — dotted paths walk nested objects). When the path mismatches, the error tells you which keys are available at the failing step and suggests a corrected path that resolves to a list.

Authentication

  • Bearer / API key: add to headers in the wizard, e.g. {"Authorization": "Bearer $TOKEN"}.
  • Query param: append to the URL.
  • Basic auth / OAuth client-credentials: not yet supported in the wizard — open an issue and we'll prioritize.

Headers are stored encrypted at rest. Values containing tokens are redacted from every error message we surface.

Pagination

Two pagination shapes are auto-detected:

Cursor pagination

{
  "data": [...],
  "next_cursor": "eyJpZCI6MTAwfQ"
}

Set pagination.type = "cursor", pagination.cursor_field = "next_cursor", and we'll follow it until the field comes back empty.

Page-number pagination

?page=1
?page=2
?page=3
... until response is empty

Set pagination.type = "page" and pagination.param = "page". We stop when the page returns an empty list.

OpenAPI import

Paste an OpenAPI 3 spec URL and we list the GET operations whose 2xx response is application/json. Pick one and we generate the source config — URL, headers, and (where the schema declares it) data_path — automatically.

Single-object responses (no array anywhere) are rejected with a clear message — we don't synthesize one-row tables from a single object.

Type inference

Each column's type is inferred from the first sync's values, the same way the Sheets connector does it: boolean → number → date → string. Subsequent syncs use the locked-in type; values that don't fit are coerced or recorded as sync errors.

Common errors

If you see…Try this
Path 'data.items' not found. Keys at 'data': ['id', 'name', 'rows']The connector walked into data but didn't find items. Try one of the listed keys (here, data.rows looks like the array).
Expected array at 'results.items', got dict with keys ['data', 'meta']. Try 'results.items.data' instead?Walk one level deeper. The connector found a list at the suggested path — copy it into data_path.
HTTP 401 from upstreamThe endpoint rejected our request. Check the Authorization header — for many APIs it's Bearer $TOKEN, not the bare token.
HTTP 429 from upstream — rate limitedThe remote API is throttling us. Lower the sync frequency, or set a smaller page_size if your endpoint supports it.
Response is HTML, not JSON (Content-Type: text/html)The URL is returning a login or error page. Confirm the endpoint requires no browser cookie, and that auth headers are correct.
Requests to private/internal addresses are not allowedThe hostname resolves to a private/loopback IP. Public URLs only — we block SSRF targets like 169.254.169.254 and 10.0.0.0/8.

Tips

  • Test a fresh source against a small slice first (?limit=10). It confirms data_path and auth before paying the cost of a full sync.
  • Long-poll endpoints aren't a fit — set up a webhook source (P2) instead, or schedule frequent short syncs.
  • A response over 10 MB is rejected. Paginate if you're hitting the cap.
See also: Getting started · Google Sheets