[FROSTLABS] · home / writing / mto qty pattern
2026-05-03 · 6-min read · Odoo · Amazon SP-API · MTO

Made-to-order on Odoo + Amazon: why qty=999 is intentional and product.free_qty is the wrong field.

Made-to-order manufacturers on Odoo have an inventory pattern that breaks naive marketplace connectors: qty=999 on the marketplace listing, free_qty=0 on the product master. Most connectors read product.free_qty as the source of truth for inventory and push the wrong number to Amazon. The result: every MTO listing goes out-of-stock on Amazon within hours of a publish cycle, sales stop, you don't know why. The fix is reading a different field. Here's the pattern.

$The MTO inventory paradox

For a standard-inventory e-commerce business, "how much do I have" is a straightforward question. Odoo's product.free_qty (or its older sibling qty_available) gives you the answer, and pushing that number to Amazon is the right thing to do.

For a made-to-order business, the question doesn't make sense. The product doesn't exist until someone orders it. There are no units sitting in a warehouse. The relevant inventory question is "how many can I make in the production lead time," and the answer is usually "as many as anyone orders." Bounded by capacity, not by stock.

Amazon doesn't have a concept of "inventory = my production capacity." Amazon has a concept of "this many units are buyable right now." For an MTO seller, both numbers are technically zero (no stock) and effectively infinite (unbounded production). The convention that works: tell Amazon you have 999 units of every listing. This is large enough that Amazon won't run you out before you can fulfill new orders, and small enough that Amazon doesn't flag the listing as suspicious.

The pattern in Odoo:

The two qty fields disagree on purpose. The product master reflects reality (no inventory). The marketplace listing reflects the message you're sending to the marketplace.

$The bug pattern: connector reads product.free_qty

A naive Odoo + Amazon connector wires inventory sync as:

# Wrong for MTO businesses.
def sync_inventory(env, listing):
    product = listing.product_id
    amazon_qty = int(product.free_qty)
    sp_client.put_listing_attribute(
        seller=listing.seller_id,
        sku=listing.amazon_sku,
        attribute="fulfillment_availability",
        value=[{"quantity": amazon_qty, "lead_time_to_ship_max_days": 10}],
    )

For a standard-inventory business: correct. free_qty tracks reality, gets pushed to Amazon, Amazon shows the listing as in-stock for that quantity.

For an MTO business: catastrophic. free_qty is 0 by design because production hasn't run. The connector pushes quantity=0 to Amazon. Amazon suppresses the listing with "out of stock." Sales stop. The on-call channel goes quiet because there are no orders to process. The seller looks at the Seller Central dashboard, sees "out of stock," and asks the engineering team why inventory isn't syncing, when in fact it's syncing perfectly to the wrong field.

The pattern is especially bad for MTO businesses with seasonality. The pattern catches you mid-launch: you announce a new product, listings go live, the first sync cycle fires free_qty=0 and Amazon suppresses everything before anyone has ordered.

$The fix: read the listing's own qty

The corrected sync reads from the marketplace listing model, not from the product master:

# Correct for both standard inventory AND MTO.
def sync_inventory(env, listing):
    # Read from the listing-specific qty field, not product.free_qty.
    amazon_qty = int(listing.qty)
    sp_client.put_listing_attribute(
        seller=listing.seller_id,
        sku=listing.amazon_sku,
        attribute="fulfillment_availability",
        value=[{
            "quantity": amazon_qty,
            "lead_time_to_ship_max_days": listing.product_id.sale_delay or 10,
        }],
    )

Two things change:

If your Odoo schema doesn't have a marketplace.listing.qty field separately from the product, add it. It's a simple integer with a default of 999. Set the default to 999 for any product flagged as MTO; set it to mirror product.free_qty for non-MTO products.

$Why qty=999 specifically

Three numbers people consider:

The 999 number is the de facto convention. It's what Amazon's own Vendor Central documentation suggests for "demand-driven" listings, and what most experienced MTO sellers use.

$What about capacity-bounded MTO?

Some MTO businesses have actual production capacity limits. A custom-leather-goods shop might be able to ship 12 wallets per week, so putting qty=999 on each listing means the seller is committing to capacity they don't have. Late shipments cascade.

The fix for capacity-bounded MTO: listing.qty is set to (weekly_capacity × weeks_in_lead_time) - currently_in_production, refreshed daily. So if you can ship 12/week, your lead time is 2 weeks, and you have 8 already in production, your effective Amazon inventory is (12 × 2) - 8 = 16. Customers ordering above that get a longer lead time displayed; the seller doesn't get over-committed.

This is more sophisticated than the 999 default and requires explicit capacity modeling in Odoo. For most MTO businesses, 999 is fine because capacity isn't the real bottleneck; production timing is, and that's handled via lead_time_to_ship_max_days.

$How to audit whether your connector has this bug

Five-minute diagnostic. Open Odoo's developer mode, navigate to any MTO product, check the linked marketplace listing record. Look at the qty field on the listing vs the product:

# In an Odoo shell or via xmlrpc:
listing = env["marketplace.listing"].search([
    ("product_id.name", "=", "Your MTO Product"),
    ("channel", "=", "amazon"),
])
print("product.free_qty:", listing.product_id.free_qty)
print("listing.qty:", listing.qty)

If product.free_qty is 0 and listing.qty is also 0 → your connector is reading the wrong field. The MTO listing on Amazon is showing as out-of-stock.

If product.free_qty is 0 and listing.qty is 999 → the schema is right, but check the actual SYNC code path. Maybe the field exists but isn't being read by the publish flow. Trace the connector's inventory-update code path back from the SP-API call to find where the qty value originates.

For a real "is this happening on my catalog" answer: pull every active MTO listing's current Amazon quantity via GET /listings/2021-08-01/items/{seller}/{sku}?includedData=attributes and look at the fulfillment_availability.quantity field. If your MTO listings are showing 0, the bug is operating in production.

$One more wrinkle: per-marketplace qty

If you sell on multiple Amazon marketplaces (US, UK, DE, etc.), fulfillment_availability takes a list of objects, one per marketplace. The connector needs to push the qty per marketplace, not a single qty applied to all marketplaces. For MTO businesses with multi-region distribution, the per-marketplace qty can be different (e.g., 999 in the US for your home market with full production, but 10 in DE for limited customs-cleared inventory in the EU).

The schema:

{
  "attributes": {
    "fulfillment_availability": [
      {"marketplace_id": "ATVPDKIKX0DER", "quantity": 999, "lead_time_to_ship_max_days": 10},
      {"marketplace_id": "A1F83G8C2ARO7P", "quantity": 10, "lead_time_to_ship_max_days": 14}
    ]
  }
}

If your connector pushes a single quantity field applied uniformly across all marketplaces, you lose the ability to differentiate. For MTO businesses with limited regional inventory, this matters.

By David H. Frost · Frost Labs LLC More writing · Home · Privacy