Skip to guide

Build with your time-series data.

Create a database, connect your application, and query with SQL. Follow the workflow available in the current Chronous release.

Public pilot Updated

On this page

From first table to first query.

The database console is the starting point for application data. Sign in, choose an organization, then open Databases. If you are new, create an organization first.

Databases opens with a searchable list. Filter by status, choose 25 or 50 rows per page, then select Open database. Inside a database, choose a table in the left panel (or the table selector on mobile). Use All databases to return to the list.

  1. Create a database and table

    Give the database a name, then create a table called readings. Keep the required row_id string and time timestamp columns. Add sensor as a string and temperature as float64.

  2. Issue a database key

    In the database’s API keys panel, issue a key with read and write grants. Copy the secret when it is shown, then use it in the console connection. Ask an organization owner or admin if you cannot manage keys.

  3. Insert your first rows

    Open SQL editor and select Insert JSON. Paste the sample rows below and run the insert. The sample timestamps are fixed examples; use observation times from your application for new data.

  4. Read the result

    Switch the editor to SELECT and run the query in Query with SQL. A successful insert returns a commit receipt; the SELECT result lets you check the stored values.

Sample rows for Insert JSON
[
  {
    "row_id": "reading-001",
    "time": "2026-10-26T00:00:00.000Z",
    "sensor": "greenhouse",
    "temperature": 25.31
  },
  {
    "row_id": "reading-002",
    "time": "2026-10-26T00:01:00.000Z",
    "sensor": "greenhouse",
    "temperature": 25.48
  }
]

Use UTC ISO 8601 timestamps. Encode int64, uint64, and decimal128 values as strings to preserve precision. The sample uses float64 for temperature.

Choose a database connection.

Open your database’s Connections panel. HTTPS is available for inserts, SQL, and supported mutations. MQTT is coming soon as another protocol for the same database and tables. Creating a device or datastream is not required.

HTTPS · Available

Use the exact database URL from Connections and a key scoped to that database. Replace the placeholders with your own database ID and key. Keep the key in your server-side environment.

Secret keys are shown once. If you lose one, create or rotate a key, update your application, then revoke the old key when it is no longer used. Viewers should request a replacement from an administrator.

Check a database connection
export CHRONOUS_DATABASE_URL="https://kunanta-telemetry.yoga-pratama-pangestu.workers.dev/v1/databases/<database-id>"
export CHRONOUS_DATABASE_KEY="<database-key>"

curl "$CHRONOUS_DATABASE_URL/connection" \
  -H "Authorization: Bearer $CHRONOUS_DATABASE_KEY"

To query from a server-side JavaScript application, copy the table ID from the selected table and provide it with the SQL statement:

Query from JavaScript
const response = await fetch(
  process.env.CHRONOUS_DATABASE_URL + "/query",
  {
    method: "POST",
    headers: {
      authorization: "Bearer " + process.env.CHRONOUS_DATABASE_KEY,
      "content-type": "application/json",
    },
    body: JSON.stringify({
      tableId: "<table-id>",
      sql: 'SELECT * FROM "readings" LIMIT 100',
      parameters: [],
    }),
  },
);
const result = await response.json();
if (!response.ok) throw new Error(JSON.stringify(result.error));
console.log(result);

Console login and database keys have different jobs. Your account selects and manages resources; a database key authorizes data operations. The current connection interface is HTTPS, rather than PostgreSQL or MySQL wire protocol.

MQTT database connections Coming soon

MQTT is planned as a connection protocol for database tables, alongside HTTPS. The target is the same schema validation, database permissions, and durable commit receipts.

This release does not provide a database MQTT endpoint, topic contract, or MQTT credential setup. Use HTTPS from Database → Connections today.

Query with familiar SQL.

The editor has three modes. Choose a mode, write the request in the editor, then select Run or Insert rows. Ctrl/Cmd + Enter runs the current mode. Filters, grouping, and aggregate functions are written inside a SELECT statement.

Choose an editor mode
ModeUse it for
SELECTRead, filter, group, and sort one table. Requires a read key grant.
UPDATE / DELETEChange values or logically delete rows. Requires write and an explicit WHERE clause.
Insert JSONAdd rows as a JSON array. Requires write. SQL INSERT INTO is not supported.

Choose the table in the console and connect a database key first. Replace readings and column names in these examples with your actual schema. The quickstart table has row_id, time, sensor (string), and temperature (float64).

Read columns and sort rows

Read the latest rows
SELECT "time", "sensor", "temperature"
FROM "readings"
ORDER BY "time" DESC, "row_id"
LIMIT 100

Use SELECT * for all columns or list the columns you need. Column and table aliases use AS. Double quotes preserve identifier spelling; unquoted names are normalized to lowercase. Use single quotes for string values. The table name must match the selected table; cross-database names are unavailable.

Filter before aggregating

Filter sensor readings
SELECT "row_id", "time", "temperature"
FROM "readings"
WHERE "sensor" IN ('greenhouse', 'nursery')
  AND "temperature" BETWEEN 20 AND 30
  AND "temperature" IS NOT NULL
ORDER BY "temperature" DESC NULLS LAST, "row_id"
LIMIT 50 OFFSET 0

Supported SELECT syntax
SyntaxBehavior
WHERECompare with =, <> (or !=), >, <, >=, and <=. Combine conditions with AND, OR, NOT, and parentheses.
BETWEEN / INBETWEEN includes both endpoints. IN accepts up to 64 values. NOT BETWEEN and NOT IN are also accepted.
IS NULL / IS NOT NULLTest missing values. column = NULL does not select null values. Zero, false, an empty string, and null are different values.
GROUP BY / HAVINGGroup rows, then filter aggregate results with HAVING. Up to eight grouping expressions.
ORDER BYUp to eight expressions, with ASC/DESC and NULLS FIRST/NULLS LAST. Include a unique tie-breaker such as row_id for stable ordering.
LIMIT / OFFSETLIMIT up to 1,000; OFFSET up to 2,400. Without ORDER BY, row order is not guaranteed. Large OFFSET values still consume scan budget.

Aggregate by sensor

Aggregate by sensor
SELECT "sensor", COUNT(*) AS "readings",
       AVG("temperature") AS "average_temperature"
FROM "readings"
GROUP BY "sensor"
HAVING COUNT(*) > 0
ORDER BY "sensor"
LIMIT 100

Aggregate functions and precision
FunctionSupported input and result
COUNT(*) / COUNT(column)Count all rows or only non-null column values. Returns int64, encoded as a JSON string.
MIN(column) / MAX(column)Return the smallest/largest non-null value, preserving the column type and decimal precision/scale.
SUM(column)Numeric columns only. int32 → int64; int64/uint64 → decimal128(38,0); decimal128(p,s) → decimal128(38,s); float64 → float64. Integer/decimal results are exact strings; overflow fails the query.
AVG(column)float64 only, returning float64. Integer and decimal averages are not supported. Float results follow floating point arithmetic.

COUNT returns zero for empty input. SUM, AVG, MIN, and MAX ignore nulls and return null for empty or all-null input. Grouping only returns groups present in the selected data; missing groups are not filled with zero. Aggregate arguments are a single column, except COUNT(*).

Group timestamps into UTC buckets

Hourly temperature buckets
SELECT DATE_TRUNC('hour', "time") AS "bucket_start",
       COUNT(*) AS "readings",
       AVG("temperature") AS "average_temperature"
FROM "readings"
GROUP BY DATE_TRUNC('hour', "time")
ORDER BY "bucket_start"
LIMIT 100

DATE_TRUNC accepts 'minute', 'hour', or 'day' and a timestamp_ms column. Buckets use UTC, with no local timezone conversion, month/week buckets, gap filling, or resampling. Add a time filter for larger tables.

Count true boolean values

If your table has a boolean column named healthy, this form counts true values while excluding false and null:

Count healthy readings
SELECT COUNT(*) AS "total",
       COUNT(*) FILTER (WHERE "healthy" = TRUE) AS "healthy_count"
FROM "readings"

FILTER is restricted to COUNT with boolean_column = TRUE. Other FILTER predicates and FILTER on SUM/AVG/MIN/MAX are not supported.

Use typed parameters.

Place $1, $2, and so on in SQL, then open JSON parameters below the editor. Paste a JSON array in the same order. Values are bound separately from the SQL text.

Query a UTC time range
SELECT "row_id", "time", "temperature"
FROM "readings"
WHERE "time" >= $1 AND "time" < $2
  AND "sensor" = $3
ORDER BY "time", "row_id"
LIMIT 100

Parameters for the UTC range
[
  {
    "type": "timestamp_ms",
    "value": "2026-10-26T00:00:00.000Z"
  },
  {
    "type": "timestamp_ms",
    "value": "2026-10-27T00:00:00.000Z"
  },
  {
    "type": "string",
    "value": "greenhouse"
  }
]

This range includes its start and excludes its end. Parameters must be consecutive from $1 to $16, with every supplied position used. Named parameters, $01, missing values, and extra values are rejected. Parameters represent values, not table or column names. Use [] when there are no placeholders.

Parameter and row value encoding
TypeJSON value
int32Integer number, for example 123.
int64 / uint64Integer string, for example "9223372036854775807". uint64 cannot be negative.
float64Finite JSON number, for example 25.31. NaN and Infinity are invalid.
decimal128Fixed-scale string, for example "1234.5600". Parameters also need precision and scale matching the column.
boolean / stringJSON true/false or a string such as "greenhouse".
timestamp_msUTC ISO 8601 string with millisecond precision, for example "2026-10-26T00:00:00.000Z".
nullUse "value": null with the actual parameter type. Decimal nulls still require precision/scale. Use IS NULL in SQL to match null values.
Decimal parameter shape
[
  {
    "type": "decimal128",
    "precision": 20,
    "scale": 4,
    "value": "1234.5600"
  }
]

Use parameters of the matching type for int64, uint64, decimal, and timestamp comparisons, including HAVING over exact aggregates. Decimal precision and scale must match the value being compared. Large numbers must not pass through JavaScript Number. Query results also encode int64, uint64, and decimal values as strings.

Insert rows with JSON.

Select Insert JSON, connect a key with write, and paste an array of 1–600 row objects. This mode takes the array directly; the console supplies the schema revision and request identity. Use the selected table’s current schema.

Insert a new reading
[
  {
    "row_id": "reading-003",
    "time": "2026-10-26T00:02:00.000Z",
    "sensor": "greenhouse",
    "temperature": 25.62
  }
]

Every row needs a unique row_id string and a time timestamp. Supply all required columns; omitted nullable columns become null. Row values use the encoding above without parameter wrappers. Use your actual observation times for new data; these timestamps are fixed examples.

Insert behavior
SituationResult
New row_idInserts a row and returns a commit receipt with inserted and duplicate counts.
Same row_id and identical contentRecognized as an exact duplicate; no additional row is inserted.
Same row_id with different contentROW_CONFLICT. Use UPDATE to correct an existing row.
Invalid row or schema conflictThe entire batch fails. There is no partial successful insert.
Lost responseRetry the original operation with the same request identity and payload. The durable receipt prevents the same request from being executed twice.

The default batch budget is 600 rows and 256 KiB of canonical row data, with up to 32 KiB per row. A batch can hit the byte limit before the row limit. Check the connection limits for your database.

Update or delete matching rows.

Choose UPDATE / DELETE and connect a key with write. Run one statement at a time. Both operations require a WHERE clause and target the selected table. First use SELECT with the same filter to review which rows will change.

Update a reading
UPDATE "readings"
SET "temperature" = $1
WHERE "row_id" = $2

Parameters for the update
[
  {
    "type": "float64",
    "value": 25.75
  },
  {
    "type": "string",
    "value": "reading-001"
  }
]

UPDATE preserves columns that are not assigned. Assignments support scalar values, typed parameters, and compatible column references. You can assign multiple columns separated by commas. General arithmetic such as temperature + 1 is unavailable. row_id and time are immutable.

Clear a nullable temperature
UPDATE "readings"
SET "temperature" = NULL
WHERE "row_id" = 'reading-002'

SET NULL only works for a nullable column. Use an empty parameter array for this example.

Delete a reading
DELETE FROM "readings"
WHERE "row_id" = $1

Parameters for the delete
[
  {
    "type": "string",
    "value": "reading-003"
  }
]

DELETE hides matching rows from later reads and records a tombstone; it does not immediately erase stored objects. A deleted row_id cannot be reused. Successful changes return a receipt with affected row count, row revisions, schema revision, and commit token.

Mutations are atomic and limited to 600 affected rows per statement, subject to the scan and resource budgets. A limit or conflict fails the operation rather than silently changing only part of the selection. Joins, extra FROM tables, RETURNING, and multiple statements are unavailable.

Use the same operations over HTTPS

Endpoints relative to your database URL
EndpointRequest body and grant
POST /query{tableId, sql, parameters}; read grant. Optional timeoutMs and atLeastCommit.
POST /tables/{tableId}/rows{issuedAt, schemaRevision, rows}; write grant.
POST /tables/{tableId}/mutations{issuedAt, schemaRevision, operation: {op: "sql", sql, parameters}}; write grant.

Use the database URL from Connections, bearer authorization, and JSON content type. For writes, generate issuedAt at submission time as UTC ISO 8601 with milliseconds and an Idempotency-Key of <epochMs>.<nonce>. The epoch must equal issuedAt and the nonce is 16 random bytes encoded as unpadded base64url. Retain the original body and key for retries; the retry window is seven days. SELECT does not require an idempotency key.

Limits, results, and errors.

Each query reads one committed snapshot of one table. Concurrent writes appear in a later query. Results include column types, rows, scan counts, schema revision, and a snapshot token. A successful query returns complete results within the requested SQL and budgets; resource failures return an error without partial results.

Default SQL execution budgets
BudgetDefault maximum
Statement and parametersOne statement; 8 KiB SQL; 16 parameters; 32 KiB query request body.
Projection and grouping32 output columns; eight GROUP BY and eight ORDER BY expressions; 64 IN values.
Scan2,400 rows / 8 MiB. Narrow time filters can reduce selected data. LIMIT restricts the output, not the work needed to compute it.
Result1,000 rows / 1 MiB serialized response, including metadata.
Execution15 seconds by default and at most; API timeoutMs may shorten this. DataFusion memory pool: 64 MiB, without disk spill.

These are execution budgets, separate from storage capacity and plan allowances. Database connection metadata reports the active limits. atLeastCommit on the API accepts a receipt token from the same table as a minimum visibility check; it does not select a historical snapshot. The console carries the latest successful write token into subsequent SELECT requests.

SQL outside the supported subset

JOIN, subqueries, WITH/CTE, UNION/set operations, DISTINCT, window/OVER functions, nested aggregates, CASE, CAST, LIKE/ILIKE, general arithmetic, and user-defined functions are unavailable. FILTER only supports the COUNT form above. SQL INSERT, CREATE, ALTER, DROP, EXPLAIN, COPY, and SET are unavailable. Use the console’s table/schema controls for supported schema changes. Queries cannot access files, URLs, external tables, or information_schema.

Common errors and next steps
ErrorWhat to check
UNSUPPORTED_SQLUse one supported statement for one table. Check functions, clauses, and parameter types.
INVALID_REQUESTCheck JSON, placeholders, timestamp formatting, column types, and decimal precision/scale.
UNAUTHENTICATED / FORBIDDENReconnect a valid database key with the required read or write grant. Admin does not imply either grant.
ROW_CONFLICT / REVISION_CONFLICTCheck existing row content and refresh the table/schema revision before submitting a corrected operation.
RESOURCE_LIMIT / QUERY_TIMEOUTNarrow the time range, reduce selected data or batch size, and inspect the active connection budgets.
NUMERIC_OVERFLOWAn exact sum or numeric result exceeded its type, or a float result became non-finite. Narrow the input or review the stored values.
COMMIT_NOT_VISIBLECheck that atLeastCommit came from this table and is visible in the current database instance.
IDEMPOTENCY_CONFLICTThe request identity was reused with a different body. Retry the original body, or create a new identity for an intentionally different write.

Keep access and schema under control.

Key grants
read allows reads and SELECT. write allows row writes and supported mutations. admin is separate and does not automatically include read or write.
Key lifecycle
Keys can be issued, rotated, and revoked. A secret is shown once. Save it when issued; a replayed issuance response does not reveal it again.
Table changes
Add nullable columns, rename resources, or use logical drop from the console. Review the confirmation carefully; a logical drop is not an immediate physical deletion of stored objects.
Console connections
The key stays in page memory. Refreshing or changing the database, organization, or session clears the connection and previous results. Connect again before running a query.
Metadata
The metadata panel exposes snapshot and file information with a read key. Use it to inspect the selected database rather than treating it as a live monitoring dashboard.

Understand your database service.

Open a database’s Service tab to review its configured compute routing, storage profile and available controls. Compute capacity and start/stop are operator managed. Runtime status: Not reported means the console has no live process observation.

Backup and recovery are operator managed; this console does not create or restore backups. Ask your administrator about recovery options. Retention controls appear only when the service enables them.

Usage separates sampled storage sizes from activity receipts. Partial observations do not establish zero consumption or a storage allowance. The last activity receipt is not the storage sample time.

What is in the current release?

Available in the console
Organizations and team access; databases and typed tables; database keys and HTTPS connections; Insert JSON; SELECT, controlled UPDATE/DELETE; schema changes, metadata inspection and read-only Service configuration.
API-only, with explicit activation
Semantic definitions, query jobs, evidence, and usage receipts are available through the database API for enabled contracts. Dedicated console panels are not included yet.
MQTT · Coming soon
Database Connections shows the planned MQTT protocol. It is not enabled in this release. Use HTTPS to connect applications to database tables.
Still being built
MCP integrations, agent workflows, automation, and agentic analysis dashboards are planned. The current release provides the time-series data foundation.

This is a public pilot. Validate your workload and recovery needs before relying on it for critical data. Usage views and API usage receipts are not invoices.

When something needs a second look.

401 or 403
Check the selected organization, database, key status, and required grant. A database key cannot replace your console session. Reconnect after a refresh or scope change.
No rows in a result
Check the table, column names, time filter, and timestamp type. Check that data was inserted into the selected database table.
A write response was lost
Retry the original operation with the same request identity and payload. Do not create a new operation for the same write merely because its acknowledgement was lost.
The database is temporarily unavailable
The pilot can take time to wake from idle. Retry reads after a short wait. For writes, retain the original operation identity and use its retry action.
MQTT database connection
Coming soon. Use HTTPS for this release. Database MQTT setup is not enabled yet.
Continue in the database console