Append rows over HTTP

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"}}
NDJSON

A 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 10

The 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 typeJSON input
intInteger 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.
uintInteger number token from zero through 9223372036854775807. This input limit is narrower than the unsigned 64-bit query-result range.
floatFinite number. Integer tokens must be exactly representable as floats; overflow and nonzero values that underflow to zero are rejected.
booleanJSON boolean, true or false.
stringJSON string; other types are not converted to text.
timestampRFC 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.
intervalFixed-duration ISO 8601 string using hours, minutes, and seconds, such as "PT1H2M3.000000004S". Calendar years, months, and days are not accepted.
binaryCanonical padded standard Base64 string, such as "aGVsbG8=".
objectJSON object, not serialized JSON inside a string.
arrayJSON array; elements may have different types.
anyAny 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.

OutcomeMeaningNext action
committedScopeDB acknowledged the write.Record num_rows_inserted; do not replay the batch.
rejectedThe request did not commit.Fix schema or input errors. Retry temporary availability rejections with backoff.
unknownThe 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.