Search in SitecoreAI has always worked best when content lives inside the platform, something gets crawled or pulled straight from the Content editor, and it shows up in search results. That model works right up until it doesn't. Product catalogs living in a separate PIM, legacy knowledge bases, or data sitting behind a custom app with no crawlable front end do not fit neatly into "crawl it or edit it in Sitecore." Until last week, there wasn't a clean answer for that gap.
Sitecore closed it on September 25 with push sources, a new search source type built specifically for content that can't be crawled or authored through the editor. Alongside it comes the Ingestion Service API, a single endpoint that lets an external system push documents directly into a SitecoreAI index. If you own data outside the platform and want it showing up in search results, autocomplete, or recommendation experiences, this is the feature to know about.
I spent some time this week setting one up end to end. Here's what it actually involves.
Why Push Sources Matter
The three existing source types all assume SitecoreAI can either crawl the content or read it from the Content editor. Push sources flip that assumption. The external system stays the system of record, and you decide exactly what gets indexed and when.
A few scenarios where this is the obvious fit:
- Product catalogs or inventory data living in a separate PIM or ERP
- Knowledge base articles or support content outside SitecoreAI
- Any custom application with no crawlable public URL
- Keeping a search index synchronized with an external data source on your own schedule
One thing worth being precise about: a push source is different from pushing updates into a site or content source. You can technically use the Ingestion Service API against a site or content source for a targeted update, but a crawl or re-index will wipe that out and rebuild from scratch. A push source is the only one of the three where your pushed content is the actual source of truth. If the data has to persist, that's the type to use.
Setting Up a Push Source and Testing It
Here's the full path from a blank search source to a document showing up in Preview, using app.sitecorecloud.io
- Log in to app.sitecorecloud.io
- Go to Content → Search Sources.
- Select your search source.
- Click Settings.
- Copy the Search Source ID. This is your 'config_id' and goes in the request URL.
- In the bottom left of the window, click the Settings gear icon.
- Copy the API Endpoint value. This is your 'base_url'.
- Click Create credential.
- Give the credential a name, then click Create.
- Copy the API key immediately. It's shown exactly once. If you close the dialog before copying it, you'll need to create a new one.
- Unzip the attached PowerShell script.
- Replace '<API END POINT HERE>', '<SOURCE ID HERE>', and '<API KEY HERE>' with the values from steps 5, 7, and 10.
- Run the script in PowerShell.
- You should get back a '200' status code.
- Go back to your search source.
- Click Preview.
- Your record should appear within a few seconds.
One thing that trips people up early: your search source needs a minimum of title, description, and url to function in search results. Fields beyond that follow whatever schema you've already defined on the source. The API does not create fields on the fly. If a field isn't already defined in the source schema, the request fails with a straightforward 'field "x" is not defined in config' error.
The Test Script
This is the sample script I used to validate the connection and confirm the document lands in the index. Swap in your own endpoint, source ID, and API key before running it.
$uri = "https://edge-platform.sitecorecloud.io/op/<API END POINT HERE>/search/search-ingestion/v1/index/<SOURCE ID HERE>/push"
$body = @{
operations = @(
@{
action = "upsert"
id = "sku-103"
locale = "en"
sc_url = "https://<websiteurl>/"
fields = @{
title = "Test Title"
description = "Test Description"
}
}
)
} | ConvertTo-Json -Depth 5
try {
$response = Invoke-WebRequest `
-Method Post `
-Uri $uri `
-Headers @{
Authorization = "ApiKey <API KEY HERE>"
} `
-ContentType "application/json" `
-Body $body `
-UseBasicParsing
Write-Host "Status Code: $($response.StatusCode)"
Write-Host "`nResponse Headers:"
$response.Headers | Format-List | Out-String | Write-Host
Write-Host "`nResponse Body:"
Write-Host $response.Content
# Return the complete response object
$response
}
catch {
Write-Host "`nRequest failed."
$r = $_.Exception.Response
if ($null -ne $r) {
Write-Host "Status Code: $([int]$r.StatusCode)"
Write-Host "`nResponse Headers:"
$r.Headers | Format-List | Out-String | Write-Host
Write-Host "`nResponse Body:"
# Windows PowerShell 5.1
if ($r -is [System.Net.HttpWebResponse]) {
$stream = $r.GetResponseStream()
$reader = New-Object System.IO.StreamReader($stream)
try {
Write-Host $reader.ReadToEnd()
}
finally {
$reader.Dispose()
}
}
# PowerShell 7+
elseif ($null -ne $_.ErrorDetails.Message) {
Write-Host $_.ErrorDetails.Message
}
}
else {
Write-Host $_.Exception.Message
}
}A clean run returns a '200' with a 'results' array confirming the operation succeeded. Push multiple documents in one request and it's entirely possible to get a '207 Multi-Status' response, where some operations succeed and others don't. Treat that as a normal response, not a failure. Each result in the array carries its own zero-based 'index' and 'succeeded' flag, so you can map failures back to the exact operation that caused them and retry just those.
A Few Things Worth Knowing Before You Build on This
A handful of details in the spec are easy to miss on a first read but will save you a debugging session later:
- Batch limits are real. A single request accepts 1 to 50 operations and must stay under 512 KiB total. Go over either limit and the entire request is rejected before anything is indexed, not just the excess.
- This writes straight to the live index. There's no dry run or preview-before-publish step. Whatever you push is live as soon as the request succeeds.
- Field names are case-sensitive. Anything prefixed with 'sc_' or named '@search.action' is reserved and silently ignored inside 'fields'.
- 'upsert' merges, it doesn't replace. Fields you omit keep their existing values. If you need to clear a field, send it as an empty string rather than leaving it out.
- Nothing is retained server-side. Push sources don't keep a history of what's been submitted. If you ever need to rebuild the index, you're resending the full document set, not replaying a log.
- Datetime fields are picky. Use the compact 'YYYYMMDDTHHMMSSZ' format. Standard ISO 8601 timestamps get rejected outright.
Where This Fits
For anyone running SitecoreAI with content that genuinely lives outside the platform, this closes a real gap. Before this, your options were a crawlable endpoint that may not have existed or living with a blind spot in search. Now it's a straightforward POST request with a predictable response shape and clear error messages when something's off.
It's a new feature and still rolling out in phases, so don't be surprised if it isn't live in your org yet. But if you've got external content that's been sitting outside search because there was no good way to get it in, this is worth setting up. The whole loop, from credential creation to seeing a record in Preview, took me a few minutes once I had the right values in hand.

