Append rows over HTTP
On this page
Related contents
Append writes rows directly to an existing table. Send one JSON object per line, with fields named after the table's columns. Use this path when your producer can supply the destination types; Append does not run a ScopeQL transformation or create missing tables.
For a continuous producer, use the append stream examples for JavaScript, Go, or Rust for batching and backpressure. The input types and delivery outcomes below also apply to those SDKs.
Before you start
Set SCOPEDB_ENDPOINT and SCOPEDB_API_KEY using the application connection guide. Create the events table from the quickstart in the scopedb database and public schema. Its columns are time, service, name, message, and var.
If you use another table, read its Catalog definition and adapt the row before sending it.
Send a batch
POST /v1/catalog/databases/{database}/schemas/{schema}/tables/{table}/ingest
Content-Type: application/x-ndjson
Authorization: Bearer <api-key>The request body is NDJSON itself, without a surrounding array or a rows wrapper. This example appends one event:
curl --fail-with-body --silent --show-error \
"$SCOPEDB_ENDPOINT/v1/catalog/databases/scopedb/schemas/public/tables/events/ingest" \
-H "Authorization: Bearer $SCOPEDB_API_KEY" \
-H "Content-Type: application/x-ndjson" \
--data-binary @- <<'NDJSON'
{"time":"2026-10-23T09:00:00Z","service":"checkout","name":"payment_failed","message":"Credit card gateway timed out","var":{"region":"apac","gateway":"credit_card"}}
NDJSONA successful response is:
{
"status": "committed",
"num_rows_inserted": 1
}Each successful run appends another row. Append does not deduplicate repeated requests.
To send a file, replace the heredoc with --data-binary @events.ndjson. The endpoint accepts gzip-compressed bodies with Content-Encoding: gzip. Split larger inputs into batches: the HTTP endpoint limits uncompressed bodies to 16 MiB and NDJSON requests to 200,000 rows. The documented SDKs impose a smaller 8 MiB request cap; use that cap when preparing batches shared with an SDK.
Verify the write
Submit this query through the statement API or Console:
FROM events
WHERE time = '2026-10-23T09:00:00Z'::timestamp
AND service = 'checkout'
AND name = 'payment_failed'
SELECT time, name, message, var['gateway']::string AS gateway
LIMIT 10The row includes payment_failed, Credit card gateway timed out, and credit_card. A completed append is available to subsequent queries.
JSON input types
Append validates the input against the destination schema. Convert fields in the producer before sending them. If the transformation needs ScopeQL, follow the separate ingestion guide.
| Column type | JSON input |
|---|---|
int | Integer number token in the signed 64-bit range, such as 42. Quoted numbers, decimal tokens such as 42.0, and exponent tokens such as 42e0 are rejected. |
uint | Integer number token from zero through 9223372036854775807. This input limit is narrower than the unsigned 64-bit query-result range. |
float | Finite number. Integer tokens must be exactly representable as floats; overflow and nonzero values that underflow to zero are rejected. |
boolean | JSON boolean, true or false. |
string | JSON string; other types are not converted to text. |
timestamp | RFC 3339 string with T and an explicit Z or ±HH:MM offset, such as "2026-10-23T09:00:00Z". Date-only strings, timezone-free strings, and numeric epochs are rejected. |
interval | Fixed-duration ISO 8601 string using hours, minutes, and seconds, such as "PT1H2M3.000000004S". Calendar years, months, and days are not accepted. |
binary | Canonical padded standard Base64 string, such as "aGVsbG8=". |
object | JSON object, not serialized JSON inside a string. |
array | JSON array; elements may have different types. |
any | Any JSON value, preserving its JSON kind. A timestamp-looking string stays a string. |
Timestamps and intervals must fit signed 64-bit nanoseconds, with up to nine fractional second digits. The signed 64-bit integer-token limit also applies inside structured values. Missing ordinary columns and explicit null become SQL NULL; nested nulls remain JSON nulls. Omit computed columns.
Unknown fields, duplicate object keys, and type mismatches reject the request. Other requests in a batch-producing application may already have committed.
Delivery and retries
Check the HTTP status together with the delivery outcome. Success returns status at the top level; structured errors return it inside details.
| Outcome | Meaning | Next action |
|---|---|---|
committed | ScopeDB acknowledged the write. | Record num_rows_inserted; do not replay the batch. |
rejected | The request did not commit. | Fix schema or input errors. Retry temporary availability rejections with backoff. |
unknown | The request may have committed. | Reconcile stored data before replaying; duplicates are possible. |
A schema failure returns HTTP 422 with a body such as:
{
"code": "invalid_schema",
"message": "input does not match the table schema",
"details": {
"status": "rejected",
"row_errors": [
{"row_index": 0, "column": "time", "message": "invalid timestamp"}
],
"row_errors_truncated": false
}
}The messages above are illustrative. Use code and details.status for decisions. row_index is zero-based within the request, column identifies the failing field, and row_errors_truncated tells you that more diagnostics were omitted. Fix the whole batch before resubmitting it.
A timeout, connection loss, or response without a trustworthy outcome must be treated as unknown after sending. An HTTP 503 alone cannot tell you whether the request was rejected or may have committed. Keep a durable copy of events when loss is unacceptable, and use stable event identifiers and application-level deduplication if you allow replay. See HTTP errors and retries.