SeriesPart 20 of Building a property procurement platform on MedusaView the cluster →
Medusa & ArchitectureArticle

One Medusa backend, three applications: keeping buyers, sellers and operators in their own lane

How we separated Store, Vendor and Admin capabilities in a Medusa marketplace, then enforced seller, organisation, property and role scope on each resource.

We Are Souk article cover: One Medusa backend, three applications: keeping buyers, sellers and operators in their own lane
Souk EngineeringCommerce architectureAug 2026·11 min read
Key takeaways
  • A marketplace order belongs to a supplier.
  • Should the API return the order?
  • The answer cannot depend on whether the person knows the order ID.
  • This became a central client problem while we were building a multi-sided procurement platform on Medusa.

The client problem

A marketplace order belongs to a supplier. It was placed for one property inside one buying organisation. An account manager opens the seller portal and asks to see it.

Should the API return the order?

The answer cannot depend on whether the person knows the order ID. It depends on a chain of business facts: which seller they represent, which role they hold, which properties that role covers and whether the order belongs to one of those properties.

This became a central client problem while we were building a multi-sided procurement platform on Medusa. The same commerce engine had to power three very different applications: the marketplace storefront used by buyers, the Vendor Panel used by suppliers and the Admin used by platform operators.

Medusa gives each route family a useful technical home. It does not automatically understand our client's organisations, properties, seller territories or operational roles. We had to turn those business boundaries into an access model that remained dependable as the product grew.

The solution was not simply to put endpoints under /store, /vendor and /admin. We separated capabilities, derived scope from authenticated relationships and checked ownership again at the resource being requested.

The client problem: three audiences sharing one commerce engine

The platform was not a conventional online shop with an administrator behind it.

Buyers worked inside organisations that could manage many properties. A master user might operate across the organisation, while a shopper could be assigned to one property and a supervisor to a defined set. Suppliers had their own teams. A seller administrator, store owner and account manager did not need the same view of orders, inventory or customer properties. Platform operators needed a broader administration surface to run the marketplace itself.

All three audiences ultimately touched related commerce data:

  • a buyer creates an order;
  • a supplier fulfils its part of that order;
  • an operator may investigate or support the transaction.

That shared data is useful. Shared authority is not.

If the Vendor Panel called Admin endpoints for convenience, a supplier-facing application would depend on platform privileges. If a Store route trusted an organisation or property ID supplied by the browser, changing one request value could change the apparent scope. If a resource check stopped at “the user is authenticated”, knowing an identifier could be enough to probe another tenant's data.

The business requirement was straightforward to say and demanding to implement: every person should see and change only the commerce capabilities that make sense for their role and current business relationship.

Route families express audiences, not complete permission rules

We kept the three Medusa API surfaces because they provide a clear first boundary.

/store is where buyer-facing commerce capabilities live. /vendor serves supplier operations. /admin belongs to platform administration. This makes the API understandable to the applications consuming it and prevents the Vendor Panel from becoming a disguised Admin client.

But a prefix is only a routing decision.

Two people can both call /vendor/orders/:id while representing different sellers. Two buyers can both use /store/orders/:id while belonging to different organisations. The path says which application is calling. It does not prove which business records the caller may access.

We therefore treated every request as a sequence of narrowing questions:

  1. Who is authenticated?
  2. Which buyer organisation or seller does that identity belong to?
  3. Which role does the person hold inside that tenant?
  4. Does the requested capability allow that role?
  5. Does the requested resource belong to the resulting scope?

Each answer reduces the accessible surface. None of them expands it merely because the client submitted a convenient identifier.

Build business context on the server

The browser still needs to communicate context. A buyer may select a property before browsing a catalogue, and a supplier user may switch between operational views.

The important distinction is between selecting a context and authorising it.

For buyer routes, the backend starts from the authenticated customer and loads the organisation relationship stored by the platform. It resolves the canonical organisation role rather than accepting a role from the request body. When a property header is present, it loads that property and its organisation. Capabilities that require a property then verify that the authenticated customer is actually assigned to it.

For vendor routes, the backend derives the seller from the authenticated seller or member relationship. The request acquires a seller identity before business resources are loaded. A seller ID typed into a URL or body is not allowed to redefine that identity.

This gives application headers, route parameters and form values a safer job: they indicate what the user wants to operate on. The server decides whether that operation belongs to the authenticated business context.

It also makes the product easier to reason about. When a route reaches its handler, it can work with a resolved organisation, property, seller and role instead of asking every feature to reinterpret raw authentication data.

Give each role capabilities that match the job

Tenant membership alone was still too broad.

Inside a buying organisation, a master user may manage organisation settings, properties or sensitive supplier credentials. A shopper should be able to browse products for an assigned property and manage a wishlist, but not create organisation members, inspect accounting exports or perform administrative onboarding actions.

The Store API therefore applies capability rules by method and path. For the restricted shopper role, safe catalogue reads and defined wishlist actions are allowed. Attempts to reach orders, accounting, organisation management or unsupported write operations are rejected. Product and wishlist capabilities that depend on a property additionally require a verified assignment.

The same principle applies on the supplier side. A seller administrator can act across the seller's own operations. A store owner is restricted to orders connected to assigned stock locations. An account manager is restricted through the properties covered by the relevant seller scope.

This is more useful than a single hierarchy where “higher” roles can do everything below them. The access rules reflect jobs: administer the supplier, operate selected locations or manage a defined customer territory.

Check the resource, not only the route

The most important guard happens when a person requests a specific business record.

Consider /vendor/orders/order_123. Authentication proves that the caller is a vendor user. Seller context proves which supplier they represent. The route then verifies that the order is linked to that seller before loading it for use.

After that first ownership check, role-specific scope is applied.

For a store owner, the order must refer to one of the locations assigned to that person. For an account manager, the order must be linked to a property within the manager's seller-specific scope. If role resolution failed, or the required scope is empty, access stops.

Buyer order access follows the order's own property and organisation relationships. A selected property header is useful while shopping, but it is not allowed to rewrite the tenancy of an existing order. The server reads the order's authoritative links, establishes which organisation owns the property and checks whether the current member may access it.

That distinction prevents two opposite failures. A malicious request cannot claim another property to gain access, and a legitimate user is not accidentally locked out of an authorised order merely because their current shopping selector points somewhere else.

Return less information when the record is outside the tenant

Many cross-tenant resource guards deliberately behave as if an inaccessible record does not exist.

Suppose an account manager for seller A requests an order owned by seller B. Returning “forbidden: this belongs to seller B” confirms that the identifier is valid and reveals something about another tenant. Returning “order not found” gives the caller no useful distinction between a missing record and a record outside their scope.

We used this fail-closed pattern for seller resources and tested it explicitly. An empty account-manager scope does not become a broad scope. An unknown role does not inherit a convenient default. A failure while resolving the member role prevents the order query from running. A cross-seller order is rejected before its contents are returned.

Not every denied action needs the same outward status. A buyer attempting a capability their shopper role never supports can receive a clear forbidden response. A vendor probing a particular resource across a tenant boundary should learn as little as practical.

The principle is consistent even when the HTTP expression differs: uncertainty never grants access.

Move seller work away from Admin APIs

As a marketplace evolves, it is tempting to let a new Vendor Panel reuse existing Admin endpoints. The data appears similar and the interface can ship faster.

That shortcut creates a structural problem. Admin routes are designed around platform operations. Adding scattered seller filters to them asks every handler to remember that a privileged surface is being consumed by a less privileged application.

We moved supplier capabilities onto vendor-owned routes and protected them with seller context. Product creation and updates, inventory operations, order actions, returns, price-list imports and seller settings could then evolve around supplier roles without borrowing operator authority.

The migration also clarified product ownership. If a supplier needs to update a product, the Vendor API must prove that the product belongs to that seller. If an operator performs a marketplace-wide intervention, that remains an Admin capability with its own authentication model.

The outcome is not duplicate APIs for the sake of symmetry. It is an explicit contract for each application. A vendor endpoint can return the seller-facing shape, enforce seller roles and hide platform-only controls. An Admin endpoint can support operator workflows without becoming an accidental public dependency of the Vendor Panel.

Test attempts to cross the boundary

Access-control tests are most valuable when they try the action the product must never permit.

We run focused tests for cross-seller access, vendor order access, shopper capabilities, property credit access and supervisor-to-property scope. The scenarios deliberately create near-matches: an order exists but belongs to another seller; a property exists but is outside the account manager's scope; a shopper is authenticated but requests an organisation capability; a role cannot be resolved; a route contains an encoded traversal-like path.

Positive controls matter too. The same suite proves that an account manager can open an order linked to an in-scope property, that a store owner can act on an assigned location and that a shopper can use the catalogue and wishlist capabilities intended for that role.

This avoids a security system that “works” by blocking the product.

We did not reduce the test strategy to checking middleware in isolation. Resource families such as products, inventory, orders and property-linked operations carry their own ownership tests because that is where a future route can accidentally omit a guard.

The practical release question becomes: can we demonstrate both the allowed path and the closest forbidden path for this capability?

Why Medusa was the right architectural choice

The client needed one commerce engine to coordinate a marketplace, not three disconnected products with copied orders and identities.

Medusa provided the commerce foundation and clear route families. Its extension model let us add the relationships the business actually used: suppliers, supplier members, buying organisations, properties, territories, locations and role-specific workflows.

That combination matters.

A rigid platform might offer a generic customer account, a generic staff account and a fixed set of permissions. The client problem did not fit those categories. A shopper assigned to one property, a seller account manager responsible for a territory and a platform operator are all legitimate commerce users, but their authority is relational.

Choosing Medusa was therefore an architecture decision tied to product capability. We could preserve one transactional backbone while extending its request context and resource guards to match the marketplace.

The commerce data stayed connected. The permissions became specific.

A practical checklist for multi-sided Medusa APIs

Before exposing another application from the same backend, ask:

  1. Which audience owns this route: buyer, seller or platform operator?
  2. Is authentication appropriate for that audience?
  3. Is tenant identity derived from the authenticated relationship?
  4. Can any request field replace the server-resolved seller or organisation?
  5. Which roles need this capability, by method as well as path?
  6. Does a selected property or location belong to the current tenant?
  7. Does the requested resource itself belong to the resolved scope?
  8. What happens if role or scope resolution fails?
  9. Should an inaccessible resource return forbidden or appear not found?
  10. Is there a negative test using a valid resource from another tenant?
  11. Is there a positive test proving the intended user can still work?
  12. Has the Vendor Panel stopped depending on Admin privileges?

This checklist is reusable because it begins with the business relationship, not the shape of one endpoint.

The broader lesson

The client did not need three cosmetic API namespaces.

They needed buyers, suppliers and operators to share a commerce platform without sharing authority. We used Medusa's route surfaces as the first boundary, then carried authenticated seller, organisation, property and role context down to the resource itself.

That let one backend support three applications while keeping each person's capabilities aligned with their job.

When a marketplace grows, access control cannot remain a collection of UI decisions. The API must be able to answer a business question for every operation: does this person, acting for this tenant and in this role, own the right to do this to this resource?

If the answer cannot be reconstructed on the server, the feature is not ready to cross the boundary.

Read next
Keep the useful ideas coming

One practical commerce field note at a time.

Join the WeAreSouk journal for grounded stories about Medusa, Shopify, AI, integrations and the systems behind serious commerce.

Working on a similar problem?Bring us the business constraint. We’ll help map the system behind it.Talk to Souk →
Souk AI · online now

Turn the article into an implementation plan.

Ask how this applies to your store, your stack, or your current bottleneck.

01 Describe your current setup.02 Name the workflow or signal that feels unreliable.03 Get a practical first architecture back.
I can help map this article to your stack. Tell me what you sell, what platform you use, and where the medusa & architecture question hurts.
Continue the cluster

Building a property procurement platform on Medusa

Start a conversation

Tell us what commerce needs to do for your business.

No scheduling maze. Send the context, the constraint or the idea. We will read it and come back to you directly.