If your product feed sync started failing intermittently with HTTP 410 since September 1, 2026, it's not your code randomly breaking. Google retired the Content API for Shopping (v2.1) on August 18, 2026, and requests from projects without an active extension now fail some of the time with a content_api_sunset error. In early 2027 all requests stop working (Google's deprecation page).
This post is for people who maintain a custom integration (a script, a cron job, a homegrown feed service). If you're on a mainstream ecommerce platform or app, the platform/app vendor usually handles the move.
1. Recognise the error
The artificial 410 looks like this (trimmed):
{
"error": {
"code": 410,
"status": "ABORTED",
"details": [{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "content_api_sunset",
"domain": "shoppingcontent.googleapis.com",
"metadata": { "GCP_ID": "...", "GCP_NUMBER": "...", "HELP_CENTER_LINK": "..." }
}]
}
}
Two practical points:
- Retrying is not a fix. The failures are deliberate and will get worse.
- Log this reason explicitly. A lot of sync jobs swallow non-2xx responses, so products quietly go stale in Merchant Center.
2. Setup steps that trip people up
These are separate from rewriting your calls:
- Enable the Merchant API in your Google Cloud project.
-
Register the project once with
developerRegistration:registerGcpon your (primary, production) Merchant Center account. If you skip it you get401 UNAUTHENTICATEDsaying the GCP project "is not registered". Allow a few minutes after registering. -
Create an API-input data source.
productInputs.insertrequires adataSourcequery parameter. Careful: inserting an offer that's currently owned by another data source transfers ownership. - Auth is OAuth 2.0 or a service account with admin access on the Merchant Center account.
3. The call changes that matter most
| Content API v2.1 | Merchant API | Watch out for |
|---|---|---|
products.insert |
productInputs.insert |
dataSource required; attributes move under productAttributes
|
products.delete |
productInputs.delete |
needs dataSource too |
products.custombatch |
no equivalent | parallel calls, or HTTP multipart batch (max 2,000 requests per batch) |
productstatuses.* |
products.get / products.list
|
status lives in productStatus
|
datafeeds.fetchnow |
dataSources.fetch |
Other changes that break things silently:
-
Product IDs:
channel:contentLanguage:targetCountry:offerIdbecomesaccounts/{account}/products/{contentLanguage~feedLabel~offerId}. No channel segment,~as the delimiter, and base64url-encode IDs that contain characters like/,:or~. -
Prices:
{"value": "33.45", "currency": "USD"}becomes{"amountMicros": "33450000", "currencyCode": "USD"}. -
targetCountrybecomesfeedLabel; enum-like strings become enums (IN_STOCK,NEW).
Always check details against Google's compatibility guide.
4. Find every old call in your code
I put together a small, dependency-free scanner that lists every Content API call it can find (Python googleapiclient, PHP Google_Service_ShoppingContent, Node googleapis, raw REST) with the Merchant API replacement and a risk note:
git clone https://github.com/trilliumcompany/merchant-migration-scan
python3 merchant-migration-scan/merchant_migration_scan.py /path/to/your/repo
It's a static text scan: no network calls, no credentials. It can miss dynamically built calls and can't tell you whether your project has an extension, so treat the output as a checklist, not proof.
Disclosure / if you want help
I'm Peter from The Trillium Company (Waterloo, Canada), which maintains the scanner. We also offer a paid, fixed-price 48-hour diagnostic of one integration for CAD $490: you send sanitized code and recent error logs, and we send back a call-by-call compatibility report, the setup steps that apply to you, and a prioritized repair and test plan. It's analysis and a plan, not code changes, and we confirm your inputs are enough before you pay. Contact: hello@thetrilliumcompany.com.
Happy to answer questions in the comments either way.
Top comments (0)