The document APIs let you index, retrieve, update, and delete documents in Elasticsearch indices. All operations target a specific index and use a document ID for routing.
Index a document
Store a document in an index. Use PUT to specify an ID, or POST to have Elasticsearch generate one.
PUT (explicit ID)
POST (auto ID)
curl -X PUT http://localhost:9200/products/_doc/1 \
-H "Content-Type: application/json" \
-d '{
"name": "Wireless Headphones",
"price": 79.99,
"in_stock": true,
"tags": ["electronics", "audio"]
}'
curl -X POST http://localhost:9200/products/_doc \
-H "Content-Type: application/json" \
-d '{
"name": "USB-C Cable",
"price": 12.99,
"in_stock": true
}'
Response:
{
"_index": "products",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"_seq_no": 0,
"_primary_term": 1
}
Key parameters
Document ID. If omitted on a POST request, Elasticsearch generates a unique ID.
Custom value used to determine the shard. Defaults to the document ID.
How long to wait for the primary shard to be available, e.g. 5s, 1m.
Controls when the change becomes visible to search. Values: false (default — rely on automatic refresh), true (refresh the shard immediately), wait_for (wait for the next scheduled refresh before returning).
Only index if the document’s current sequence number matches. Used with if_primary_term for optimistic concurrency control.
Used together with if_seq_no. Returns 409 Conflict if the primary term does not match.
Get a document
Retrieve a document by its ID.
curl http://localhost:9200/products/_doc/1
Response:
{
"_index": "products",
"_id": "1",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "Wireless Headphones",
"price": 79.99,
"in_stock": true,
"tags": ["electronics", "audio"]
}
}
To retrieve only the _source without metadata:
curl http://localhost:9200/products/_source/1
To check existence without fetching the document:
curl -I http://localhost:9200/products/_doc/1
# Returns 200 if found, 404 if not
To retrieve specific fields only:
curl "http://localhost:9200/products/_doc/1?_source_includes=name,price"
true if the document exists, false otherwise.
The original JSON document body as indexed.
Update a document
Partially update a document using a doc merge or a Painless script. The document must already exist unless upsert is provided.
Partial doc merge
Script
Upsert
curl -X POST http://localhost:9200/products/_update/1 \
-H "Content-Type: application/json" \
-d '{
"doc": {
"price": 69.99,
"on_sale": true
}
}'
curl -X POST http://localhost:9200/products/_update/1 \
-H "Content-Type: application/json" \
-d '{
"script": {
"source": "ctx._source.price *= params.discount",
"lang": "painless",
"params": {
"discount": 0.9
}
}
}'
curl -X POST http://localhost:9200/products/_update/1 \
-H "Content-Type: application/json" \
-d '{
"doc": {
"price": 69.99
},
"doc_as_upsert": true
}'
The update API performs a read-modify-write cycle internally. Use if_seq_no and if_primary_term to guard against concurrent modifications.
Delete a document
curl -X DELETE http://localhost:9200/products/_doc/1
Response:
{
"_index": "products",
"_id": "1",
"_version": 2,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
If the document does not exist, result is "not_found" and the status is 404.
Bulk API
Execute multiple index, create, update, and delete operations in a single request. The body is newline-delimited JSON (NDJSON): each action line is followed by an optional source line.
POST /_bulk
POST /{index}/_bulk
curl -X POST http://localhost:9200/_bulk \
-H "Content-Type: application/x-ndjson" \
-d '
{"index": {"_index": "products", "_id": "10"}}
{"name": "Keyboard", "price": 49.99}
{"create": {"_index": "products", "_id": "11"}}
{"name": "Mouse", "price": 29.99}
{"update": {"_index": "products", "_id": "1"}}
{"doc": {"in_stock": false}}
{"delete": {"_index": "products", "_id": "5"}}
'
The body must end with a newline character (\n). Each action (index, create, update, delete) is a JSON object on its own line, followed by the document source on the next line (except delete, which has no source line).
Action types:
| Action | Behavior |
|---|
index | Index or replace a document |
create | Index only if the document does not exist (fails with 409 if it does) |
update | Partially update a document (requires doc or script in the source line) |
delete | Delete a document (no source line) |
Response:
{
"took": 5,
"errors": false,
"items": [
{
"index": {
"_index": "products",
"_id": "10",
"_version": 1,
"result": "created",
"status": 201
}
},
{
"delete": {
"_index": "products",
"_id": "5",
"result": "not_found",
"status": 404
}
}
]
}
true if any action in the batch failed. Always inspect individual items when errors is true.
One entry per action, in the same order as the request. Each entry contains the action result including status, result, and any error details.
For large data loads, aim for batches of 5–15 MB uncompressed. Monitor the bulk queue (_cat/thread_pool/write) and back off if you receive 429 responses.
Multi-get
Retrieve multiple documents by ID in a single request.
curl -X GET http://localhost:9200/_mget \
-H "Content-Type: application/json" \
-d '{
"docs": [
{"_index": "products", "_id": "1"},
{"_index": "products", "_id": "10"},
{"_index": "orders", "_id": "abc"}
]
}'
If all documents are in the same index, you can specify the index in the path and use the shorter ids form:
curl -X GET http://localhost:9200/products/_mget \
-H "Content-Type: application/json" \
-d '{
"ids": ["1", "10", "11"]
}'
Response:
{
"docs": [
{
"_index": "products",
"_id": "1",
"_version": 1,
"found": true,
"_source": {"name": "Wireless Headphones", "price": 79.99}
},
{
"_index": "products",
"_id": "999",
"found": false
}
]
}
Delete by query
Delete all documents matching a query without fetching them first.
curl -X POST http://localhost:9200/products/_delete_by_query \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"in_stock": false
}
}
}'
Update by query
Update all documents matching a query using a Painless script.
curl -X POST http://localhost:9200/products/_update_by_query \
-H "Content-Type: application/json" \
-d '{
"script": {
"source": "ctx._source.price = ctx._source.price * 1.1",
"lang": "painless"
},
"query": {
"term": {
"in_stock": true
}
}
}'
Update by query takes a snapshot of the index at the start. Documents modified after the snapshot are skipped but counted as version conflicts. Use conflicts=proceed to ignore version conflicts and continue processing.