httpapi

May 3, 2026 · View on GitHub

A DuckDB extension that exposes scalar functions for making HTTP requests directly from SQL. Built on top of cpp-httplib with OpenSSL for HTTPS support, httpapi lets you call REST endpoints without leaving DuckDB.

LOAD httpapi;

SELECT (http_get('https://api.github.com/zen')).body AS zen;

Features

  • HTTP and HTTPS (TLS via OpenSSL)
  • Scalar functions for the common verbs: GET, POST, PUT, PATCH, DELETE, HEAD
  • Vectorised — call any function over a column of URLs
  • Uniform STRUCT(status INTEGER, body VARCHAR, error VARCHAR) return type so transport errors and HTTP responses are easy to handle in SQL
  • Automatic redirect following
  • 30 second connect / read / write timeout

Installation

This extension is not (yet) part of the DuckDB core or community-extensions repository. Until then, build from source or load a binary built by the CI pipeline:

-- Start DuckDB with: duckdb -unsigned
LOAD '/path/to/httpapi.duckdb_extension';

If you publish to a custom repository, install with:

SET custom_extension_repository = 'your-bucket.example.com/httpapi/latest';
INSTALL httpapi;
LOAD httpapi;

Function reference

Every function returns a STRUCT with three fields:

FieldTypeDescription
statusINTEGERHTTP status code. 0 when the request never completed.
bodyVARCHARResponse body. Empty string when the request failed.
errorVARCHARTransport-level error message, or NULL if the request succeeded.

A request "succeeded" here means a response was received from the server. A 4xx or 5xx response is still considered successful at the transport level — error is NULL and status carries the HTTP code.

If the URL argument is NULL, the result is NULL.

Functions without a body

http_get(url VARCHAR)    -> STRUCT(status INTEGER, body VARCHAR, error VARCHAR)
http_delete(url VARCHAR) -> STRUCT(status INTEGER, body VARCHAR, error VARCHAR)
http_head(url VARCHAR)   -> STRUCT(status INTEGER, body VARCHAR, error VARCHAR)

Functions with a body

The body is sent with Content-Type: application/json. A NULL body is treated as an empty body.

http_post(url VARCHAR,  body VARCHAR) -> STRUCT(...)
http_put(url VARCHAR,   body VARCHAR) -> STRUCT(...)
http_patch(url VARCHAR, body VARCHAR) -> STRUCT(...)

Examples

Single request

SELECT (http_get('https://duckdb.org')).status;
-- 200

Unpack the response

SELECT
    r.status,
    r.error,
    json_extract_string(r.body, '$.title') AS title
FROM (
    SELECT http_get('https://jsonplaceholder.typicode.com/posts/1') AS r
) t;

Vectorised over a column

WITH urls(url) AS (
    VALUES ('https://duckdb.org'),
           ('https://example.com'),
           ('not-a-url')
)
SELECT
    url,
    (http_get(url)).status AS status,
    (http_get(url)).error  AS error
FROM urls;

POST with a JSON payload

SELECT (http_post(
    'https://httpbin.org/post',
    '{"hello": "duckdb"}'
)).body;

Error handling

Invalid URLs or unreachable hosts surface in the error field instead of raising:

SELECT (http_get('not-a-url')).error;
-- invalid url: not-a-url

Behaviour and limitations

  • Only http:// and https:// URLs are accepted; ports must be valid integers in [0, 65535].
  • Custom request headers and query-string helpers are not yet exposed — encode query parameters into the URL yourself.
  • Redirects are followed automatically.
  • Connect, read and write timeouts are fixed at 30 seconds.
  • Content-Type for body-bearing requests is always application/json.
  • Response headers are not returned; only status, body and any transport error are exposed.
  • Each function call opens its own connection — there is no connection pooling. Be considerate when fanning out across large tables.

Building from source

The extension uses DuckDB's standard extension build system, with cpp-httplib vendored as a single header under third_party/httplib and OpenSSL pulled in via vcpkg.

git clone --recurse-submodules https://github.com/diegoglozano/duckdb-httpapi.git
cd duckdb-httpapi
make            # release build (use GEN=ninja for faster rebuilds)

The resulting artifacts:

./build/release/duckdb                                       # shell with extension preloaded
./build/release/test/unittest                                # test runner
./build/release/extension/httpapi/httpapi.duckdb_extension   # loadable binary

For more on the build system and CI workflow, see docs/README.md and docs/UPDATING.md.

Testing

SQL-logic tests live under test/sql. Run them with:

make test

License

MIT — see LICENSE.