Ecommerce

Akeneo Magento & Shopify Connectors: Patterns That Keep Catalogs in Sync

How to design Akeneo → Magento and Akeneo → Shopify connectors that respect ownership, map variants correctly, and fail in ways catalog teams can fix.

Akeneo Magento & Shopify Connectors: Patterns That Keep Catalogs in Sync
Ecommerce 14 min read

Akeneo Magento and Shopify connectors are the most common Akeneo integration requests we see—and the easiest place for silent catalog drift. This guide covers the patterns Spygar uses so storefronts stay aligned with PIM truth, including the real REST calls from the official Akeneo API reference index.

A connector is a product contract—if ownership is fuzzy, every sync becomes a conflict.

Ownership first

Akeneo should own marketing content, structured attributes, categories (as modeled), associations, and assets. Magento/Shopify typically own price, inventory, and orderable status. Write the RACI before mapping fields. Ambiguity here is the root of most “sync bugs.”

Authenticate the connector

Every Magento/Shopify sync worker needs a short-lived Bearer token from POST /api/oauth/v1/token. Store client id/secret and connection credentials in a secrets manager—never in the storefront.

POST /api/oauth/v1/token Official docs

Get an authentication token

Exchange connection credentials for a Bearer access token. No prior Bearer auth is required for this call.

Headers

  • Content-Type: application/json (or application/x-www-form-urlencoded)
  • Authorization: Basic {base64(client_id:client_secret)}

Parameters

Name In Type Required Description
username body string Yes PIM connection username
password body string Yes PIM connection password
grant_type body string Yes Must be password

Example request

curl -X POST "https://YOUR_PIM_HOST/api/oauth/v1/token" \
  -H "Content-Type: application/json" \
  -H "Authorization: Basic $(echo -n 'CLIENT_ID:CLIENT_SECRET' | base64)" \
  -d '{
    "username": "your_connection_username",
    "password": "your_connection_password",
    "grant_type": "password"
  }'

Response — 200 OK

Returns an authentication token used as Authorization: Bearer on later calls.

{
  "access_token": "ZTZmYjU4ZmQxZWNmMzk1M2NlYzA5NmFhNmIzVjExMzE4NmJmODBkZGIyYTliYmQyNjk2ZDQwZThmNjdiZDQzOQ",
  "expires_in": 3600,
  "token_type": "bearer",
  "scope": null,
  "refresh_token": "M3FlODI0OTE3ODMyNjViMzRiOWE5ODMyNWViMThkNDU5YzJjNjFiZjNkZWFjMzIyYjc4YTgzZWY1MjE5ZTY5Mw"
}

Response — 400 Bad Request

Malformed JSON or invalid request framing.

{
  "code": 400,
  "message": "Invalid JSON message received"
}

Response — 422 Unprocessable Entity

Validation failed (wrong grant_type, bad credentials shape, etc.).

{
  "code": 422,
  "message": "Property \"grant_type\" expects a valid grant type. Check the expected format on the API documentation."
}

Pull changed products from Akeneo

Use GET /api/rest/v1/products-uuid with pagination_type=search_after, channel scope, target locales, and with_completenesses=true. Filter on updated for incremental sync. Full parameter and response shapes are documented by Akeneo here.

GET /api/rest/v1/products-uuid Official docs

Get list of products (UUID)

Preferred product list endpoint. Paginated, filterable, and permission-aware. Prefer pagination_type=search_after for large catalogs.

Headers

  • Authorization: Bearer {access_token}
  • Accept: application/json

Parameters

Name In Type Required Description
search query string (JSON) No Filter products (see Filters docs)
scope query string No Return scopable values for this channel (+ non-scopable)
locales query string No Comma-separated locales for localizable values
attributes query string No Comma-separated attribute codes to return
pagination_type query string No page (default) or search_after
page query integer No Page number when pagination_type=page (default 1)
search_after query string No Cursor for search_after pagination (do not invent manually)
limit query integer No Page size (default 10)
search_scope query string No Default scope for multi-attribute filters
search_locale query string No Default locale for multi-attribute filters
with_count query boolean No Include items_count (can be expensive)
with_attribute_options query boolean No Include option labels (linked_data)
with_quality_scores query boolean No Include quality scores
with_completenesses query boolean No Include completenesses
with_root_parent query boolean No Include root parent model code for variants

Example request

curl -G "https://YOUR_PIM_HOST/api/rest/v1/products-uuid" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Accept: application/json" \
  --data-urlencode 'pagination_type=search_after' \
  --data-urlencode 'limit=20' \
  --data-urlencode 'scope=ecommerce' \
  --data-urlencode 'locales=en_US,fr_FR' \
  --data-urlencode 'attributes=sku,name,description' \
  --data-urlencode 'with_completenesses=true' \
  --data-urlencode 'search={"updated":[{"operator":">","value":"2026-01-01 00:00:00"}]}'

Response — 200 OK

Paginated product collection with navigation links.

{
  "_links": {
    "self": {
      "href": "https://YOUR_PIM_HOST/api/rest/v1/products-uuid?pagination_type=search_after&limit=20"
    },
    "first": {
      "href": "https://YOUR_PIM_HOST/api/rest/v1/products-uuid?pagination_type=search_after&limit=20"
    },
    "next": {
      "href": "https://YOUR_PIM_HOST/api/rest/v1/products-uuid?pagination_type=search_after&search_after=CURSOR&limit=20"
    }
  },
  "_embedded": {
    "items": [
      {
        "_links": {
          "self": {
            "href": "https://YOUR_PIM_HOST/api/rest/v1/products-uuid/25566245-55c3-42ce-86d9-8610ac459fa8"
          }
        },
        "uuid": "25566245-55c3-42ce-86d9-8610ac459fa8",
        "enabled": true,
        "family": "tshirt",
        "categories": ["summer_collection"],
        "groups": [],
        "parent": null,
        "values": {
          "sku": [{"data": "top", "locale": null, "scope": null}],
          "name": [
            {"data": "Top", "locale": "en_US", "scope": null},
            {"data": "Débardeur", "locale": "fr_FR", "scope": null}
          ]
        },
        "created": "2026-01-05T10:00:00+00:00",
        "updated": "2026-03-01T12:30:00+00:00",
        "completenesses": [
          {"scope": "ecommerce", "locale": "en_US", "data": 100}
        ]
      }
    ]
  }
}

Response — 401 Unauthorized

Missing/expired token or invalid credentials.

{
  "code": 401,
  "message": "Authentication is required"
}

Response — 422 Unprocessable Entity

Invalid search JSON or unsupported query combination.

{
  "code": 422,
  "message": "Property \"search\" expects a valid search. Check the expected format on the API documentation."
}

Variant mapping is the hard part

Akeneo product models must translate cleanly into Magento configurables or Shopify variants. Wrong axes create duplicate PDPs or missing options. Prototype with your hardest family and verify storefront UX before scaling the connector.

  • Stable external IDs: prefer Akeneo UUID as the immutable key
  • Incremental upserts with idempotent writers into Magento/Shopify
  • Separate structure sync from content/media sync when helpful
  • Canary SKUs that detect silent freezes

Media and completeness gates

Do not publish products missing required hero/gallery roles. Upload or relink media via POST /api/rest/v1/media-files when Akeneo is the asset source, and only push storefronts when completeness thresholds pass.

POST /api/rest/v1/media-files Official docs

Create a new product media file

Upload a binary and associate it to a product (or product model) attribute value via multipart form-data.

Headers

  • Authorization: Bearer {access_token}
  • Content-Type: multipart/form-data (with boundary)

Parameters

Name In Type Required Description
product form string (JSON) No {"identifier":"...","attribute":"...","scope":"...","locale":"..."} — use product OR product_model, not both
product_model form string (JSON) No Same shape targeting a product model
file form binary Yes Media binary

Example request

curl -X POST "https://YOUR_PIM_HOST/api/rest/v1/media-files" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -F 'product={"identifier":"top","attribute":"image","scope":null,"locale":null}' \
  -F "file=@./hero-top.jpg;type=image/jpeg"

Response — 201 Created

Media created and linked. Location header contains the media resource URI.

Headers:
Location: /api/rest/v1/media-files/f/i/l/e/hash_hero-top.jpg

Body: (empty)

Response — 422 Unprocessable Entity

Invalid product JSON, unknown attribute, or file validation failure.

{
  "code": 422,
  "message": "Validation failed.",
  "errors": [
    {
      "property": "product",
      "message": "The product does not exist."
    }
  ]
}

Push content updates back? Prefer PATCH upserts

When ERP or enrichment tools write into Akeneo (not the storefront), use PATCH /api/rest/v1/products-uuid/{uuid} for idempotent upserts—including add_categories / remove_categories when you must not clobber taxonomy.

PATCH /api/rest/v1/products-uuid/{uuid} Official docs

Update/create a product (UUID)

Upsert by UUID: updates if present, creates if missing. Supports add_categories / remove_categories. EE may create a draft when rights are limited.

Headers

  • Authorization: Bearer {access_token}
  • Content-Type: application/json

Parameters

Name In Type Required Description
uuid path string Yes Product UUID
create_missing_options query string No Auto-create missing select options when allowed
update_parent_values query boolean No When true, allow updating parent model values while patching a variant
enabled body boolean No Enable/disable product
family body string|null No Family code
categories body array[string] No Replace category set
add_categories body array[string] No Add categories (preserve existing)
remove_categories body array[string] No Remove categories (preserve others)
parent body string|null No Parent product model
values body object No Partial attribute values to merge

Example request

curl -X PATCH "https://YOUR_PIM_HOST/api/rest/v1/products-uuid/25566245-55c3-42ce-86d9-8610ac459fa8" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "add_categories": ["winter_collection"],
    "values": {
      "name": [
        {"data": "Top — refreshed", "locale": "en_US", "scope": null}
      ],
      "description": [
        {"data": "Updated summer top copy", "locale": "en_US", "scope": "ecommerce"}
      ]
    }
  }'

Response — 204 No Content

Product updated successfully (empty body).

Body: (empty)

Response — 201 Created

No product existed for UUID; product was created. Location header returned.

Headers:
Location: /api/rest/v1/products-uuid/25566245-55c3-42ce-86d9-8610ac459fa8

Body: (empty)

Response — 422 Unprocessable Entity

Validation failed on patched payload.

{
  "code": 422,
  "message": "Validation failed.",
  "errors": [
    {
      "property": "values",
      "message": "The name attribute requires a value in the locale en_US."
    }
  ]
}

Failure handling humans can use

Retries, dead-letter queues, and UUID-level messages beat “check the logs.” Add a reconciliation job that re-lists via products-uuid periodically. For a full integration engagement—including ERP and marketplaces—see our Akeneo integration services and the deeper Akeneo API integration guide.

Build vs buy

Evaluate existing connectors when your model is standard. Build or extend when transforms, volume, or variant complexity exceed what packages handle cleanly—using Akeneo’s job/step pattern documented in How to create a custom Akeneo connector (official guide: create-connector.html). Either way, treat the connector as a supported product with monitoring and a runbook—not a weekend script.

Ready to start your next project?

Let's work together to bring your ideas to life.