API Documentation
Overview
Observability API for ingesting and querying telemetry data. This API allows you to ingest application metrics, OpenTelemetry logs and traces, then query the collected data using SQL.
Server URL: https://<SERVER_URL> — Search (SQL query)
Engine URL: https://<ENGINE_URL> — Ingestion
These can differ by deployment. Use the Server URL for SQL query requests and the Engine URL for all ingestion endpoints.
Authentication
Seeker (SQL query) — use a Bearer token against the Server URL:
Authorization: Bearer <YOUR_API_KEY>
POST /api/v1/sql accepts the workspace API_KEY as a Bearer token.
Engine (ingestion) — use Basic auth against the Engine URL:
Authorization: Basic <YOUR_API_KEY>
Endpoints
SQL Query
Execute a SQL query against ingested telemetry data.
Request
POST /api/v1/sql
Query Parameters
| Name | Type | Required | Description |
|---|---|---|---|
type | string | No | Dataset type: logs (default), traces, or metrics |
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
query | string | Yes | SQL query to execute (max 5000 characters) |
from | integer | Yes | Start timestamp (Unix seconds). |
to | integer | Yes | End timestamp (Unix seconds). |
The dataset name is taken from the SQL FROM clause. When authenticated with the API_KEY, access is unrestricted (admin).
Recommendation: Always include a
LIMITclause in your SQL query. Avoid querying more than 7 days in a single request — large time ranges can trigger expensive unbounded scans. For longer windows, split the query into smaller time-range partitions (for example, daily or weekly) and combine the results.
Example Request
curl -X POST "https://<SERVER_URL>/api/v1/sql?type=logs" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "SELECT * FROM application_logs WHERE level = '\''ERROR'\'' LIMIT 100",
"from": 1705756200,
"to": 1705759800
}'
Response - Success
{
"matches": [
{
"timestamp": "2024-01-20T10:30:00Z",
"level": "ERROR",
"message": "Connection failed",
"service": "payment-api"
},
{
"timestamp": "2024-01-20T10:31:00Z",
"level": "ERROR",
"message": "Database timeout",
"service": "payment-api"
}
],
"total": 156,
"offset": 0,
"limit": 100,
"scanSize": 1048576,
"trace_id": "",
"partial": false
}
Response Fields
| Field | Type | Description |
|---|---|---|
matches | array | Matching result rows |
total | number | Total number of results found |
offset | number | Offset used in the query |
limit | number | Page size used in the query |
scanSize | number | Bytes scanned to produce the result |
trace_id | string | Query trace identifier (when available) |
partial | boolean | Whether the response is a partial result set |
Error Response Examples
Invalid or unrecognized API token:
{
"error": "token not found"
}
Malformed request body:
{
"error": "Invalid request body"
}
Empty query:
{
"error": "query cannot be empty"
}
No extractable table in the SQL FROM clause:
{
"error": "no table found in FROM clause"
}
Query longer than 5000 characters:
{
"error": "query too long"
}
Invalid type query parameter:
{
"error": "invalid dataset type"
}
Invalid SQL or engine errors:
{
"error": "internal server error"
}
Dataset access denied:
{
"error": "user not allowed access to dataset: application_logs"
}
Status Codes
| Code | Description |
|---|---|
| 200 | Query successful |
| 400 | Invalid request body, empty query, missing FROM table, query too long, or bad type |
| 401 | Unauthorized — invalid token (token not found) |
| 403 | Forbidden — dataset access denied |
| 500 | Engine/runtime query failure (internal server error) |
Ingest - JSON
Ingest JSON data to a stream.
Request
POST /api/{org_id}/{stream_name}/_json_evolving
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
org_id | string | Yes | Organization ID |
stream_name | string | Yes | Stream name to ingest into |
Request Body
JSON object or array of JSON objects. Schema is inferred and evolved based on incoming data.
Use _timestamp for the event time. It must be a Unix timestamp in microseconds. If _timestamp is omitted or not in Unix microseconds, the CtrlB engine sets it to the time of ingestion.
Example Request
curl -X POST "https://<ENGINE_URL>/api/default/application_logs/_json_evolving" \
-H "Authorization: Basic YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"_timestamp": 1705746600000000,
"level": "ERROR",
"message": "Database connection failed",
"service": "payment-api",
"trace_id": "abc123def456"
}'
Response - Success
OK
Error Response Examples
Invalid JSON:
{
"error": "Failed to parse JSON",
"code": "BAD_REQUEST",
"details": "Invalid JSON at line 1, column 15"
}
Organization not found:
{
"error": "Organization not found",
"code": "NOT_FOUND",
"details": "Organization 'default' does not exist"
}
Status Codes
| Code | Description |
|---|---|
| 200 | Data ingested successfully |
| 400 | Invalid JSON format |
| 401 | Unauthorized |
| 403 | Forbidden - no write permission |
| 404 | Organization or stream not found |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Ingest - OpenTelemetry Logs
Ingest OpenTelemetry logs.
Request
POST /api/{org_id}/{stream_name}/_otel/v1/logs
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
org_id | string | Yes | Organization ID |
stream_name | string | Yes | Stream name to ingest into |
Request Body
OpenTelemetry Protocol (OTLP) logs in JSON or protobuf format. Payload should conform to the OpenTelemetry specification.
Example Request
curl -X POST "https://<ENGINE_URL>/api/default/otel_logs/_otel/v1/logs" \
-H "Authorization: Basic YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resourceLogs": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": { "stringValue": "payment-service" }
}
]
},
"scopeLogs": [
{
"scope": {
"name": "my-instrumentation"
},
"logRecords": [
{
"timestamp": 1642598400000000000,
"severityNumber": 13,
"severityText": "ERROR",
"body": { "stringValue": "Payment processing failed" },
"attributes": [
{
"key": "transaction_id",
"value": { "stringValue": "txn_12345" }
}
]
}
]
}
]
}
]
}'
Response - Success
Plain text response:
OK
Error Response Examples
Invalid JSON format:
{
"error": "Failed to decode OTel JSON",
"code": "BAD_REQUEST",
"details": "Invalid field value in resourceLogs[0]"
}
Invalid protobuf format:
{
"error": "Failed to decode OTel protobuf",
"code": "BAD_REQUEST",
"details": "Protobuf deserialization error at offset 42"
}
Status Codes
| Code | Description |
|---|---|
| 200 | Logs processed |
| 400 | Invalid OTEL format (JSON/protobuf decode error) |
| 401 | Unauthorized |
| 403 | Forbidden - no write permission |
| 404 | Organization or stream not found |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
Ingest - OpenTelemetry Traces
Ingest OpenTelemetry traces.
Request
POST /api/{org_id}/v1/traces
Path Parameters
| Name | Type | Required | Description |
|---|---|---|---|
org_id | string | Yes | Organization ID |
Request Body
OpenTelemetry Protocol (OTLP) traces in JSON or protobuf format. Payload should conform to the OpenTelemetry specification.
Example Request
curl -X POST "https://<ENGINE_URL>/api/default/v1/traces" \
-H "Authorization: Basic YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"resourceSpans": [
{
"resource": {
"attributes": [
{
"key": "service.name",
"value": { "stringValue": "api-server" }
}
]
},
"scopeSpans": [
{
"scope": {
"name": "my-instrumentation"
},
"spans": [
{
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"spanId": "00f067aa0ba902b7",
"name": "payment_processing",
"startTimeUnixNano": 1642598400000000000,
"endTimeUnixNano": 1642598400000500000,
"status": { "code": 0 }
}
]
}
]
}
]
}'
Response - Success
Plain text response:
OK
Error Response Examples
Invalid JSON format:
{
"error": "Failed to decode OTel JSON",
"code": "BAD_REQUEST",
"details": "Invalid traceId format"
}
Invalid protobuf format:
{
"error": "Failed to decode OTel protobuf",
"code": "BAD_REQUEST",
"details": "Unexpected EOF while decoding spans"
}
Status Codes
| Code | Description |
|---|---|
| 200 | Traces processed |
| 400 | Invalid OTEL format (JSON/protobuf decode error) |
| 401 | Unauthorized |
| 403 | Forbidden - no write permission |
| 404 | Organization not found |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
cURL
# SQL Query
curl -X POST "https://<SERVER_URL>/api/v1/sql?type=logs" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "SELECT * FROM my_stream LIMIT 10", "from": 1705756200, "to": 1705759800}'
# Ingest JSON
curl -X POST "https://<ENGINE_URL>/api/default/my_stream/_json_evolving" \
-H "Authorization: Basic YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"_timestamp": 1705746600000000, "level": "ERROR", "message": "Failed"}'
# Ingest OTel Logs
curl -X POST "https://<ENGINE_URL>/api/default/otel_logs/_otel/v1/logs" \
-H "Authorization: Basic YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"resourceLogs": [...]}'
# Ingest OTel Traces
curl -X POST "https://<ENGINE_URL>/api/default/v1/traces" \
-H "Authorization: Basic YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"resourceSpans": [...]}'
Support
For questions or issues, contact: support@ctrlb.ai