httpapi connection
and set its required manifest_path to your v1 YAML file. Once embedded in the
catalog, the same manifest behaves like any other named source.
Start with the basic structure
A manifest has four main parts: connector metadata, user configuration, shared connection behavior, and resources. This shortened GitHub-style example shows how they fit together:Config
config maps field names to {type, required, default, enum, help, scope}.
Secrets (type: secret) render masked and are stored as refs. Config values
are referenced elsewhere in the manifest as config.<name>.
Configure requests
base_urlcan reference connector config, as PostHog does forhost. It is resolved once when the connector is configured, not separately for every request.authsupports no auth, bearer tokens, a custom header, basic auth, and OAuth 2 client credentials. OAuth tokens are cached. Query, HMAC, and chained authentication exist in the Go runtime but cannot currently be declared in YAML.rate_limit.requests_per_secondsets a shared limit for the connection. Adynamicblock can adjust that limit from response headers using a Unix timestamp, seconds from now, or an HTTP date.timeout_secondssets the request timeout and defaults to 60 seconds.
Resources
Each resource describes one endpoint:pathis the URL path. Placeholders such as{organization}are filled fromparams, using connection config or values captured from a parent resource.primary_keylists one or more fields that uniquely identify a record.fieldsmaps response values into typed columns. Add?to the shorthand type for a nullable field. Usemode: remainderto keep unprojected values, ormode: rawto keep a complete subtree.recordspoints to the array in the response. Usecardinality: onefor an endpoint that returns a single object.bodydescribes JSON, form, multipart, raw, or empty request bodies. Linear uses this support for GraphQL requests.
link: <rel>(RFC 5988 headers, default relnext)next_url: <path>(whole-URL envelopes like Django REST Framework)cursor {response, request: query.X|body.X|header.X, more, null_terminates}offset {offset, limit, page_size}(stops on a short page)page {number, size, page_size, total_pages}
Read child endpoints
Usefor_each: <parent> when an endpoint must be called once per parent record.
The parent’s capture block names the values available to the child. Child
requests default to five concurrent calls, and relationships can nest, such as
Resend webhooks → events → attempts.
A resource marked capture_only: true is walked for its captures and nothing
else. It is never emitted, discovered, or selectable, and it only runs when a
child that depends on it is selected. Use it when the parent walk a child needs
differs from the parent resource users read, for example a full history walk
that finds threads with new activity while the readable messages resource
stays incremental.
A child can gate its fan-out with parent.since: <captured key>. Parents whose
captured value is empty are skipped on every run. On an incremental run, parents
whose value sorts before the child’s effective lower bound under the child’s
comparator are skipped too, so the child only calls the API for parents that
changed since the last run. The child must declare an incremental block.
Incremental
Anincremental block turns a resource into an incremental read:
cursor_field: the record field carrying the watermark. It must be projected.start_param+inject_into: query | body | header: where the lower bound goes on the request.initial: the first-run lower bound.checkpoint_key: the durable key (defaults tocursor_field).comparator: lex (default) | numeric | time.overlap_secondsrewinds the lower bound for a lookback window and is valid fortimeornumericcomparators.
Discovery and streaming
discovery supports mode: static | dynamic, though every shipped manifest
is static. A stream mode (ndjson | sse | chunked_array) exists per
resource as an alternative to pagination. No shipped manifest uses it.
Runtime behavior
Available modes. Every manifest can run a full read. Incremental reads are available only when at least one resource has anincremental block, which is
why Attio and Resend are full-only. The manifest chooses the cursor field; a
pipeline can change only its lookback window.
Resuming full reads. Top-level resources checkpoint their pagination cursor
per page, so an interrupted full run resumes mid-listing. Child (fan-out)
resources always restart from the beginning. A resumed cursor the API no
longer honors falls back to a full read with a warning rather than failing.
Incremental child resources. Children share one watermark per resource
across all parents. There is no per-parent checkpoint. If parents progress
at different speeds (say, per-repository issues), a run can advance the
watermark past rows in slower parents. Set overlap_seconds generously to
re-read the window and let an upsert write mode absorb the duplicates.
Retries and limits. Responses are handled by status:
- HTTP 429 is retried up to 10 attempts, honoring
Retry-Aftercapped at 60 seconds and reported aspressure.rate_limited - 5xx responses are retried 3 times
- other 4xx responses are fatal
TestConnection capability, it sends one request to the first top-level,
non-streaming resource. Saving or validating a connection through the service
API does not invoke this live test.
Manifest validation. Loading a manifest reports all detectable problems
together. It checks:
- required objects, field types, and allowed enum values
- strict YAML decoding that rejects unknown fields
- defaults and shorthand expansion
- semantic checks for cross-field rules such as parent cycles and unprojected cursor fields
Maturity
Manifest-driven integrations start atalpha. Their behavior is derived from
API documentation, and schema validation plus mocked HTTP tests prove internal
consistency, not fidelity to the live API. They graduate to beta once run
end-to-end against the real service.