Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Connect Odoo with Shopify: sync products, inventory, orders, and customers using the Shopify API and Odoo's external API or connector modules.
.claude/skills/odoo-shopify-integration/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✓→✓ | = Same ✓ | — | — |
| case-15 | ✗→✗ | = Same ✗ | — | — |
| case-06 | ✗→✗ | = Same ✗ | — | — |
| case-17 | ✗→✗ | = Same ✗ | — | — |
This skill guides you through integrating Odoo with Shopify — syncing your product catalog, real-time inventory levels, incoming orders, and customer data. It covers both using the official Odoo Shopify connector (Enterprise) and building a custom integration via Shopify REST + Odoo XMLRPC APIs.
@odoo-shopify-integration and describe your sync scenario.SHOPIFY ODOO
-------- ----
Product Catalog <──────sync────── Product Templates + Variants
Inventory Level <──────sync────── Stock Quants (real-time)
New Order ───────push──────> Sale Order (auto-confirmed)
Customer ───────push──────> res.partner (created if new)
Fulfillment <──────push────── Delivery Order validatedpythonimport xmlrpc.client, requests # Odoo connection odoo_url = "https://myodoo.example.com" db, uid, pwd = "my_db", 2, "api_key" models = xmlrpc.client.ServerProxy(f"{odoo_url}/xmlrpc/2/object") def create_odoo_order_from_shopify(shopify_order): # Find or create customer partner = models.execute_kw(db, uid, pwd, 'res.partner', 'search_read', [[['email', '=', shopify_order['customer']['email']]]], {'fields': ['id'], 'limit': 1} ) partner_id = partner[0]['id'] if partner else models.execute_kw( db, uid, pwd, 'res.partner', 'create', [{ 'name': shopify_order['customer']['first_name'] + ' ' + shopify_order['customer']['last_name'], 'email': shopify_order['customer']['email'], }] ) # Create Sale Order order_id = models.execute_kw(db, uid, pwd, 'sale.order', 'create', [{ 'partner_id': partner_id, 'client_order_ref': f"Shopify #{shopify_order['order_number']}", 'order_line': [(0, 0, { 'product_id': get_odoo_product_id(line['sku']), 'product_uom_qty': line['quantity'], 'price_unit': float(line['price']), }) for line in shopify_order['line_items']], }]) return order_id def get_odoo_product_id(sku): result = models.execute_kw(db, uid, pwd, 'product.product', 'search_read', [[['default_code', '=', sku]]], {'fields': ['id'], 'limit': 1}) return result[0]['id'] if result else False
pythonfrom flask import Flask, request app = Flask(__name__) @app.route('/webhook/shopify/orders', methods=['POST']) def shopify_order_webhook(): shopify_order = request.json order_id = create_odoo_order_from_shopify(shopify_order) return {"odoo_order_id": order_id}, 200
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.