Generic HTTP/REST Connector
Integrate any REST or GraphQL API as a first-class Nexus connector by authoring sandbox-compiled build_request and parse_response scripts, with OAuth2/auth resolved through the bridge and SSRF-checked egress.
The generic HTTP/REST connector is Nexus's answer to "we need data from an API you don't have a native adapter for." Instead of waiting for Stingray to ship a bespoke integration, you author two small scripts — one that builds the outbound request for a query, one that parses the response into rows — and register them as a bridge. From that point the API behaves like any other connector: it is reachable through UQL, ctx.adapters, discovery, RBAC, and the capability model, with credentials held in Key Vault and every request SSRF-checked. This page is for engineers integrating a REST or GraphQL API that has no native connector.
When to use this connector (and when not to)
| You want to… | Use |
|---|---|
| Expose a third-party REST/GraphQL API as a governed, reusable data source for reports, pipelines, and AI agents | Generic HTTP/REST connector |
| Integrate a SQL database with a JDBC driver | Generic JDBC connector |
| Make a one-off, ad-hoc call from inside a single pipeline (a webhook, a status poke) | pipeline ctx.http |
| Integrate a well-known system (NetSuite, QuickBooks, Monday, a SQL database) | the native connector — less to author, more capability built in |
The distinction between this connector and ctx.http matters and is covered in full below. In short: the connector is a durable, shared, credentialed data source; ctx.http is a raw client for one pipeline's private logic.
How it works
A generic HTTP/REST bridge is configured with a base URL, an authentication method, and two author-supplied scripts:
build_request— receives the incoming UQL query (collection,where,limit,offset, ordering, and — for writes — the record payload) and returns the HTTP request to make: method, path, query string, headers, and body. This is where you translate UQL into the API's own query conventions and where pagination is expressed.parse_response— receives the raw HTTP response and returns the list of row dicts (for reads) or the write result. This is where you dig the records out of whatever envelope the API wraps them in (data,results,items, a GraphQLdata.board.itemspath) and normalize field names.
Both scripts are compiled under the same sandbox as pipelines: an import allowlist, no filesystem access, no raw sockets, no exec/eval. They receive data and a small helper surface and return data. They cannot open their own network connections — all egress goes through the governed HTTP client, which enforces the SSRF checks and the tenant egress allowlist described below. A script that tries to reach outside that boundary fails at compile or call time, not silently.
Authentication resolved through the bridge
You do not put tokens in your scripts. The bridge resolves credentials from your tenant's Key Vault and injects them into the request context, so build_request reads a resolved value rather than a raw secret. Supported shapes include:
- OAuth2 client-credentials — the bridge fetches and caches an access token, refreshing it before expiry, and exposes it to
build_requestfor theAuthorizationheader. - Bearer / static token — a stored secret presented as
Authorization: Bearer <token>. - API-key header — an arbitrary header name/value (some APIs use
X-API-Key, or a rawAuthorizationvalue with noBearerprefix).
Because auth is bridge-resolved, rotating a credential is a Key Vault operation — no script edit, no redeploy — and the token never appears in report definitions, pipeline code, or logs.
SSRF-checked, allowlisted egress
Every request the connector makes passes the same egress guard as the rest of the platform: the destination is validated against the tenant's app-layer egress allowlist, and the resolved IP is checked to block private, link-local, and metadata-endpoint ranges (the DNS-rebind-resistant IP-pinning check). This holds at the network layer too — the tenant VNet's NSG constrains outbound traffic. The connector cannot be turned into a server-side request forgery primitive by a crafted build_request.
Capabilities
A generic HTTP/REST bridge declares its capabilities like any other connector, and you set them to match what you actually implement in your scripts:
- Read — always, once
build_request/parse_responsehandle reads. - Write — insert/update/delete if you implement the write branch of
build_requestand the API supports it. - Discover — you can implement schema discovery to enumerate the API's resources, or leave collections author-specified.
- Pushdown — you declare which
whereoperators and which ordering/paging yourbuild_requestgenuinely translates into the API's query parameters. Anything you don't push down, the engine applies in memory over the returned rows (bounded by the row budget), exactly as with any low-pushdown native connector. Declaring pushdown you haven't implemented would produce wrong results — declare only what you translate, and let the engine handle the rest correctly. See the pushdown semantics in the overview.
Worked example: a paginated REST API → rows
Suppose you're integrating a project-tracking API at https://api.example.com/v2, where issues live at GET /issues, results are wrapped in { "data": [...], "next_cursor": "..." }, and the API takes status, limit, and cursor query params. You want a bridge that reads issues and pushes down an equality filter on status.
build_request — translate the UQL query into the API's request, following the cursor for pagination:
def build_request(query, auth, page):
params = {"limit": min(query.get("limit", 100), 200)}
# Push down an equality filter on status; everything else is left to the engine.
where = query.get("where", {})
status = where.get("status", {}).get("$eq")
if status:
params["status"] = status
# page.cursor is None on the first call, then whatever parse_response returned.
if page.cursor:
params["cursor"] = page.cursor
return {
"method": "GET",
"path": "/issues",
"params": params,
"headers": {"Authorization": f"Bearer {auth.access_token}"},
}
parse_response — pull rows out of the envelope and hand back the next cursor so the engine keeps paging until it has enough:
def parse_response(response):
body = response.json()
rows = [
{
"id": item["id"],
"title": item["title"],
"status": item["status"],
"assignee": item.get("assignee", {}).get("email"),
"updated_at": item["updated_at"],
}
for item in body.get("data", [])
]
return {"rows": rows, "next_cursor": body.get("next_cursor")}
With pushdown-filter declared for $eq on status, a UQL query like where status $eq "open" limit 500 sends status=open to the API and pages until it has 500 rows. A filter the scripts don't translate — say $contains on title — is still honored: the engine applies it in memory over the fetched rows, so the answer is correct even though the API never saw that predicate.
Reached from a pipeline, the bridge is just another adapter:
async def run(ctx):
issues = await ctx.adapters.example_issues.read({
"collection": "issues",
"where": {"status": {"$eq": "open"}},
"limit": 500,
})
await ctx.workspace.upsert("open_issues", issues.rows, keys=["id"])
ctx.logger.info("staged %d open issues", len(issues.rows))
Relationship to ctx.http
Both go through the same SSRF-safe, allowlisted egress path — but they serve different jobs.
| Generic HTTP/REST connector | ctx.http |
|
|---|---|---|
| What it is | A registered bridge / data source | A raw HTTP client injected into one pipeline |
| Reusability | Shared across reports, pipelines, AI agents | Private to the pipeline that calls it |
| Query surface | Full UQL: where, pushdown, limit, discovery |
Whatever you code by hand |
| Credentials | Bridge-resolved from Key Vault, cached/refreshed | You fetch secrets via ctx.secrets and set headers yourself |
| Governance | RBAC-scoped as a connector resource; appears in audit as a bridge |
Governed as pipeline egress |
| Best for | A durable integration many things depend on | A one-off call inside bespoke pipeline logic |
Use ctx.http when the call is incidental to one pipeline's logic — posting a notification, poking a webhook, a single lookup. Promote it to a generic HTTP/REST connector the moment more than one thing needs the data, you want it queryable via UQL, or you want it governed and auditable as a first-class source.
What this does and doesn't do
- It does give you a governed, credentialed, SSRF-checked, RBAC-scoped, UQL-queryable data source for any REST or GraphQL API, without a native adapter.
- It does let you declare exactly the pushdown you implement, with the engine correctly handling everything else in memory.
- It does not magically infer an API's shape — you author the request/response mapping. The scripts are small, but they are yours to write and maintain.
- It does not grant scripts unrestricted network or filesystem access — they are sandbox-compiled and can only egress through the governed client.
- For heavy analytical workloads over a low-pushdown API, stage into the internal workspace and aggregate there rather than pulling large row sets repeatedly.
See also
- Connectors overview — the adapter contract and pushdown semantics
- Generic JDBC connector — the equivalent path for SQL databases
- Capability matrix
- Pipeline SDK —
ctx.http,ctx.secrets, and the sandbox - Connections & bridges and Secrets