OpenObserve Docs
ReferenceApiSearch

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 nameData typeDefault valueDescription
queryobject-query params
query.sqlstring-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_timeint640unit: microseconds, filter data by time range, you need always provide this value
query.end_timeint640unit: microseconds, filter data by time range, you need always provide this value
query.fromint640offset in SQL
query.sizeint640limit in SQL
search_typestring-default is empty, support: ui, dashboards, reports, alerts
timeoutint0default value based on ZO_QUERY_TIMEOUT=600
agent_optionsobject-options for agent/MCP clients
agent_options.modestringdefaultquery execution mode: default (result cache path) or partition (streaming partition loop, see below)
agent_options.output_formatstringjsonresponse 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 nameData typeDefault valueDescription
tookint64-unit: milliseconds, query execute time
fromint640value from query.from
sizeint640value from query.size
scan_sizeint640unit: MB, it response the data size scale when execute the query.
hitsarray-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.

ValueDescription
jsonDefault. Hits returned as a JSON array of objects.
csvTabular hits as a compact CSV block (~40% fewer tokens than JSON).
md_tableTabular 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.

ValueDescription
default(default) Single search through the result cache path. Behavior is byte-for-byte unchanged.
partitionPartitioned 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 Response identical in shape to default mode, 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 _search in a loop. Let the server do it with one call in partition mode.

SQL Syntax

Please refer to PostgreSQL for SQL Syntax.

Notes:

  • We have a build-in time field, _timestamp you 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 nameData typeAlways presentDescription
codeintyesNumeric error code (see below)
messagestringyesHuman-readable problem description
error_detailstringnoRaw technical detail (omitted when hint/suggestions are present)
hintstringnoOne-line guidance on how to fix the error
suggestionsarray[string]noClosest 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 hint lists all valid fields.
  • Large schemas: the hint directs you to use the stream schema endpoint.
  • UDS violations (the field exists in the raw stream but not in the User-Defined Schema): the hint explains 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

CodeNameDescription
20001ServerInternalErrorInternal server error
20002SearchSQLNotValidSQL syntax is invalid
20003SearchStreamNotFoundStream does not exist
20004SearchFieldNotFoundField name not found (with suggestions)
20005SearchFunctionNotDefinedFunction name not found (with suggestions)
20008SearchSQLExecuteErrorSQL execution failed
20009SearchCancelQueryQuery was cancelled
20010SearchTimeoutQuery timed out
20011InvalidParamsInvalid request parameters
20012RatelimitExceededRate limit exceeded
20013SearchHistogramNotAvailableHistogram data unavailable

Next steps

Need some help?

Was this page helpful?

Last updated on

On this page