---
title: "Ingest JSON/NDJSON data into Ducklake"
method: POST
path: "/v2/stream"
tags: ["v2", "Stream"]
---

# Ingest JSON/NDJSON data into Ducklake

`POST /v2/stream`

Ingest JSON/NDJSON data into Ducklake tables.

This endpoint accepts:
- JSON objects or arrays (Content-Type: application/json)
- NDJSON streams (Content-Type: application/x-ndjson)
- Optional compression with gzip or zstd (Content-Encoding: gzip|zstd)

Two modes are supported via `config.mode`:
- `append` (default): inserts rows into the target table.
- `merge`: atomic delete-then-insert keyed on `config.primary_key`
  (exactly one field for v2). Existing rows whose PK matches an
  incoming row are deleted; all incoming rows are then inserted.
  Within-batch duplicate PKs are rejected before any duckserver call.

Partitioning uses the table's existing partition spec.

Type-coercion notes:
- Values are inlined as SQL literals and `CAST(... AS <target_type>)`
  to the target column's type from DESCRIBE, so mixed-type input
  (e.g. an int and a string in the same VARCHAR column) inserts cleanly.
- Python `bool` values land via DuckDB's CAST semantics: `TRUE`/`FALSE`
  for BOOLEAN columns, lowercase `'true'`/`'false'` for VARCHAR columns.
  Send a string if you need case preservation.

Authentication is required via API key or JWT token in Authorization header.

## Request body

- StreamRequest — Request model for stream ingestion
  - `data` union — Data to ingest (object or array; omitted for NDJSON)
    - object
    - object[]
  - `config` StreamConfig, required — Configuration for stream ingestion
    - `table` string, required — Target table name (e.g., 'bronze.events')
    - `many` boolean, nullable — Whether data is an array; inferred if not provided
    - `mode` 'append' | 'merge' — Ingestion mode: 'append' inserts rows; 'merge' deletes-then-inserts keyed on primary_key
    - `primary_key` string[], nullable — Required when mode='merge'; exactly one field for v2 (composite keys planned later)
    - `wait` boolean — Wait for commit and return snapshot_id
    - `tags` object, nullable — Optional tags for metadata
    - `trace` object, nullable — Optional trace information

## Response `200`

Successful Response

- StreamResponse — Response model for stream ingestion
  - `success` boolean, required — Whether the ingestion was successful
  - `request_id` string, required — Unique request identifier
  - `stream_id` string, required — Unique stream identifier
  - `table` string, required — Fully qualified table name
  - `accepted` integer, required — Number of rows parsed and accepted
  - `successful_rows` integer, required — Number of rows successfully written
  - `rejected_rows` integer, required — Number of rows rejected due to validation errors
  - `partitions` string[], required — Human-friendly partition summary
  - `snapshot_id` string, nullable — Iceberg snapshot ID (present when wait=true)
  - `warnings` string[] — Warning messages
  - `errors` string[] — Error messages

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/definite/apis/defapi.md) · [All operations](https://skmtc.dev/definite/apis/defapi/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/definite/defapi/revisions/3943d8deb3be/schema)
