Platform API
An HTTP surface over the engine that already runs behind the UCP Fluent Shopify app: catalog ingest from the Shopify Admin API, enrichment behind confidence gates, and publishing to Merchant Center, product-page JSON-LD and Shopify's agent catalog.
There is no public sandbox. Credentials and a base URL are issued per partner. The operations below — ingest, gated enrichment, three-rail publishing — run in production today inside the Shopify app. What is in beta is this HTTP surface over them, including the job callbacks, so treat these shapes as a working draft rather than a frozen contract. Request access: anri@ucpfluent.com · info@ucpfluent.com
Bearer token on every request. Tokens are issued per partner and scoped to the shops that have installed the app — a token cannot reach a merchant who has not consented.
Authorization: Bearer <partner-token>
Idempotency-Key: <uuid>
Content-Type: application/jsonBase URL is issued with your credentials. All timestamps are ISO 8601, UTC.
Reads the full catalog from Shopify's Admin API — 250 products per page, cursor-paginated through the Link header — together with 90 days of orders, so per-variant sales aggregates land in the same snapshot.
A large catalog takes minutes to walk, so this returns 202 Accepted with a job_id rather than blocking. Poll the readiness endpoint, or supply callback_url and we POST once the job reaches a terminal state. Enrichment then runs behind confidence gates: high-confidence attributes are eligible to publish, uncertain ones are held for merchant review and counted in the readiness response. Nothing uncertain ships unreviewed.
| Field | Type | Description |
|---|---|---|
| shop | string * | The merchant’s myshopify.com domain. Must have UCP Fluent installed. |
| callback_url | string | HTTPS endpoint we POST the terminal job state to. Omit it to poll instead. |
{
"shop": "example-store.myshopify.com",
"callback_url": "https://partner.example.com/hooks/ucp"
}{
"job_id": "job_9f3c1a4e",
"status": "queued",
"shop": "example-store.myshopify.com",
"submitted_at": "2026-08-26T09:14:22Z"
}The catalog's current state: the last ingest job, per-rail publish status, and how many attributes are still waiting on merchant approval.
readiness_score is our own 0–100 diagnostic, and pillars shows exactly how it is composed: identity 35, semantics 25, discovery 25, conversational 15. It is a number we compute to tell you where a catalog is weak — no search engine or agent reads it, and it is not a ranking signal. Each entry in surfaces carries a state of synced, publishing, stale or blocked.
| Field | Type | Description |
|---|---|---|
| shop | string * | The same myshopify.com domain used at ingest. |
{
"shop": "example-store.myshopify.com",
"last_ingest": {
"job_id": "job_9f3c1a4e",
"status": "complete",
"variants_ingested": 1450,
"completed_at": "2026-08-26T09:21:40Z"
},
"readiness_score": 82,
"pillars": {
"identity": { "score": 31, "max": 35 },
"semantics": { "score": 22, "max": 25 },
"discovery": { "score": 25, "max": 25 },
"conversational": { "score": 4, "max": 15 }
},
"surfaces": {
"merchant_center": {
"state": "synced",
"last_published_at": "2026-08-26T09:22:05Z"
},
"jsonld_anchor": { "state": "published" },
"shopify_catalog": { "state": "synced" }
},
"attributes_held_for_review": 37
}Pushes every approved, gate-passing value out to the rails you name. Writing back to Shopify goes through bulkOperationRunMutation — one staged JSONL upload and one bulk job, instead of thousands of per-variant mutations against the rate limit.
Shopify runs one bulk operation per shop at a time, so we check currentBulkOperation before submitting and return 409 rather than queue behind a job we did not start. Values still held for merchant review are never included; they come back as values_held.
| Field | Type | Description |
|---|---|---|
| rails | array | Any of merchant_center, jsonld_anchor, shopify_catalog. Omit to publish all three. |
{
"rails": ["merchant_center", "shopify_catalog"]
}{
"job_id": "job_5b81d240",
"status": "queued",
"rails": ["merchant_center", "shopify_catalog"],
"values_queued": 1183,
"values_held": 37
}Failure modes
| Status | Code | When |
|---|---|---|
| 202 | accepted | Ingest queued. The body carries the job_id to poll. |
| 400 | invalid_request | Malformed body, an unknown rail name, or a callback_url that is not HTTPS. |
| 401 | invalid_token | Missing, expired or malformed bearer token. |
| 403 | app_not_installed | The shop has not installed UCP Fluent, or your token is not scoped to it. |
| 404 | catalog_not_found | No completed ingest exists for this shop yet. |
| 409 | job_in_progress | A job of the same kind is already running for this shop. On publish this is Shopify’s own limit — one bulk operation per shop — which we check via currentBulkOperation before submitting, and refuse rather than queue behind. |
| 429 | rate_limited | Retry after the interval in the Retry-After header. |
| 503 | upstream_unavailable | Shopify or Merchant Center rejected or throttled the call. Safe to retry with the same Idempotency-Key. |
- One job per shop, per kind. Shopify permits a single bulk operation per shop at a time, so a second
publishreturns 409 with the running job's id instead of queuing. Ingest is serialised the same way, by us rather than by Shopify. - Idempotency. A repeated
Idempotency-Keyon POST returns the original job rather than starting a second one. - Versioning. Breaking changes ship under a new path prefix. While the beta is open,
/v1shapes can still change and partners are told before they do. - Attribution scope. A hard receipt arms only when the visit carries our
utm_source=ucp_fluentparameters — the tagging we add to links in the merchant's Merchant Center feed. A click from an assistant that does not carry them produces no receipt. We report that gap rather than estimate across it, and never bill on traffic we cannot evidence. - Merchant consent. Every call is bounded by the merchant's own Shopify install and the scopes they granted. There is no path to a catalog whose owner has not installed the app.
{
"error": {
"code": "job_in_progress",
"message": "A bulk operation is already running.",
"job_id": "job_9f3c1a4e"
}
}Production infrastructure
Three planes, all in us-central1. This is the stack serving merchants today — the Platform API is a new entry point onto it, not a separate system.
App & orchestration
- Cloud Run
- Cloud SQL · Postgres
- Memorystore · Redis
Serves the embedded Shopify admin app, holds job state and merchant sessions in Postgres, and drives webhook-triggered re-sync. Redis carries the job queue and the enrichment cache.
Extract, classify, gate
- Cloud Run
- Vertex AI · Gemini
- GPC brick classification
Reads the product page and images, extracts attributes, classifies each product to a GPC brick, and scores every value. Values below the confidence gate are held for merchant review rather than published.
One record, three rails
- BigQuery lakehouse
- Merchant Center API
- Theme app embed · metafields
The enriched record lands in BigQuery, then publishes out: Merchant Center through its API, JSON-LD onto the product page through the theme app embed, and catalog metafields onto Shopify’s agent surface.