Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Note
Azure AI Search is available through the Azure portal, REST APIs, and Azure SDKs.
Important
Features, capabilities, or properties marked (preview) aren't covered by a service-level agreement, aren't recommended for production workloads, and might change or be constrained before they become generally available. The Azure AI Search preview terms apply to all preview functionality, whether it's standalone or part of a generally available feature.
In this tutorial, you configure independent blob indexers for DOCX, JSON, and CSV content that write to one Azure AI Search index. A nullable union schema lets each pipeline populate the fields it owns while your application queries all formats together. You can extend this pattern to other compatible indexer types when the indexers have compatible field mappings and target the same index. This tutorial demonstrates and verifies only folder-scoped blob indexers.
In this tutorial, you:
- Prepare DOCX, JSON, and CSV sample data in separate blob folders
- Create a union-schema index and three folder-scoped blob data sources
- Configure three indexers with different parsing and enrichment paths
- Run the indexers and verify the combined keyword, vector, and hybrid results
- Review content ownership, troubleshooting, and cleanup practices
Semantic chunking (preview) in the Azure Content Understanding skill requires the 2026-08-01-preview REST API. The multi-indexer pattern, index projections, and the Content Understanding skill without semantic chunking are generally available. However, for consistency, this tutorial uses 2026-08-01-preview across all requests.
Prerequisites
An Azure subscription. Create one for free.
An Azure AI Search service with a system-assigned managed identity. On the search service Settings > Keys page, set API access control to Both so that the service accepts either Microsoft Entra ID bearer tokens or API keys. This setting enables the bearer-token requests in this tutorial without breaking existing clients that use API keys. Select Role-based access control instead only if you no longer need key-based authentication. For more information, see Enable role-based access control.
Your user account must have the Search Service Contributor, Search Index Data Contributor, and Search Index Data Reader roles on the search service. These roles authorize the management and query operations sent with the bearer token throughout this tutorial. For more information, see Connect to Azure AI Search using roles. If a request includes both a bearer token and an API key, the API key takes precedence.
An Azure Storage account with a blob container. Create
docx,json, andcsvfolders in the container. Assign the Storage Blob Data Reader role at the storage-account scope to the search service's managed identity. Your user account needs Storage Blob Data Contributor at the storage-account scope to upload the sample files.The Azure CLI and a REST client that can send authenticated requests. Run
az login, select the subscription that contains your search service, and request a bearer token for the Azure AI Search scope:az account get-access-token --scope https://search.azure.cn/.default --query accessToken --output tsvCopy the returned token into the
<search-access-token>placeholder used by everyAuthorization: Bearer <search-access-token>header in this tutorial. Access tokens expire, so request another token if you receive an authentication error. Replace every other angle-bracketed placeholder before you send a request.
Placeholder reference
This tutorial uses the following placeholders:
| Placeholder | Value |
|---|---|
<search-endpoint> |
https://<search-service-name>.search.azure.cn |
<search-access-token> |
The Azure AI Search access token returned by the Azure CLI |
<storage-resource-id> |
/subscriptions/<subscription-id>/resourceGroups/<resource-group-name>/providers/Microsoft.Storage/storageAccounts/<storage-account-name> |
<container-name> |
The name of your blob container |
<storage-account-name> |
Your storage account name |
Review the architecture
Three blob indexers write independently to the same index. You can apply the general design when multiple indexers have compatible field mappings and target the same index, but this tutorial demonstrates and verifies only folder-scoped blob indexers.
sourceType isn't an Azure AI Search system field. It's an article-defined discriminator that the JSON and CSV source records populate directly and the DOCX skillset adds during enrichment. Azure AI Search doesn't infer which indexer wrote a document.
| Pipeline | Source shape | Indexing path | Vector behavior |
|---|---|---|---|
| DOCX | One short .docx file |
Content Understanding semantic chunking (preview), constant sourceType assignment, an embedding skill, and index projections |
Populated |
| JSON | Two objects in a JSON array | Direct field mappings | Intentionally null |
| CSV | Two rows with first-line headers | Direct field mappings and an embedding skill | Populated |
If the sample DOCX produces one chunk, the sample result contains five search documents: one DOCX projected child, two JSON products, and two CSV tickets. The four directly indexed documents (2 JSON + 2 CSV) get their keys from the source data. The DOCX child gets a generated key from the index projection. Longer or more structured documents can produce multiple projected children.
Warning
Give every directly indexed source globally unique document key values, such as values prefixed with json_ and csv_. Indexers use document update semantics that can combine or overwrite fields when two writes have the same key. A collision can create a document with unsafe mixed ownership.
The shared index has these fields:
id,parentId,sourceType,content,contentVector,sourceUri,title, andlastModified.- Nullable format-specific fields:
category,price,status, andpriority.
The id field is the index key field. Its keyword analyzer preserves each identifier as one exact value. JSON and CSV records supply unique document keys, while index projections generate DOCX child keys. Generated child keys can change when source content or projected chunk boundaries change, so don't predict or persist assumptions about their values.
Prepare sample files
Each indexer reads one format from its associated folder. Create one small file per folder so that each pipeline has content to index and query from the shared index later in this tutorial.
To prepare the sample files:
In the
docxfolder, upload a small file namedsearch-overview.docx. Add the titleSearch overviewand this one-sentence paragraph:Azure AI Search supports keyword and vector search over indexed content.A short document normally produces one semantic chunk, but chunk count depends on document structure.In the
jsonfolder, uploadproducts.jsonwith this two-element array:[ { "id": "json_product_001", "sourceType": "json", "content": "Compact wireless keyboard for shared workspaces.", "sourceUri": "https://<storage-account-name>.blob.core.chinacloudapi.cn/<container-name>/json/products.json#product-001", "title": "Compact wireless keyboard", "lastModified": "2026-08-07T00:00:00Z", "category": "Accessories", "price": 49.99 }, { "id": "json_product_002", "sourceType": "json", "content": "Adjustable monitor stand with cable management.", "sourceUri": "https://<storage-account-name>.blob.core.chinacloudapi.cn/<container-name>/json/products.json#product-002", "title": "Adjustable monitor stand", "lastModified": "2026-08-07T00:00:00Z", "category": "Office", "price": 89.50 } ]In the
csvfolder, uploadtickets.csvwith these two rows:id,sourceType,content,sourceUri,title,lastModified,status,priority csv_ticket_001,csv,"Search results omit a recently added product.","https://<storage-account-name>.blob.core.chinacloudapi.cn/<container-name>/csv/tickets.csv#ticket-001","Missing product",2026-08-07T00:00:00Z,open,high csv_ticket_002,csv,"A synonym rule needs review before deployment.","https://<storage-account-name>.blob.core.chinacloudapi.cn/<container-name>/csv/tickets.csv#ticket-002","Review synonym rule",2026-08-07T00:00:00Z,pending,mediumThe prefixes
json_andcsv_keep the directly indexed document key values unique across the two pipelines.Verify that all three blobs exist in the container:
az storage blob list \ --account-name <storage-account-name> \ --container-name <container-name> \ --auth-mode login \ --query "[].name" \ --output tsvReference: az storage blob list (Azure CLI)
Expected output:
csv/tickets.csv docx/search-overview.docx json/products.jsonIf the output doesn't list all three files, verify you uploaded each file to the correct folder path.
Create the union-schema index
Create one index that accepts all three document shapes. Fields that a pipeline doesn't populate remain null. The vectorizer converts query text to vectors at query time, and the two skillsets use the same embedding model to generate index-time vectors.
To create the union-schema index:
Send the create request:
POST <search-endpoint>/indexes?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "multi-source-index", "fields": [ { "name": "id", "type": "Edm.String", "key": true, "searchable": true, "filterable": true, "retrievable": true, "analyzer": "keyword" }, { "name": "parentId", "type": "Edm.String", "filterable": true, "retrievable": true }, { "name": "sourceType", "type": "Edm.String", "filterable": true, "facetable": true, "retrievable": true }, { "name": "content", "type": "Edm.String", "searchable": true, "retrievable": true }, { "name": "contentVector", "type": "Collection(Edm.Single)", "searchable": true, "retrievable": false, "stored": false, "dimensions": 1536, "vectorSearchProfile": "content-vector-profile" }, { "name": "sourceUri", "type": "Edm.String", "filterable": true, "retrievable": true }, { "name": "title", "type": "Edm.String", "searchable": true, "retrievable": true }, { "name": "lastModified", "type": "Edm.DateTimeOffset", "filterable": true, "sortable": true, "retrievable": true }, { "name": "category", "type": "Edm.String", "filterable": true, "facetable": true, "retrievable": true }, { "name": "price", "type": "Edm.Double", "filterable": true, "sortable": true, "retrievable": true }, { "name": "status", "type": "Edm.String", "filterable": true, "facetable": true, "retrievable": true }, { "name": "priority", "type": "Edm.String", "filterable": true, "facetable": true, "retrievable": true } ], "vectorSearch": { "algorithms": [ { "name": "content-hnsw", "kind": "hnsw", "hnswParameters": { "metric": "cosine", "m": 4, "efConstruction": 400, "efSearch": 500 } } ], "profiles": [ { "name": "content-vector-profile", "algorithm": "content-hnsw", "vectorizer": "content-azure-openai-vectorizer" } ], "vectorizers": [ { "name": "content-azure-openai-vectorizer", "kind": "azureOpenAI", "azureOpenAIParameters": { "resourceUri": "<resource-endpoint>", "deploymentId": "<embedding-deployment-name>", "modelName": "text-embedding-3-small" } } ] } }Reference: Create Index (REST API)
The response returns HTTP
201 Createdwith the full index definition.Verify that the index was created:
GET <search-endpoint>/indexes/multi-source-index?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Get Index (REST API)
The response contains
"name": "multi-source-index"and 12 fields. Confirm thefieldsarray length equals 12 and thevectorSearchsection includes thecontent-azure-openai-vectorizer.
Create three folder-scoped data sources
Create a separate data source for each blob folder. All three definitions can use the same storage connection and container. The container.query value limits each data source to one virtual folder. For example, "query": "docx" limits the DOCX data source to blobs under the docx folder.
Each indexer also uses indexedFileNameExtensions as a file-type allow list. The folder query and extension allow list are independent filters: the data source limits which folder the indexer enumerates, and the indexer configuration limits which file types in that folder are eligible. This example doesn't need excludedFileNameExtensions because each allow list contains only one extension.
To create the three folder-scoped data sources:
Create the DOCX data source:
POST <search-endpoint>/datasources?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "docx-blob-datasource", "type": "azureblob", "credentials": { "connectionString": "ResourceId=<storage-resource-id>;" }, "container": { "name": "<container-name>", "query": "docx" } }Reference: Create Data Source (REST API)
The response returns HTTP
201 Created.Create the JSON data source:
POST <search-endpoint>/datasources?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "json-blob-datasource", "type": "azureblob", "credentials": { "connectionString": "ResourceId=<storage-resource-id>;" }, "container": { "name": "<container-name>", "query": "json" } }Reference: Create Data Source (REST API)
The response returns HTTP
201 Created.Create the CSV data source:
POST <search-endpoint>/datasources?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "csv-blob-datasource", "type": "azureblob", "credentials": { "connectionString": "ResourceId=<storage-resource-id>;" }, "container": { "name": "<container-name>", "query": "csv" } }Reference: Create Data Source (REST API)
The response returns HTTP
201 Created.
Verify that all three data sources exist:
GET <search-endpoint>/datasources?api-version=2026-08-01-preview
Authorization: Bearer <search-access-token>
Reference: List Data Sources (REST API)
The response value array contains docx-blob-datasource, json-blob-datasource, and csv-blob-datasource. If any are missing, review the corresponding creation step for errors.
Configure the DOCX pipeline
The DOCX pipeline pairs a skillset with an indexer. The skillset uses semantic chunking (preview), assigns the docx discriminator, generates an embedding for each chunk, and projects each child into the shared index. The indexer binds that skillset to the docx-blob-datasource, reads the file by using allowSkillsetToReadFileData, and runs the skillset on each blob.
To configure the DOCX pipeline:
Create the skillset:
POST <search-endpoint>/skillsets?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "docx-semantic-skillset", "description": "Chunk and vectorize DOCX files for the shared index.", "skills": [ { "@odata.type": "#Microsoft.Skills.Util.ContentUnderstandingSkill", "name": "docx-content-understanding", "context": "/document", "chunkingProperties": { "method": "semantic", "unit": "tokens", "maximumLength": 500 }, "inputs": [ { "name": "file_data", "source": "/document/file_data" } ], "outputs": [ { "name": "text_sections", "targetName": "text_sections" } ] }, { "@odata.type": "#Microsoft.Skills.Util.ConditionalSkill", "name": "docx-source-type", "context": "/document/text_sections/*", "inputs": [ { "name": "condition", "source": "= true" }, { "name": "whenTrue", "source": "= 'docx'" }, { "name": "whenFalse", "source": "= null" } ], "outputs": [ { "name": "output", "targetName": "source_type" } ] } ], "indexProjections": { "selectors": [ { "targetIndexName": "multi-source-index", "parentKeyFieldName": "parentId", "sourceContext": "/document/text_sections/*", "mappings": [ { "name": "sourceType", "source": "/document/text_sections/*/source_type" }, { "name": "content", "source": "/document/text_sections/*/content" }, { "name": "contentVector", "source": "/document/text_sections/*/content_vector" }, { "name": "sourceUri", "source": "/document/metadata_storage_path" }, { "name": "title", "source": "/document/metadata_storage_name" }, { "name": "lastModified", "source": "/document/metadata_storage_last_modified" } ] } ], "parameters": { "projectionMode": "skipIndexingParentDocuments" } } }Reference: Azure Content Understanding skill, and Define an index projection
The response returns HTTP
201 Created.The Conditional skill doesn't select or exclude blobs. File selection already happens through
container.queryandindexedFileNameExtensionsbefore the skillset runs. Its always-true condition only assigns the constantdocxvalue tosourceTypeon each projected chunk.Verify the skillset was created:
GET <search-endpoint>/skillsets/docx-semantic-skillset?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Get Skillset (REST API)
The response contains
"name": "docx-semantic-skillset"and three skills in theskillsarray.Create the DOCX indexer:
POST <search-endpoint>/indexers?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "docx-indexer", "dataSourceName": "docx-blob-datasource", "targetIndexName": "multi-source-index", "skillsetName": "docx-semantic-skillset", "parameters": { "batchSize": 1, "configuration": { "dataToExtract": "contentAndMetadata", "parsingMode": "default", "indexedFileNameExtensions": ".docx", "allowSkillsetToReadFileData": true } }, "fieldMappings": [], "outputFieldMappings": [] }Reference: Create Indexer (REST API)
The response returns HTTP
201 Created. The indexer starts its first run automatically.Index projections create
idandparentId. Don't map either field in the projection selector.Check the status of the indexer:
GET <search-endpoint>/indexers/docx-indexer/status?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Get Indexer Status (REST API)
Wait for
lastResult.statusto showsuccessand confirm thatlastResult.itemsFailedis0. If the status showsinProgress, wait 30 seconds and check again. You verify the projected DOCX output by querying the index later in this tutorial.
Configure the JSON pipeline
The JSON pipeline uses an indexer alone, without a skillset. The indexer creates one search document for each array element, maps the source-owned key and other fields directly, and leaves contentVector null intentionally. Vector queries exclude these documents, while full-text and filtered queries can still return them.
To configure the JSON pipeline:
Create the JSON indexer:
POST <search-endpoint>/indexers?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "json-indexer", "dataSourceName": "json-blob-datasource", "targetIndexName": "multi-source-index", "parameters": { "configuration": { "parsingMode": "jsonArray", "indexedFileNameExtensions": ".json" } }, "fieldMappings": [ { "sourceFieldName": "/id", "targetFieldName": "id" }, { "sourceFieldName": "/sourceType", "targetFieldName": "sourceType" }, { "sourceFieldName": "/content", "targetFieldName": "content" }, { "sourceFieldName": "/sourceUri", "targetFieldName": "sourceUri" }, { "sourceFieldName": "/title", "targetFieldName": "title" }, { "sourceFieldName": "/lastModified", "targetFieldName": "lastModified" }, { "sourceFieldName": "/category", "targetFieldName": "category" }, { "sourceFieldName": "/price", "targetFieldName": "price" } ], "outputFieldMappings": [] }Reference: Create Indexer (REST API), Index JSON blobs and files, and Define field mappings
The response returns HTTP
201 Created. The indexer starts its first run automatically.Verify that the indexer processed both records:
GET <search-endpoint>/indexers/json-indexer/status?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Get Indexer Status (REST API)
Wait for
lastResult.statusto showsuccessand confirm thatlastResult.itemsFailedis0. If the status showsinProgress, wait 15 seconds and check again. You verify both JSON records by querying the index later in this tutorial.
Configure the CSV pipeline
The CSV pipeline pairs a skillset with an indexer. The indexer creates one search document for each row, maps the source-owned key and other fields directly, and maps the skillset's embedding to contentVector. Because these documents carry vectors, both full-text and vector queries can return them.
To configure the CSV pipeline:
Create the CSV skillset:
POST <search-endpoint>/skillsets?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "csv-embedding-skillset", "description": "Vectorize CSV ticket content for the shared index.", "skills": [ { "@odata.type": "#Microsoft.Skills.Text.AzureOpenAIEmbeddingSkill", "name": "csv-embedding", "context": "/document", "resourceUri": "<resource-endpoint>", "deploymentId": "<embedding-deployment-name>", "modelName": "text-embedding-3-small", "dimensions": 1536, "inputs": [ { "name": "text", "source": "/document/content" } ], "outputs": [ { "name": "embedding", "targetName": "csv_content_vector" } ] } ] }Reference: Create Skillset (REST API)
The response returns HTTP
201 Created.Verify the skillset was created:
GET <search-endpoint>/skillsets/csv-embedding-skillset?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Get Skillset (REST API)
The response contains
"name": "csv-embedding-skillset"and one skill in theskillsarray.Create the CSV indexer:
POST <search-endpoint>/indexers?api-version=2026-08-01-preview Content-Type: application/json Authorization: Bearer <search-access-token> { "name": "csv-indexer", "dataSourceName": "csv-blob-datasource", "targetIndexName": "multi-source-index", "skillsetName": "csv-embedding-skillset", "parameters": { "configuration": { "parsingMode": "delimitedText", "firstLineContainsHeaders": true, "delimitedTextDelimiter": ",", "indexedFileNameExtensions": ".csv" } }, "fieldMappings": [ { "sourceFieldName": "id", "targetFieldName": "id" }, { "sourceFieldName": "sourceType", "targetFieldName": "sourceType" }, { "sourceFieldName": "content", "targetFieldName": "content" }, { "sourceFieldName": "sourceUri", "targetFieldName": "sourceUri" }, { "sourceFieldName": "title", "targetFieldName": "title" }, { "sourceFieldName": "lastModified", "targetFieldName": "lastModified" }, { "sourceFieldName": "status", "targetFieldName": "status" }, { "sourceFieldName": "priority", "targetFieldName": "priority" } ], "outputFieldMappings": [ { "sourceFieldName": "/document/csv_content_vector", "targetFieldName": "contentVector" } ] }Reference: Create Indexer (REST API), Index CSV blobs and files, and Output field mappings
The response returns HTTP
201 Created. The indexer starts its first run automatically.Verify that the indexer processed both rows:
GET <search-endpoint>/indexers/csv-indexer/status?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Get Indexer Status (REST API)
Wait for
lastResult.statusto showsuccessand confirm thatlastResult.itemsFailedis0. If the status showsinProgress, wait 15 seconds and check again. You verify both CSV rows by querying the index later in this tutorial.
Run and monitor the indexers
Each indexer starts its first run automatically when you create it. To start another run and verify the results:
Run the three indexers:
POST <search-endpoint>/indexers/docx-indexer/run?api-version=2026-08-01-preview Content-Type: application/json Content-Length: 0 Authorization: Bearer <search-access-token>POST <search-endpoint>/indexers/json-indexer/run?api-version=2026-08-01-preview Content-Type: application/json Content-Length: 0 Authorization: Bearer <search-access-token>POST <search-endpoint>/indexers/csv-indexer/run?api-version=2026-08-01-preview Content-Type: application/json Content-Length: 0 Authorization: Bearer <search-access-token>Reference: Run Indexer (REST API)
Each run request returns HTTP
202 Acceptedwith an empty body. Some REST clients require theContent-Length: 0header for a request without a body.Note
If the source data didn't change since the last run, an indexer might report
itemsProcessedas0. This behavior is expected because indexers use change detection and skip blobs that aren't modified.Check the status of each indexer:
GET <search-endpoint>/indexers/docx-indexer/status?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>GET <search-endpoint>/indexers/json-indexer/status?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>GET <search-endpoint>/indexers/csv-indexer/status?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Get Indexer Status (REST API)
For each indexer, confirm that
lastResult.statusissuccessandlastResult.itemsFailedis0. The service reports processing details inlastResult; verify the indexed document counts in the next section because projected chunks and parsed records don't necessarily correspond one-to-one with source blobs.
Query the shared index
Use the following requests to verify the combined result.
Verify the union result
Return all documents written by the three indexers:
POST <search-endpoint>/indexes/multi-source-index/docs/search?api-version=2026-08-01-preview
Content-Type: application/json
Authorization: Bearer <search-access-token>
{
"search": "*",
"select": "id,parentId,sourceType,title",
"count": true
}
Reference: Search Documents (REST API)
The result contains two JSON products, two CSV tickets, and one or more DOCX chunks. If the sample DOCX produces one chunk, @odata.count is 5. Confirm all three sourceType values appear: docx, json, and csv.
Query by discriminator
Return the two JSON products:
POST <search-endpoint>/indexes/multi-source-index/docs/search?api-version=2026-08-01-preview
Content-Type: application/json
Authorization: Bearer <search-access-token>
{
"search": "*",
"filter": "sourceType eq 'json'",
"select": "id,sourceType,title,category,price",
"count": true
}
Reference: Search Documents (REST API)
Confirm @odata.count is 2. Both results have category and price values. The status and priority fields aren't in the select list because they belong to the CSV schema.
Run a full-text query
Full-text search considers all documents with matching searchable text, including JSON records with null vectors.
POST <search-endpoint>/indexes/multi-source-index/docs/search?api-version=2026-08-01-preview
Content-Type: application/json
Authorization: Bearer <search-access-token>
{
"search": "search product",
"searchFields": "content,title",
"select": "id,parentId,sourceType,title,content",
"count": true
}
Reference: Search Documents (REST API)
Results include documents from any source type that contains "search" or "product" in their content or title fields. With the sample data, expect matches from the DOCX chunk (contains "search") and the CSV ticket (contains "product").
Run a vector query
Send text to the index vectorizer instead of pasting 1,536 vector values. The results contain vector-bearing DOCX and CSV documents, not JSON documents whose contentVector is null.
POST <search-endpoint>/indexes/multi-source-index/docs/search?api-version=2026-08-01-preview
Content-Type: application/json
Authorization: Bearer <search-access-token>
{
"vectorQueries": [
{
"kind": "text",
"text": "search indexing issue",
"fields": "contentVector",
"k": 3
}
],
"select": "id,parentId,sourceType,title,content"
}
Reference: Search Documents (REST API)
Confirm that no results have "sourceType": "json". JSON documents are excluded because their contentVector is null.
Run a filtered vector query
Limit vector results to CSV tickets:
POST <search-endpoint>/indexes/multi-source-index/docs/search?api-version=2026-08-01-preview
Content-Type: application/json
Authorization: Bearer <search-access-token>
{
"filter": "sourceType eq 'csv'",
"vectorFilterMode": "preFilter",
"vectorQueries": [
{
"kind": "text",
"text": "deployment review",
"fields": "contentVector",
"k": 2
}
],
"select": "id,sourceType,title,status,priority"
}
Reference: Search Documents (REST API)
Confirm that all results have "sourceType": "csv" and include the status and priority fields.
Run a hybrid query
Combine keyword and vector retrieval in one request:
POST <search-endpoint>/indexes/multi-source-index/docs/search?api-version=2026-08-01-preview
Content-Type: application/json
Authorization: Bearer <search-access-token>
{
"search": "search issue",
"searchFields": "content,title",
"vectorQueries": [
{
"kind": "text",
"text": "search issue",
"fields": "contentVector",
"k": 3
}
],
"select": "id,parentId,sourceType,title,content"
}
Reference: Search Documents (REST API)
Hybrid queries combine keyword matches (which can include JSON documents) with vector matches (which exclude null-vector documents). The merged result set can contain documents from all three source types when both keyword and vector terms match across pipelines.
For more query options, see Create a vector query and Create a hybrid query.
Operate and reconcile indexed content
Treat each indexer as an independent writer and define ownership and cleanup rules for its document key values.
Monitor, schedule, run, and reset each indexer separately. A failure in one pipeline doesn't remove documents already written by another pipeline. For example, a malformed CSV can cause
csv-indexerto fail without making existing documents or the other indexers unavailable. For scheduling options and detailed status fields, see Schedule an indexer and Monitor indexer status.Keep direct JSON and CSV keys stable and globally unique. Don't reuse a key between pipelines.
Expect DOCX projection reruns to reconcile stale child documents through their parent relationship. Generated child keys can change, so identify a projected group by
parentIdinstead of predicting child keys. For projection lifecycle details, see Content lifecycle.Configure an appropriate blob deletion detection policy before relying on a deleted DOCX parent blob to remove its projected children.
Caution
Direct one-to-many parsing requires application-owned cleanup. Blob deletion detection policies don't apply to the search documents created from JSON array elements or CSV rows. Removing an element or row, or deleting its source blob, can leave target documents in the index. Delete each orphan explicitly by key with Delete documents or rebuild the index from the authoritative set of document keys.
Troubleshooting
The following table lists common issues and resolutions when running the three pipelines.
| Symptom | Possible cause | Resolution |
|---|---|---|
lastResult.itemsFailed is greater than zero |
A source document has an unexpected format, or the embedding skill received empty text. | Check lastResult.errors in the indexer status response for the specific error message and the document key that failed. |
@odata.count is lower than expected after all indexers succeed |
An indexer hasn't finished its run, the DOCX produced a different number of chunks, or a key collision overwrote a document. | Wait for all three indexers to report success. Confirm all three sourceType values appear, and verify that JSON and CSV keys use distinct prefixes (json_, csv_). |
| Vector query returns zero results | The vectorizer configuration in the index doesn't match the embedding model used at index time, or no documents have populated contentVector values. |
Confirm the vectorizer's deploymentId and modelName match the values in the skillsets. Confirm DOCX and CSV indexers completed with zero failures. |
| JSON documents appear in vector query results | The JSON indexer populated contentVector unexpectedly. |
Confirm json-indexer has no outputFieldMappings that target contentVector. JSON records should leave contentVector null. |
| CSV indexer fails with parsing errors | The CSV file has unescaped commas, mismatched quotes, or encoding issues. | Open the CSV file and verify that fields with commas are enclosed in double quotes. Save the file with UTF-8 encoding. |
"The index 'multi-source-index' was not found" error during queries |
The index wasn't created, or you sent the query to the wrong search service. | Verify <search-endpoint> points to the correct service. Run GET <search-endpoint>/indexes?api-version=2026-08-01-preview to list indexes. |
Run indexer request returns HTTP 411 Length Required |
The POST request has no body, and the REST client didn't send a Content-Length header. |
Add Content-Length: 0 to the request headers. Some REST clients require this header explicitly for POST requests with empty bodies. |
Re-run shows itemsProcessed equal to 0 |
The source blobs haven't changed since the last run. | This is expected behavior. Indexers use change detection and skip unchanged blobs. To force reprocessing, reset the indexer first by sending POST <search-endpoint>/indexers/<indexer-name>/reset?api-version=2026-08-01-preview with Content-Length: 0 and the bearer token header. |
Clean up resources
When you no longer need the Azure AI Search objects you created in this tutorial, delete them in the following dependency order:
Delete the indexers:
DELETE <search-endpoint>/indexers/docx-indexer?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>DELETE <search-endpoint>/indexers/json-indexer?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>DELETE <search-endpoint>/indexers/csv-indexer?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Delete Indexer (REST API)
Each delete operation returns HTTP
204 No Contenton success. If you receive404 Not Found, the indexer was already deleted.Delete the skillsets:
DELETE <search-endpoint>/skillsets/docx-semantic-skillset?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>DELETE <search-endpoint>/skillsets/csv-embedding-skillset?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Delete Skillset (REST API)
Each delete operation returns HTTP
204 No Contenton success.Delete the data sources:
DELETE <search-endpoint>/datasources/docx-blob-datasource?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>DELETE <search-endpoint>/datasources/json-blob-datasource?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>DELETE <search-endpoint>/datasources/csv-blob-datasource?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Delete Data Source (REST API)
Each delete operation returns HTTP
204 No Contenton success.Delete the index:
DELETE <search-endpoint>/indexes/multi-source-index?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: Delete Index (REST API)
Returns HTTP
204 No Contenton success.Verify the indexers are removed:
GET <search-endpoint>/indexers?api-version=2026-08-01-preview Authorization: Bearer <search-access-token>Reference: List Indexers (REST API)
Confirm that
docx-indexer,json-indexer, andcsv-indexerno longer appear in the response.If you created a resource group for this exercise, delete it and all resources it contains:
az group delete --name <resource-group-name> --yes --no-waitReference: az group delete (Azure CLI)
The command returns immediately and deletes the resource group and all its resources asynchronously.