REST Endpoint Map
This is a reference map from the REST endpoints you actually type into curl
to the Java handler class that serves them and the chapter that explains the
machinery behind the path. Use it the way you'd use a phone book: you know the
endpoint, you want the class, you want where to read next.
Every row is method + path → handler class → chapter. The handler is almost
always a BaseRestHandler subclass whose routes() declares the (method, path)
pairs — see The REST Layer for how RestController
turns a path into a handler. The handler parses the request and calls
NodeClient.execute(SomeAction.INSTANCE, ...); the real work is in the matching
Transport*Action (action-framework.md).
Warning: Class names, package locations, and even which endpoints exist evolve by version. Treat the class column as "the name to grep for," not a fixed line number. Plugin endpoints (
_plugins/_*) live in their own repos (k-NN,neural-search,ml-commons,security), not inOpenSearch/server. Always confirm against your checkout:# core handler for a path: grep the literal path segment in rest/action grep -rn '"_cluster/health"\|_cluster/health' \ server/src/main/java/org/opensearch/rest/action/ # or find every BaseRestHandler and read its routes() grep -rln "extends BaseRestHandler" server/src/main/java/org/opensearch/rest/action/ grep -rn "new Route(" server/src/main/java/org/opensearch/rest/action/cat/RestShardsAction.java
How to read this map
| Column | Meaning |
|---|---|
| Method + path | What you send. {index} etc. are templated path segments bound from the URL. |
| Handler class | The RestHandler (usually BaseRestHandler) whose routes() matches. Grep the simple name; package may move. |
| Action / where it goes | The ActionType + Transport*Action the handler dispatches to, or the subsystem that does the work. |
| Chapter | Where the path behind the endpoint is explained in this book. |
Core handlers live under
server/src/main/java/org/opensearch/rest/action/{admin/cluster,admin/indices,cat,document,search}.
Cat handlers extend AbstractCatAction (which extends BaseRestHandler).
Cluster APIs
State, health, settings, and the allocation/reroute control plane. These ride the cluster-state and cluster coordination machinery.
| Method + path | Handler class | Action / where it goes | Chapter |
|---|---|---|---|
GET /_cluster/health | RestClusterHealthAction | ClusterHealthAction → TransportClusterHealthAction | cluster-and-node-model.md |
GET /_cluster/state | RestClusterStateAction | ClusterStateAction → TransportClusterStateAction (reads the published ClusterState) | cluster-state.md, cluster-state-publishing.md |
GET/PUT /_cluster/settings | RestClusterUpdateSettingsAction / RestClusterGetSettingsAction | ClusterUpdateSettingsAction → TransportClusterUpdateSettingsAction | cluster-state.md |
GET /_cluster/allocation/explain | RestClusterAllocationExplainAction | ClusterAllocationExplainAction → TransportClusterAllocationExplainAction (runs AllocationDeciders) | shard-allocation.md |
POST /_cluster/reroute | RestClusterRerouteAction | ClusterRerouteAction → TransportClusterRerouteAction (drives AllocationService) | shard-allocation.md |
GET /_cluster/pending_tasks | RestPendingClusterTasksAction | PendingClusterTasksAction (cluster-manager queue depth) | cluster-state-publishing.md |
Note:
_cluster/allocation/explainis your first stop when a shard isUNASSIGNEDor won't move — it returns the exactAllocationDeciderthat said "no."_cluster/reroutewith?explainlets you dry-run a manual move. Both are explained from the inside in shard-allocation.md and the allocation/rebalancing lab.
grep -rln "extends BaseRestHandler" server/src/main/java/org/opensearch/rest/action/admin/cluster/
grep -rn "new Route(" .../admin/cluster/RestClusterHealthAction.java
The cluster-manager (formerly "master") node owns the ClusterState; every write
here is a cluster-state update task it serializes.
Cat APIs
_cat/* are the human-readable, column-oriented diagnostics. They render plain
text tables (add ?v for headers, ?h=... to pick columns, &s=... to sort).
All extend AbstractCatAction. They are thin views over the same stats/state the
JSON APIs expose.
| Method + path | Handler class | Reads | Chapter |
|---|---|---|---|
GET /_cat/shards | RestShardsAction | shard routing + per-shard stats (IndicesStats + ClusterState routing table) | index-shard-lifecycle.md, shard-allocation.md |
GET /_cat/nodes | RestNodesAction | NodesInfo + NodesStats + cluster state | cluster-and-node-model.md |
GET /_cat/segments | RestSegmentsAction | IndicesSegments (per-segment: docs, size, codec) | segments-and-codecs.md, refresh-flush-merge.md |
GET /_cat/thread_pool | RestThreadPoolAction | NodesStats thread-pool section (queue/active/rejected) | threadpools-concurrency.md |
GET /_cat/cluster_manager | RestClusterManagerAction | who the elected cluster-manager is (alias: _cat/master, deprecated) | discovery-coordination.md |
GET /_cat/recovery | RestCatRecoveryAction | RecoveryState per shard (peer/store/snapshot recovery progress) | recovery.md |
GET /_cat/segment_replication | RestCatSegmentReplicationAction | segment-replication lag/bytes-behind per shard | replication.md, remote-store-and-durability.md |
GET /_cat/allocation | RestAllocationAction | disk + shard counts per node | shard-allocation.md |
GET /_cat/indices | RestIndicesAction | per-index health/docs/size | index-shard-lifecycle.md |
GET /_cat/pending_tasks | RestCatPendingClusterTasksAction | cluster-manager task queue | cluster-state-publishing.md |
Note:
_cat/masterwas renamed_cat/cluster_managerin the terminology rename; the old path is kept as a deprecated alias._cat/segment_replicationonly returns rows when segment replication is the replication strategy.
# Every cat handler and its columns:
grep -rln "extends AbstractCatAction" server/src/main/java/org/opensearch/rest/action/cat/
grep -rn "getTableWithHeader\|new Route(" .../cat/RestShardsAction.java
Nodes APIs
Per-node introspection — the heavy machinery behind debugging & profiling.
| Method + path | Handler class | Action | Chapter |
|---|---|---|---|
GET /_nodes/stats | RestNodesStatsAction | NodesStatsAction → TransportNodesStatsAction (per-node: jvm, os, thread_pool, indices, breakers, ...) | circuit-breakers-memory.md, threadpools-concurrency.md |
GET /_nodes/{nodeId}/hot_threads | RestNodesHotThreadsAction | NodesHotThreadsAction → TransportNodesHotThreadsAction (samples stacks via HotThreads) | debugging-profiling/lab-01-hot-threads-profile-api.md |
GET /_nodes/plugins | RestNodesInfoAction (?filter_path=**.plugins) | NodesInfoAction → TransportNodesInfoAction (installed plugins/modules) | plugin-architecture.md |
GET /_nodes / /_nodes/{nodeId} | RestNodesInfoAction | NodesInfoAction (settings, jvm, os, transport, http) | cluster-and-node-model.md |
GET /_nodes/usage | RestNodesUsageAction | NodesUsageAction (REST action call counts) | rest-layer.md |
POST /_nodes/reload_secure_settings | RestReloadSecureSettingsAction | NodesReloadSecureSettingsAction (re-read keystore) | plugin-architecture.md |
Note:
_nodes/hot_threadsis the first tool when the cluster is hot or stuck: it samples each node's thread stacks, ranks by CPU/wait/block, and shows the hottest frames. It is implemented by theHotThreadsclass (grep -rn "class HotThreads"), not a profiler. For deeper work attach JFR / async-profiler — see the debugging masterclass.
Document and search APIs
The data plane: index, get, update, delete, bulk, and every flavor of search. These dispatch to the actions in action-framework.md and run the path in search-execution.md.
| Method + path | Handler class | Action | Chapter |
|---|---|---|---|
PUT/POST /{index}/_doc/{id} | RestIndexAction | IndexAction → TransportIndexAction (via TransportBulkAction) | index-shard-lifecycle.md, translog.md |
GET /{index}/_doc/{id} | RestGetAction | GetAction → TransportGetAction (realtime get from translog) | translog.md |
DELETE /{index}/_doc/{id} | RestDeleteAction | DeleteAction → TransportDeleteAction | refresh-flush-merge.md |
POST /_bulk, /{index}/_bulk | RestBulkAction | BulkAction → TransportBulkAction (groups per-shard) | action-framework.md |
POST /{index}/_update/{id} | RestUpdateAction | UpdateAction → TransportUpdateAction (get + script/merge + index) | index-shard-lifecycle.md |
GET/POST /{index}/_search | RestSearchAction | SearchAction → TransportSearchAction (query+fetch over shards) | search-execution.md, query-dsl-querybuilders.md |
GET/POST /_msearch | RestMultiSearchAction | MultiSearchAction → TransportMultiSearchAction (NDJSON of searches) | search-execution.md |
GET/POST /{index}/_count | RestCountAction | SearchAction with size:0 (count-only) | search-execution.md |
GET /{index}/_explain/{id} | RestExplainAction | ExplainAction → TransportExplainAction (per-doc score breakdown) | query-engine/lab-02-bm25-and-scoring.md |
GET/POST /{index}/_validate/query | RestValidateQueryAction | ValidateQueryAction → TransportValidateQueryAction (parse/rewrite without running) | query-dsl-querybuilders.md |
POST /{index}/_search?profile=true | RestSearchAction (profile flag) | SearchAction with ProfileResult per shard/collector | query-engine/index.md, debugging-profiling/lab-01-hot-threads-profile-api.md |
POST /{index}/_pit / DELETE /_pit | RestCreatePitAction / RestDeletePitAction | CreatePitAction / DeletePitAction (Point-in-Time reader) | search-execution.md |
POST /_search/scroll / DELETE /_search/scroll | RestSearchScrollAction / RestClearScrollAction | SearchScrollAction / ClearScrollAction | search-execution.md |
GET/POST /{index}/_termvectors/{id} | RestTermVectorsAction | TermVectorsAction → TransportTermVectorsAction | inverted-index-and-postings.md |
Note:
_countand_search?size=0both render through the search action;_countis justRestCountActionpackaging a count-only request.?profile=trueadds aProfileResultto theSearchResponsewith rewrite/build_scorer/score/next_doc/advance timings per query and per aggregation — read it next to the Profile API discussion in the query-engine masterclass.
grep -rn "new Route(\|profile\|pointInTime" \
server/src/main/java/org/opensearch/rest/action/search/RestSearchAction.java
Point-in-Time vs scroll
| Endpoint | Holds open | Paginates by | Use |
|---|---|---|---|
_search/scroll | a scroll context (segment snapshot) per request | scroll_id | legacy deep export; one consumer |
_pit + search_after | a PIT reader id (snapshot, shareable) | search_after + pit.id | preferred deep pagination |
Both pin Lucene readers (keeping segments from being merged away), so close them. See search-execution.md.
Index admin APIs
Create/configure indices and drive the
refresh/flush/merge and resize machinery.
Handlers live under rest/action/admin/indices.
| Method + path | Handler class | Action | Chapter |
|---|---|---|---|
PUT /{index} | RestCreateIndexAction | CreateIndexAction → TransportCreateIndexAction (cluster-state update) | index-shard-lifecycle.md |
DELETE /{index} | RestDeleteIndexAction | DeleteIndexAction → TransportDeleteIndexAction | index-shard-lifecycle.md |
GET/PUT /{index}/_mapping | RestGetMappingAction / RestPutMappingAction | GetMappingsAction / PutMappingAction | mapping-and-analysis.md |
GET/PUT /{index}/_settings | RestGetSettingsAction / RestUpdateSettingsAction | GetSettingsAction / UpdateSettingsAction | cluster-state.md |
POST /{index}/_refresh | RestRefreshAction | RefreshAction → TransportRefreshAction (open new reader) | refresh-flush-merge.md |
POST /{index}/_flush | RestFlushAction | FlushAction → TransportFlushAction (Lucene commit + trim translog) | refresh-flush-merge.md, translog.md |
POST /{index}/_forcemerge | RestForceMergeAction | ForceMergeAction → TransportForceMergeAction (max_num_segments) | indexwriter-and-merges.md |
POST /{index}/_split/{target} | RestResizeHandler.RestSplitIndexAction | ResizeAction (split: more shards, hard-linked segments) | sharding-and-scaling.md |
POST /{index}/_shrink/{target} | RestResizeHandler.RestShrinkIndexAction | ResizeAction (shrink: fewer shards) | sharding-and-scaling.md |
POST /{index}/_clone/{target} | RestResizeHandler.RestCloneIndexAction | ResizeAction (clone: same shard count, hard-linked) | sharding-and-scaling.md |
GET /{index}/_recovery | RestRecoveryAction | RecoveryAction → TransportRecoveryAction | recovery.md |
GET /{index}/_stats | RestIndicesStatsAction | IndicesStatsAction → TransportIndicesStatsAction | index-shard-lifecycle.md |
GET /{index}/_segments | RestIndicesSegmentsAction | IndicesSegmentsAction (per-segment detail) | segments-and-codecs.md |
POST /{index}/_open / _close | RestOpenIndexAction / RestCloseIndexAction | OpenIndexAction / CloseIndexAction | index-shard-lifecycle.md |
Note:
_split,_shrink, and_cloneare inner classes ofRestResizeHandlerdispatching the oneResizeActionwith aResizeType. They hard-link (not copy) segment files where possible — which is why the source index must be read-only/blocked first (Lucene file formats).
grep -rn "RestSplitIndexAction\|RestShrinkIndexAction\|RestCloneIndexAction\|ResizeType" \
server/src/main/java/org/opensearch/rest/action/admin/indices/RestResizeHandler.java
Aggregations
Aggregations have no dedicated endpoint — they ride inside the _search body
under "aggs" and are served by RestSearchAction. The one exception worth
mapping is composite, the only paginating aggregation: it returns an
after_key you feed back in "after" to get the next page.
| Method + path | Handler / mechanism | Where it goes | Chapter |
|---|---|---|---|
POST /{index}/_search with "aggs" | RestSearchAction (body parses AggregatorFactories) | SearchAction → AggregationPhase | aggregations.md, aggregations/index.md |
composite agg after_key paging | same; CompositeAggregationBuilder reads "after" | CompositeAggregator (paginating) | aggregations/lab-03-composite-pipeline-memory.md |
?profile=true with aggs | RestSearchAction | per-aggregation ProfileResult | aggregations/index.md |
// Composite paging: take after_key from response N, send it as "after" in N+1.
POST /sales/_search
{ "size": 0,
"aggs": { "by": { "composite": {
"size": 1000,
"sources": [ { "sku": { "terms": { "field": "sku" } } } ],
"after": { "sku": "Z-998" }
} } } }
MultiBucketConsumer enforces search.max_buckets; aggs are slice-safe under
concurrent segment search via
their CollectorManager. Eligible aggregations can short-circuit through the
star-tree index.
Tasks API
The task framework tracks every running action; cancellation flows through it.
| Method + path | Handler class | Action | Chapter |
|---|---|---|---|
GET /_tasks | RestListTasksAction | ListTasksAction → TransportListTasksAction | debugging-profiling/index.md |
GET /_tasks/{taskId} | RestGetTaskAction | GetTaskAction → TransportGetTaskAction | debugging-profiling/index.md |
POST /_tasks/{taskId}/_cancel, /_tasks/_cancel | RestCancelTasksAction | CancelTasksAction → TransportCancelTasksAction (TaskManager) | debugging-profiling/index.md |
Note: Long search/reindex jobs register a cancellable
Taskwith theTaskManager._tasks/<id>/_cancelflips a flag the action polls; well-behaved actions check it and abort. Find a runaway withGET /_tasks?detailed&actions=*search*.
Snapshot and repository APIs
Backup/restore over a pluggable repository (fs, s3, ...).
| Method + path | Handler class | Action | Chapter |
|---|---|---|---|
PUT/GET /_snapshot/{repo} | RestPutRepositoryAction / RestGetRepositoriesAction | PutRepositoryAction / GetRepositoriesAction | snapshots-repositories.md |
PUT /_snapshot/{repo}/{snapshot} | RestCreateSnapshotAction | CreateSnapshotAction → TransportCreateSnapshotAction | snapshots-repositories.md |
GET /_snapshot/{repo}/{snapshot} | RestGetSnapshotsAction | GetSnapshotsAction | snapshots-repositories.md |
POST /_snapshot/{repo}/{snapshot}/_restore | RestRestoreSnapshotAction | RestoreSnapshotAction → TransportRestoreSnapshotAction | snapshots-repositories.md, recovery.md |
GET /_snapshot/{repo}/{snapshot}/_status | RestSnapshotsStatusAction | SnapshotsStatusAction | snapshots-repositories.md |
DELETE /_snapshot/{repo}/{snapshot} | RestDeleteSnapshotAction | DeleteSnapshotAction | snapshots-repositories.md |
Snapshots are incremental at the segment file level — unchanged
.cfs/.si/etc. are referenced, not re-uploaded (Lucene file formats),
and the same Repository abstraction underpins
remote-backed storage.
k-NN APIs (_plugins/_knn/*)
Provided by the k-NN plugin (org.opensearch.knn, separate repo). The vector
search itself rides inside _search via the knn query clause; the dedicated
endpoints are for warmup, stats, and model training.
| Method + path | Handler class (in k-NN repo) | Purpose | Chapter |
|---|---|---|---|
GET /_plugins/_knn/warmup/{index} | RestKNNWarmupHandler | load native HNSW/faiss graphs into off-heap memory before querying | knn/warmup.md, knn/native-jni-and-memory.md |
GET /_plugins/_knn/stats | RestKNNStatsHandler | per-node k-NN stats: cache hits, graph memory, miss counts | knn/architecture.md, knn/warmup.md |
POST /_plugins/_knn/models/{id}/_train | RestTrainModelHandler | train an IVF/PQ model (faiss) before indexing quantized vectors | knn/quantization-and-disk-ann.md, vector-internals/lab-02-faiss-index-types-jni.md |
GET/DELETE /_plugins/_knn/models/{id} | RestGetModelHandler / RestDeleteModelHandler | fetch/delete a trained model from the model system index | knn/quantization-and-disk-ann.md |
_search with "knn" query clause | parsed by KNNQueryBuilder (not a REST handler) | approximate / exact vector search | knn/query-path.md, hnsw-vector-search.md |
// The knn query lives inside _search, not its own endpoint:
POST /my-vectors/_search
{ "size": 10,
"query": { "knn": { "my_vector": { "vector": [0.1, 0.2, ...], "k": 10 } } } }
# In a k-NN checkout, find the handlers (names vary by version):
grep -rln "extends BaseRestHandler" src/main/java/org/opensearch/knn/plugin/rest/
grep -rn "_plugins/_knn\|warmup\|/stats\|_train" src/main/java/org/opensearch/knn/plugin/rest/
Note: "warmup" exists because the HNSW/faiss graphs live in native, off-heap memory and are loaded lazily on first query — see native-jni-and-memory.md. The graph construction itself is in vector-internals/lab-01-hnsw-graph-construction.md.
Neural search and ML APIs
Provided by neural-search and ml-commons (separate repos). Models are
registered/deployed via ml-commons; ingest and search pipelines call them; the
neural/hybrid/neural_sparse query clauses ride inside _search.
| Method + path | Handler / mechanism | Purpose | Chapter |
|---|---|---|---|
POST /_plugins/_ml/models/_register | RestMLRegisterModelAction (ml-commons) | register a model (embedding / LLM / sparse) | vectorization-embeddings/index.md |
POST /_plugins/_ml/models/{id}/_deploy / _undeploy | RestMLDeployModelAction / RestMLUndeployModelAction | load/unload a model onto ML nodes | vectorization-embeddings/index.md |
POST /_plugins/_ml/models/{id}/_predict | RestMLPredictionAction | run inference directly | vectorization-embeddings/index.md |
text_embedding ingest processor | TextEmbeddingProcessor (neural-search) via _ingest/pipeline | embed text → vector at index time | vectorization-embeddings/index.md |
_search with "neural" clause | NeuralQueryBuilder | embed query text → ANN vector search | knn/query-path.md, vectorization-embeddings/index.md |
_search with "hybrid" clause | HybridQueryBuilder + normalization search pipeline | combine BM25 + vector, normalize/combine scores | query-engine/index.md |
_search with "neural_sparse" clause | NeuralSparseQueryBuilder | learned sparse (term-expansion) retrieval | vectorization-embeddings/index.md |
PUT /_search/pipeline/{id} | RestPutSearchPipelineAction (core) | register request/response processors (e.g. score normalization for hybrid) | search-execution.md |
GET/DELETE /_search/pipeline/{id} | RestGetSearchPipelineAction / RestDeleteSearchPipelineAction | manage search pipelines | search-execution.md |
Note: Search pipelines (
_search/pipeline) are a core feature (SearchRequestProcessor/SearchResponseProcessor), buthybridquery scoring depends on a normalization-combination response processor shipped by neural-search. The pipeline runs around the search; the query clause runs inside it. Endpoint paths and class names move quickly here — confirm on docs.opensearch.org and in the plugin repo.
# In neural-search / ml-commons checkouts:
grep -rln "extends BaseRestHandler" src/main/java/org/opensearch/ml/rest/ # ml-commons
grep -rn "NeuralQueryBuilder\|HybridQueryBuilder\|NeuralSparseQueryBuilder" \
src/main/java/org/opensearch/neuralsearch/query/ # neural-search
Security APIs (_plugins/_security/api/*)
Provided by the security plugin (org.opensearch.security, separate repo).
Configuration lives in a system index (.opendistro_security / newer
.opensearch_security) and is loaded from YAML with securityadmin.sh; the REST
admin surface mirrors those YAML files.
| Method + path | Backing config | Purpose | Chapter |
|---|---|---|---|
GET/PUT/PATCH /_plugins/_security/api/internalusers/{name} | internal_users.yml | manage internal (basic-auth) users | security/lab-01-authn-tls.md |
.../api/roles/{name} | roles.yml | cluster/index permissions + DLS/FLS/masking per role | security/lab-02-authz-dls-fls.md |
.../api/rolesmapping/{name} | roles_mapping.yml | map users / backend-roles → roles | security/lab-02-authz-dls-fls.md |
.../api/actiongroups/{name} | action_groups.yml | named bundles of actions | security/index.md |
.../api/tenants/{name} | tenants.yml | Dashboards multitenancy | security/index.md |
GET /_plugins/_security/authinfo | (runtime) | who am I: the authenticated user + roles in the ThreadContext | security/lab-01-authn-tls.md |
GET /_plugins/_security/health | (runtime) | security plugin health | security/index.md |
.../api/ssl/certs, .../api/securityconfig | TLS / config.yml | cert info, the authc/authz chain config | security/lab-01-authn-tls.md |
Requests are intercepted on the REST side by SecurityRestFilter and on the
transport side by SecurityFilter (an ActionFilter); PrivilegesEvaluator
decides per action. The free, built-in security plugin is one of the headline
OpenSearch-vs-Elasticsearch differences.
# In a security checkout:
grep -rln "extends BaseRestHandler\|AbstractApiAction" src/main/java/org/opensearch/security/dlic/rest/api/
grep -rn "class SecurityRestFilter\|class SecurityFilter\b\|class PrivilegesEvaluator" \
src/main/java/org/opensearch/security/
Finding the handler for any endpoint
When a row here is stale or missing, derive it yourself — three reliable moves:
# 1. Grep the literal path segment under rest/action (core):
grep -rn "_forcemerge\|forcemerge" server/src/main/java/org/opensearch/rest/action/
# 2. List every handler, then read its routes():
grep -rln "extends BaseRestHandler\|extends AbstractCatAction" \
server/src/main/java/org/opensearch/rest/action/
grep -n "new Route(" server/src/main/java/org/opensearch/rest/action/admin/indices/RestForceMergeAction.java
# 3. From a running cluster, see which action a request hit:
GET /_nodes/usage # per-REST-action call counts (RestNodesUsageAction)
GET /_tasks?detailed&actions=* # live actions, names like indices:data/read/search
The action name (indices:data/read/search, cluster:monitor/health, ...) in
_tasks is the bridge from REST to the ActionType registered in ActionModule
— follow it into action-framework.md.
Related references
- Lucene file formats cheat-sheet — the files behind
_segments,_forcemerge,_split, and snapshots. - OpenSearch vs. Elasticsearch today — why
_cat/cluster_manager,_cat/segment_replication, and_plugins/_*exist. - The REST Layer and action framework — how a path becomes a handler call and what it dispatches into.