Importing files
Load CSV, NDJSON or Parquet into a namespace in one request, parsed straight into typed columns, every row visible together.
For data that already lives in files (exports, logs, tables from a warehouse or a lake), import it in one request instead of writing documents in batches. Every row becomes a document; the file is parsed straight into typed columns, and all rows become visible together, or none.
POST /v1/namespaces/{name}/import?format=csv|ndjson|parquet&id=<column>&text=<col,col> with
the file as the body. Write scope; up to 2 GB and 1,000,000 rows a request.
B=https://api.skimmerdb.com/v1
curl -s "$B/namespaces/support/import?format=csv&id=ticket&text=subject,body" \
-H "Authorization: Bearer $KEY" --data-binary @tickets.csvimport os, requests
with open("tickets.csv", "rb") as f:
r = requests.post(
"https://api.skimmerdb.com/v1/namespaces/support/import",
params={"format": "csv", "id": "ticket", "text": "subject,body"},
headers={"Authorization": f"Bearer {os.environ['SKIMMERDB_KEY']}"},
data=f,
)
print(r.json())import { readFile } from "node:fs/promises";
const url = "https://api.skimmerdb.com/v1/namespaces/support/import?format=csv&id=ticket&text=subject,body";
const res = await fetch(url, {
method: "POST",
headers: { Authorization: `Bearer ${process.env.SKIMMERDB_KEY}` },
body: await readFile("tickets.csv"),
});
console.log(await res.json());The answer:
{"written": 52000, "deleted": 0, "version": 4, "stored_bytes": 18412203, "rows": 52000, "took_ms": 1840, "created": false}An import to a name that doesn't exist makes the namespace (text only; "created": true).
Parameters
| Parameter | Meaning |
|---|---|
format | csv (the default), ndjson or parquet. |
id | The column whose values are the documents' ids. Without it, ids are made from the import and the row number. A later row with the same id wins. |
text | The columns that make each document's searchable text, as name: value lines (note: refund requested). None: rows are found by SQL and filters only. |
schema | CSV only: a CREATE TABLE statement (URL-encoded) giving the columns and their types, for files without a header row. |
Every column is metadata, whether or not it's in text: filters, SQL and search hits all see
it.
Tip Pick a natural key for id (an order number, a ticket number) and re-importing the
same file replaces rather than duplicates. See Data model.
Types
| Format | How columns are typed |
|---|---|
| CSV | A header row is required (or schema=). Kinds are inferred: integers and true/false are int, other numbers float, YYYY-MM-DD a date, YYYY-MM-DD HH:MM:SS a time, anything else text. |
| NDJSON | One JSON object a line, its keys the columns, typed like meta (integers and booleans int, other numbers float, strings text). |
| Parquet | Keeps its types. Lists and maps aren't supported yet. |
date and time columns compare with strings like '2026-10-01' in SQL and work with
DATE_TRUNC. See Analytics with SQL.
NULL versus empty
NDJSON and Parquet keep NULL separate from empty strings. In CSV, an empty field (quoted or
not) means a missing value. Use NDJSON or Parquet when the difference between NULL and empty
text matters.
NDJSON
One flat object a line:
{"event_id": "e-1", "ts": 1760572937, "agent": "agent-1", "tool": "search", "ok": false, "message": "error E504 upstream timeout"}
{"event_id": "e-2", "ts": 1760572951, "agent": "agent-1", "tool": "sql_query", "ok": true, "message": "ok"}Import it:
curl -s "$B/namespaces/events/import?format=ndjson&id=event_id&text=message" \
-H "Authorization: Bearer $KEY" --data-binary @events.ndjsonCSV without a header
Give the columns with schema=, a URL-encoded CREATE TABLE:
SCHEMA=$(jq -rn --arg s "CREATE TABLE orders (order_id TEXT, placed_at TIMESTAMP, country TEXT, amount DOUBLE)" '$s|@uri')
curl -s "$B/namespaces/orders/import?format=csv&id=order_id&schema=$SCHEMA" \
-H "Authorization: Bearer $KEY" --data-binary @orders-noheader.csvTypes: TEXT (or VARCHAR, STRING), DOUBLE (or FLOAT, REAL), DATE, TIMESTAMP
(or DATETIME), and integer types (INT, BIGINT, ...).
Speed and limits
Measured on 8 vCPUs: CSV about 440,000 rows a second, Parquet about 500,000, NDJSON about 175,000. A request takes up to 2 GB and 1,000,000 rows; split bigger files into several imports. Each import is one atomic write: its rows become visible together, after which they compact with the rest of the namespace like any write.
An import is metered as a write of that many documents and the file's bytes. See Limits and plans.