Search
Endpoint: POST /api/{organization}/_search
Replace {stream} in the SQL examples below with your stream name (e.g. default).
Request
{
"query": {
"sql": "SELECT * FROM {stream} WHERE [condition]",
"start_time": 1674789786006000,
"end_time": 1674789786006000,
"from": 0,
"size": 0
},
"search_type": "ui",
"timeout": 0
}Description
| Field name | Data type | Default value | Description |
|---|---|---|---|
| query | object | - | query params |
| query.sql | string | - | use SQL query data, and filter data by start_time and end_time, and default order by _timestamp, you can use order by override order, and fetch offset limit by form and size |
| query.start_time | int64 | 0 | unit: microseconds, filter data by time range, you need always provide this value |
| query.end_time | int64 | 0 | unit: microseconds, filter data by time range, you need always provide this value |
| query.from | int64 | 0 | offset in SQL |
| query.size | int64 | 0 | limit in SQL |
| search_type | string | - | default is empty, support: ui, dashboards, reports, alerts |
| timeout | int | 0 | default value based on ZO_QUERY_TIMEOUT=600 |
| agent_options | object | - | options for agent/MCP clients |
| agent_options.mode | string | default | query execution mode: default (result cache path) or partition (streaming partition loop, see below) |
| agent_options.output_format | string | json | response format for hits: json, csv, or md_table |
Response
{
"took": 155,
"hits": [
{
"_p": "F",
"_timestamp": 1674213225158000,
"kubernetes": {
"annotations": {
"kubernetes": {
"io/psp": "eks.privileged"
}
},
"container_hash": "dkr.ecr.us-west-2.amazonaws.com/ziox@sha256:3dbbb0dc1eab2d5a3b3e4a75fd87d194e8095c92d7b2b62e7cdbd07020f54589",
"container_image": "dkr.ecr.us-west-2.amazonaws.com/ziox:v0.0.3",
"container_name": "ziox",
"docker_id": "eb0983bdb9ff9360d227e6a0b268fe3b24a0868c2c2d725a1516c11e88bf5789",
"host": "ip.us-east-2.compute.internal",
"labels": {
"app": "ziox",
"controller-revision-hash": "ziox-ingester-579b7767cf",
"name": "ziox-ingester",
"role": "ingester",
"statefulset": {
"kubernetes": {
"io/pod-name": "ziox-ingester-0"
}
}
},
"namespace_name": "ziox",
"pod_id": "35a0421f-9203-4d73-9663-9ff0ce26d409",
"pod_name": "ziox-ingester-0"
},
"log": "[2023-01-20T11:13:45Z INFO actix_web::middleware::logger] 10.2.80.192 \"POST /api/demo/_bulk HTTP/1.1\" 200 68 \"-\" \"go-resty/2.7.0 (https://github.com/go-resty/resty)\" 0.001074",
"stream": "stderr"
}
],
"total": 27179431,
"from": 0,
"size": 1,
"scan_size": 28943
}Response description:
Description
| Field name | Data type | Default value | Description |
|---|---|---|---|
| took | int64 | - | unit: milliseconds, query execute time |
| from | int64 | 0 | value from query.from |
| size | int64 | 0 | value from query.size |
| scan_size | int64 | 0 | unit: MB, it response the data size scale when execute the query. |
| hits | array | - | records for query, each record is a log row what you ingested. |
Agent options
output_format
Controls how hits are formatted in the response. Useful for agent/MCP clients that pay per token.
| Value | Description |
|---|---|
json | Default. Hits returned as a JSON array of objects. |
csv | Tabular hits as a compact CSV block (~40% fewer tokens than JSON). |
md_table | Tabular hits as a Markdown table. Best for small result sets. |
mode: partition
Set agent_options.mode to partition to run search through the streaming backend pipeline. The server scans time partitions one by one and stops early once enough rows are collected, which reduces data scanned for top-N queries. For aggregation queries, the server accumulates reusable cache partition by partition, so repeated queries against shifting time windows hit progressively warmer cache instead of invalidating a monolithic entry.
This mode is designed for agent/MCP clients that speak plain request-response and cannot consume SSE — it exposes the same partitioned execution that the streaming _search_stream endpoint uses, collected into a single response.
| Value | Description |
|---|---|
default | (default) Single search through the result cache path. Behavior is byte-for-byte unchanged. |
partition | Partitioned execution with per-partition early termination and streaming-agg caching, collected into one response. |
Key behaviors in partition mode:
- Early termination: scanning stops once the query has enough rows; useful for
SELECT ... LIMIT 100-style queries scanning a wide time range. - Progressive aggregation cache: aggregation queries (histogram, term counts, etc.) build up the streaming-agg cache partition by partition, so follow-up queries with similar time windows reuse cached work.
- Cancellation: if the HTTP client disconnects (drops the request), the per-partition loop stops — you are not billed for scanning partitions you never see.
- No SSE required: the response is a plain JSON
Responseidentical in shape todefaultmode, with all hit pages and scan counters folded together.
When to use partition mode:
- Your client cannot consume SSE (MCP tools, REST clients, curl-based scripts).
- You are scanning a large time range but only need a few rows (top-N or sample).
- You run exploratory queries that shift their time window with each call — the progressive cache rewards this pattern.
- Do NOT split the time range yourself and call
_searchin a loop. Let the server do it with one call inpartitionmode.
SQL Syntax
Please refer to PostgreSQL for SQL Syntax.
Notes:
- We have a build-in time field,
_timestampyou can use it to do time range filter. - Field name can not start with
@. - Field name can use double quote or without quote.
- Field integer value without quote.
- Field string value must use single quote.
Limitation
- You should give a time range for each query or it will scan all data, it is a very expensive operate.
Examples
Here list some common examples, if you want more example please create a issue tell us, we will add it.
Query latest 10 record logs
{
"query": {
"sql": "SELECT * FROM {stream}",
"start_time": 1674789786006000,
"end_time": 1674789786006000,
"from": 0,
"size": 10
}
}Query latest 10 record logs with filter
{
"query": {
"sql": "SELECT * FROM {stream} WHERE kubernetes.namespace_name='default' AND code=200 ",
"start_time": 1674789786006000,
"end_time": 1674789786006000,
"from": 0,
"size": 10
}
}Full text query
{
"query": {
"sql": "SELECT * FROM {stream} WHERE match_all('err') ",
"start_time": 1674789786006000,
"end_time": 1674789786006000,
"from": 0,
"size": 10
}
}Match on a field (log)
{
"query": {
"sql": "SELECT * FROM {stream} WHERE str_match(log, 'err') ",
"start_time": 1674789786006000,
"end_time": 1674789786006000,
"from": 0,
"size": 10
}
}Histogram aggregation (full mode)
{
"query": {
"sql": "SELECT histogram(_timestamp, '5 minute') AS key, COUNT(*) AS num FROM {stream} GROUP BY key ORDER BY key LIMIT 10 OFFSET 1",
"start_time": 1674789786006000,
"end_time": 1674789786006000
}
}Term aggregation (full mode)
{
"query": {
"sql": "SELECT kubernetes.namespace_name AS namespace, COUNT(*) AS num FROM {stream} GROUP BY namespace ORDER BY namespace",
"start_time": 1674789786006000,
"end_time": 1674789786006000
}
}Use custom functions
{
"query": {
"sql": "SELECT *, my_func(log) as mykey FROM {stream}",
"start_time": 1674789786006000,
"end_time": 1674789786006000,
"from": 0,
"size": 10
}
}Partition mode with CSV output
Run a top-N query across a wide time window with partition mode, returning hits as compact CSV:
{
"query": {
"sql": "SELECT * FROM {stream} WHERE code=500 ORDER BY _timestamp DESC",
"start_time": 1674000000000000,
"end_time": 1675000000000000,
"from": 0,
"size": 50
},
"agent_options": {
"mode": "partition",
"output_format": "csv"
}
}With mode: partition, the server scans partitions one by one and stops early once it has 50 rows, instead of scanning the full time range through the cache path.
Partition mode for aggregation
{
"query": {
"sql": "SELECT kubernetes_namespace_name, COUNT(*) AS cnt FROM {stream} GROUP BY kubernetes_namespace_name ORDER BY cnt DESC LIMIT 10",
"start_time": 1674000000000000,
"end_time": 1675000000000000
},
"agent_options": {
"mode": "partition"
}
}Each partition's aggregation result accumulates into a progressively merged cache entry. Follow-up queries with slightly shifted time windows reuse that cached work.
Error Responses
When a search request fails, the API returns a standard error body. For certain error codes, the response includes self-correcting guidance to help you (or an AI agent) fix the query without manual investigation.
Error Response Body
| Field name | Data type | Always present | Description |
|---|---|---|---|
| code | int | yes | Numeric error code (see below) |
| message | string | yes | Human-readable problem description |
| error_detail | string | no | Raw technical detail (omitted when hint/suggestions are present) |
| hint | string | no | One-line guidance on how to fix the error |
| suggestions | array[string] | no | Closest valid alternatives (up to 3), ranked by similarity |
The hint and suggestions fields appear only when the server has enough information to offer useful guidance. Existing clients that ignore these optional fields are unaffected.
Field Not Found (code: 20004)
When you reference a field that doesn't exist in the stream schema:
{
"code": 20004,
"message": "unknown field 'servce'",
"suggestions": ["service"]
}The suggestions list contains the closest matching field names ranked by edit distance. If no close match is found, hint provides a fallback:
- Small schemas (10 fields or fewer): the
hintlists all valid fields. - Large schemas: the
hintdirects you to use the stream schema endpoint. - UDS violations (the field exists in the raw stream but not in the User-Defined Schema): the
hintexplains that the field is excluded by the UDS and suggests adding it in stream settings.
Function Not Defined (code: 20005)
When you call a function that doesn't exist:
{
"code": 20005,
"message": "unknown function 'str_mach'",
"hint": "usage: str_match(field, 'v')",
"suggestions": ["str_match", "str_match_ignore_case"]
}Suggestions cover both OpenObserve UDFs and DataFusion built-in functions. When a top suggestion is found, the hint carries a usage example for the closest match.
Other Error Codes
| Code | Name | Description |
|---|---|---|
| 20001 | ServerInternalError | Internal server error |
| 20002 | SearchSQLNotValid | SQL syntax is invalid |
| 20003 | SearchStreamNotFound | Stream does not exist |
| 20004 | SearchFieldNotFound | Field name not found (with suggestions) |
| 20005 | SearchFunctionNotDefined | Function name not found (with suggestions) |
| 20008 | SearchSQLExecuteError | SQL execution failed |
| 20009 | SearchCancelQuery | Query was cancelled |
| 20010 | SearchTimeout | Query timed out |
| 20011 | InvalidParams | Invalid request parameters |
| 20012 | RatelimitExceeded | Rate limit exceeded |
| 20013 | SearchHistogramNotAvailable | Histogram data unavailable |
Next steps
- Example queries: copy-paste SQL examples to try.
- Full-text search functions:
match_all,str_match,re_match, and friends. - Logs UI: run these queries interactively.
Need some help?
- Join our Community Slack
- Or Contact support
Last updated on
Search
Query OpenObserve data with the Search API, including full-text and SQL search, the values endpoint for field values, and around search for surrounding logs.
Around
Search around a specific timestamp to fetch nearby log records using GET /_around with forward and backward context in a 5-minute window.