BoxProbe
Public lab Open-source e-commerce Reproducible

Medusa Admin API behavior changed under real UI flows

Between versions 2.13.6 and 2.14.0, the Medusa admin UI continued to function normally. All 15 scenarios passed. But browser-side API responses changed in ways no UI test would catch — specifically, additive field changes to two domain resources. We discovered this with scout; the analysis below is BoxProbe's editorial — the raw scout report is linked at the bottom.

15
Scenarios
15
Passed
921
Endpoints
264
UI steps
8 structure 7 value 0 status 0 endpoint +/− 906 clean
Headline finding structure

ProductCategoryDTO gained an external_id field

Found in scenario
categories/categories-crud
UI action (step 9)
Click Save on Create Category form
Primary endpoint
POST /admin/product-categories
Diff
+ $.product_category.external_id: null

Same field also appears on: GET /admin/product-categories/{id} (×2), POST /admin/product-categories/{id}

intentional · lock-down candidate

Adding `external_id: string | null` follows a Medusa 2.14 pattern of exposing third-party identifier fields on domain objects (also observed on Collection in the same release). The field is null on creation through the admin UI, which suggests the path is server-set via integrations, not user-set via the dashboard. UI continues to render correctly without it — but downstream integrations parsing this response with strict schema validation would encounter an unexpected field.

Suggested native test

A small integration test in Medusa's existing Jest suite asserting `external_id` presence in the `POST /admin/product-categories` response body. No new runtime dependencies, no browser runner — the kind of regression fixture that locks the contract without imposing tooling on maintainers.

How we found it: scout drove categories-crud step 9 through the admin UI and recorded baseline (2.13.6) vs target (2.14.0) API traffic.

CollectionDTO gained the same external_id field

structure
collections/collections-crud · POST /admin/collections
+ $.collection.external_id: null

Also on: GET /admin/collections, GET /admin/collections/{id} (×2)

intentional · same pattern as ProductCategory

Confirms the headline finding is part of a deliberate architectural change in 2.14: external_id is becoming a uniform attribute across multiple domain models, not a one-off addition. Worth lock-down for the same downstream-integration reasons.

No status-code or endpoint inventory changes

meta

Zero status-code mismatches across 921 paired endpoints. Zero endpoints appeared or disappeared between versions. Auth, customers, inventory, draft-orders, orders all returned the same status codes for the same UI actions.

On the dimensions where regressions are most painful for downstream consumers (auth flows breaking, endpoints moving, status semantics changing), 2.13.6 → 2.14.0 is clean. The drift is contained to the additive field changes above.

All 7 value diffs are mock-data noise, not behavior drift

noise

Values like `address_1: "test-a9a1e3" → "test-283ab7"` and `handle: "test-cf100e" → "test-93a998"` differ because the scenario uses randomized test input each run, not because Medusa's behavior changed.

In production use of scout, these get suppressed via `diff_ignore.json` value-type rules (`mock_name`, etc.). They surface here intentionally to demonstrate scout's separation of noise filtering from real findings — and the result is that 7 "value diffs" become 0 once filtered, leaving the 2 real structural findings clearly visible.

13 of 15 scenarios show no API drift

meta

Auth (3), api-keys (2), campaigns, customers (2), draft-orders, inventory (2), locations, orders — all clean. Only categories and collections show structural change.

scout's noise filtering kept the report focused. For users evaluating the method: the signal-to-noise ratio at default settings is high — out of 264 UI steps and 921 captured endpoints, only 8 surface as worth reading.

15 admin user flows

Test code lives in github.com/boxprobe/scout-medusa.

auth/login-and-logout
6 steps
critical
auth/login-with-invalid-credentials
4 steps
critical
auth/protected-routes-redirect-unauthenticated-users
3 steps
critical
api-keys/publishable-api-keys-crud
21 steps
api-keys/secret-api-keys-create
18 steps
campaigns/campaigns-crud
23 steps
categories/categories-crud
23 steps
+1 field
collections/collections-crud
19 steps
+1 field
customers/customer-groups-crud
18 steps
customers/customers-crud
23 steps
critical
draft-orders/draft-orders-crud
26 steps
critical
inventory/inventory-crud
23 steps
inventory/inventory-search-and-filter
12 steps
locations/location-edit
21 steps
orders/orders-export
24 steps
Native test PR — not yet filed

BoxProbe's default approach for upstream contributions: the artifact submitted upstream is a native test in the project's existing framework — no scout dependency, no new CI workflow, no generated reports in the upstream repo. scout is the discovery layer; the upstream test is what maintainers own.

Pending — issue not yet filed

Public lab analysis published before contacting upstream. Per BoxProbe's upstream PR strategy, the next step is to file an issue with medusajs/medusa proposing native regression coverage for the two findings — using Medusa's existing Jest integration test setup, no scout dependency on the upstream side.

Run this on your machine

Every number above is reproducible. scout is the open-source executor, scout-medusa is the demo repo with Docker compose for both Medusa versions plus all 15 scenarios.

# 1. Clone the demo repo
git clone https://github.com/boxprobe/scout-medusa
cd scout-medusa

# 2. Start both Medusa versions (5-10 min first time while images build)
bash compose/start.sh

# 3. Install scout
pip install boxprobe-scout
playwright install chromium

# 4. Record against baseline (v2.13.6 @ localhost:19000)
cd admin
scout run scenarios/ --web-version 2.13.6

# 5. Record against target (v2.14.0 @ localhost:29000)
scout run scenarios/ --web-version 2.14.0 \
    --web-base-url http://localhost:29000/app \
    --api-base-url http://localhost:29000

# 6. Diff
scout runs
scout diff <baseline-id> <target-id>
github.com/boxprobe/scout-medusa About Scout

scout produces a self-contained HTML report alongside this analysis: filterable endpoint table, popup body diffs per row, structural / value / status categorization, known-change suppression. Use it if you want to inspect every paired endpoint, not just the headline findings.

Open scout's raw report →

Want this for your application?

Send a URL and a user scenario. We'll run scout against your baseline and target and share the analysis.

Get a Free Sample Report