medusajs/medusa · error · MedusaError

Service zone with id: ${zone_id} not found on fulfillment se

Error message

Service zone with id: ${zone_id} not found on fulfillment set

What it means

The DELETE /admin/fulfillment-sets/:id/service-zones/:zone_id route performs the same ownership check as the update route: it loads the fulfillment set with service_zones and throws NOT_FOUND (HTTP 404) if the zone is not among them, preventing deletion of zones belonging to other sets.

Source

Thrown at packages/medusa/src/api/admin/fulfillment-sets/[id]/service-zones/[zone_id]/route.ts:116

  req: AuthenticatedMedusaRequest,
  res: MedusaResponse<HttpTypes.AdminServiceZoneDeleteResponse>
) => {
  const { id, zone_id } = req.params

  const fulfillmentModuleService = req.scope.resolve<IFulfillmentModuleService>(
    Modules.FULFILLMENT
  )

  // ensure fulfillment set exists and that the service zone is part of it
  const fulfillmentSet = await fulfillmentModuleService.retrieveFulfillmentSet(
    id,
    {
      relations: ["service_zones"],
    }
  )

  if (!fulfillmentSet.service_zones.find((s) => s.id === zone_id)) {
    throw new MedusaError(
      MedusaError.Types.NOT_FOUND,
      `Service zone with id: ${zone_id} not found on fulfillment set`
    )
  }

  await deleteServiceZonesWorkflow(req.scope).run({
    input: { ids: [zone_id] },
  })

  res.status(200).json({
    id: zone_id,
    object: "service_zone",
    deleted: true,
    parent: fulfillmentSet as unknown as HttpTypes.AdminFulfillmentSet,
  })
}

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Fetch the fulfillment set's zones first and delete using matching ids
  2. Validate both path params come from the same source object in the client
  3. Handle 404 as a no-op 'already gone/does not belong' outcome for idempotent deletes
Defensive patterns

Strategy: validation

Validate before calling

const set = await fulfillmentModuleService.retrieveFulfillmentSet(setId, { relations: ["service_zones"] })
if (!set.service_zones.some((z) => z.id === zoneId)) return // already gone / wrong set: no-op

Type guard

const isDeletableZone = (set: {service_zones:{id:string}[]}, zoneId: string) =>
  set.service_zones.some((z) => z.id === zoneId)

Try / catch

try { await deleteZone(setId, zoneId) } catch (e) { if (e.statusCode === 404) { /* treat as already deleted */ return } throw e }

Prevention

When it happens

Trigger: DELETE on a zone id that is not attached to the given fulfillment set id.

Common situations: Scripts deleting zones by id while passing a mismatched fulfillment set path param; UI sending the set id of a different profile.

Related errors


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/e418676f01863728. Report an issue: GitHub.