# Use SkimmerDB from DuckDB

> Query SkimmerDB from DuckDB over https with read_csv or read_json, send exports to your bucket as Parquet for DuckDB to read, and keep DuckLake tables searchable.

DuckDB and SkimmerDB work well together. DuckDB runs analytics where you are; SkimmerDB serves
search and SQL to apps and agents. Data moves between them three ways, with no glue code.

## Query SkimmerDB from DuckDB

Both SQL endpoints also answer a `GET` with the query in `?sql=`, so DuckDB's `read_csv` and
`read_json` can read an answer straight over https. Give DuckDB your key once as an HTTP secret:

```sql title="DuckDB"
CREATE SECRET skimmerdb (
    TYPE http,
    BEARER_TOKEN 'tb_...',
    SCOPE 'https://api.skimmerdb.com'
);

-- an answer as CSV, typed by DuckDB
SELECT kind, n
FROM read_csv('https://api.skimmerdb.com/v1/sql?format=csv&sql=SELECT%20kind%2C%20count(%2A)%20AS%20n%20FROM%20events%20GROUP%20BY%20kind')
ORDER BY n DESC;

-- or as NDJSON, here with a text predicate that runs on SkimmerDB's index
SELECT count(*)
FROM read_json(
    'https://api.skimmerdb.com/v1/sql?format=ndjson&sql=SELECT%20id%2C%20n%20FROM%20events%20WHERE%20contains(text%2C%20%27timeout%27)',
    format = 'newline_delimited'
);
```

- **Encode the SQL.** It goes in the URL, so encode it (`%20` for spaces and so on). Write `*`
  as `%2A`: DuckDB reads a bare `*` in a URL as a file glob and refuses it.
- **Streamed, every row.** `format=csv` and `format=ndjson` stream the whole answer. A plain scan
  of one namespace has no row limit; other query shapes stop at their row cap (at least
  1,000,000 rows).
- **Reads only.** A `GET` that would write (`INSERT`, `DELETE`, `CREATE TABLE AS`) answers
  `405`; use `POST` for those.
- `/v1/sql` sees every namespace of the key's tenant by name.
  `/v1/namespaces/{name}/sql` has that namespace as `docs`.
- Each request is one query for [metering and limits](/reference/limits), like a `POST`.

## Send answers to DuckDB as Parquet

For big answers, an [export](/guides/export#exports) writes a Parquet file. With a
`destination` it goes [into your own bucket](/guides/buckets#deliver-exports-to-your-bucket),
on AWS S3, R2, B2 or MinIO, where DuckDB reads it directly:

```sql title="DuckDB"
CREATE SECRET r2 (
    TYPE r2,
    KEY_ID '...',
    SECRET '...',
    ACCOUNT_ID '<account-id>'
);

SELECT status, count(*) FROM 'r2://analytics/exports/tickets.parquet' GROUP BY status;
```

## Keep a DuckLake table searchable

If your tables are in a DuckLake, [sync one into a namespace](/guides/ducklake). Each sync
reads the catalog and the Parquet files where they are and writes only the rows that changed.
After that, apps and agents can search it by substring, regex, filters and vectors, which
DuckDB has no index for.
