diff --git a/circle/dashboard/models.py b/circle/dashboard/models.py
index c5fe0a3..91bd278 100644
--- a/circle/dashboard/models.py
+++ b/circle/dashboard/models.py
@@ -44,7 +44,7 @@ from common.models import HumanReadableObject, create_readable, Encoder
 
 from vm.tasks.agent_tasks import add_keys, del_keys
 
-from dashboard import store_api
+from .store_api import Store
 
 logger = getLogger(__name__)
 
@@ -213,12 +213,10 @@ def create_profile(sender, user, request, **kwargs):
         return False
     profile, created = Profile.objects.get_or_create(user=user)
 
-    if created:
-        user_home = "u-%d" % user.pk
-        if not store_api.userexist(user_home):
-            store_api.createuser(user_home, profile.smb_password, None,
-                                 profile.disk_quota)
-
+    try:
+        Store(user).create_user(profile.smb_password, None, profile.disk_quota)
+    except:
+        logger.exception("Can't create user %s", unicode(user))
     return created
 
 user_logged_in.connect(create_profile)
diff --git a/circle/dashboard/store_api.py b/circle/dashboard/store_api.py
index 64dc6ee..4ec4e4f 100644
--- a/circle/dashboard/store_api.py
+++ b/circle/dashboard/store_api.py
@@ -1,203 +1,136 @@
-from django.http import Http404
 import json
 import logging
-import requests
-
+from urlparse import urljoin
 from datetime import datetime
-from sizefield.utils import filesizeformat
 
+from django.http import Http404
 from django.conf import settings
+from requests import get, post, codes
+from sizefield.utils import filesizeformat
 
 logger = logging.getLogger(__name__)
 
 
-class Mock(object):
+class StoreApiException(Exception):
     pass
 
 
-def get_host():
-    return settings.STORE_URL
-
-
-def get_request_arguments():
-    args = {'verify': settings.STORE_VERIFY_SSL}
-
-    if settings.STORE_SSL_AUTH:
-        args['cert'] = (settings.STORE_CLIENT_CERT, settings.STORE_CLIENT_KEY)
-    if settings.STORE_BASIC_AUTH:
-        args['auth'] = (settings.STORE_CLIENT_USER,
-                        settings.STORE_CLIENT_PASSWORD)
-    return args
-
-
-def post_request(url, payload, timeout=None):
-    try:
-        headers = {'content-type': 'application/json'}
-        r = requests.post(url, data=payload, headers=headers, timeout=timeout,
-                          **get_request_arguments())
-        return r
-    except Exception as e:
-        logger.error("Error in store POST: %s" % e)
-        dummy = Mock()
-        setattr(dummy, "status_code", 200)
-        setattr(dummy, "content", "[]")
-        return dummy
-
-
-def get_request(url, timeout=None):
-    try:
-        headers = {'content-type': 'application/json'}
-        r = requests.get(url, headers=headers, timeout=timeout,
-                         **get_request_arguments())
-        return r
-    except Exception as e:
-        logger.error("Error in store GET: %s" % e)
-        dummy = Mock()
-        setattr(dummy, "status_code", 200)
-        setattr(dummy, "content", "[]")
-        return dummy
-
-
-def listfolder(neptun, path):
-    url = settings.STORE_URL + '/' + neptun
-    payload = json.dumps({'CMD': 'LIST', 'PATH': path})
-    r = post_request(url, payload, timeout=5)
-    if r.status_code == requests.codes.ok:
-        tupplelist = json.loads(r.content)
-        return tupplelist
-    else:
-        raise Http404
-
-
-def toplist(neptun):
-    url = settings.STORE_URL + '/' + neptun
-    payload = json.dumps({'CMD': 'TOPLIST'})
-    r = post_request(url, payload, timeout=2)
-    if r.status_code == requests.codes.ok:
-        tupplelist = json.loads(r.content)
-        return tupplelist
-    else:
-        raise Http404
-
-
-def requestdownload(neptun, path):
-    url = settings.STORE_URL + '/' + neptun
-    payload = json.dumps({'CMD': 'DOWNLOAD', 'PATH': path})
-    r = post_request(url, payload)
-    response = json.loads(r.content)
-    return response['LINK']
-
-
-def requestupload(neptun, path):
-    url = settings.STORE_URL+'/'+neptun
-    payload = json.dumps({'CMD': 'UPLOAD', 'PATH': path})
-    r = post_request(url, payload)
-    response = json.loads(r.content)
-    print response
-    if r.status_code == requests.codes.ok:
-        return response['LINK']
-    else:
-        raise Http404
-
-
-def requestremove(neptun, path):
-    url = settings.STORE_URL+'/'+neptun
-    payload = json.dumps({'CMD': 'REMOVE', 'PATH': path})
-    r = post_request(url, payload)
-    if r.status_code == requests.codes.ok:
-        return True
-    else:
-        return False
-
-
-def requestnewfolder(neptun, path):
-    url = settings.STORE_URL+'/'+neptun
-    payload = json.dumps({'CMD': 'NEW_FOLDER', 'PATH': path})
-    r = post_request(url, payload)
-    if r.status_code == requests.codes.ok:
-        return True
-    else:
-        return False
-
-
-def requestrename(neptun, old_path, new_name):
-    url = settings.STORE_URL+'/'+neptun
-    payload = json.dumps(
-        {'CMD': 'RENAME', 'NEW_NAME': new_name, 'PATH': old_path})
-    r = post_request(url, payload)
-    if r.status_code == requests.codes.ok:
-        return True
-    else:
-        return False
-
-
-def requestquota(neptun):
-    url = settings.STORE_URL+'/'+neptun
-    r = get_request(url)
-    if r.status_code == requests.codes.ok:
-        return json.loads(r.content)
-    else:
-        return False
-
-
-def set_quota(neptun, quota):
-    url = settings.STORE_URL+'/quota/'+neptun
-    payload = json.dumps({'QUOTA': quota})
-    r = post_request(url, payload)
-    if r.status_code == requests.codes.ok:
-        return True
-    else:
-        return False
-
-
-def userexist(neptun):
-    url = settings.STORE_URL+'/'+neptun
-    r = get_request(url, timeout=5)
-    if r.status_code == requests.codes.ok:
-        return True
-    else:
-        return False
-
-
-def createuser(neptun, password, key_list, quota):
-    url = settings.STORE_URL+'/new/'+neptun
-    payload = json.dumps(
-        {'SMBPASSWD': password, 'KEYS': key_list, 'QUOTA': quota})
-    r = post_request(url, payload, timeout=5)
-    if r.status_code == requests.codes.ok:
-        return True
-    else:
-        return False
-
-
-def updateauthorizationinfo(neptun, password, key_list):
-    url = settings.STORE_URL+'/set/'+neptun
-    payload = json.dumps({'SMBPASSWD': password, 'KEYS': key_list})
-    r = post_request(url, payload)
-    if r.status_code == requests.codes.ok:
-        return True
-    else:
-        return False
-
-
-def process_list(content):
-    for d in content:
-        d['human_readable_date'] = datetime.utcfromtimestamp(float(
-            d['MTIME']))
-        delta = (datetime.utcnow() - d['human_readable_date']).total_seconds()
-        d['is_new'] = delta < 5 and delta > 0
-        d['human_readable_size'] = (
-            "directory" if d['TYPE'] == "D" else
-            filesizeformat(float(d['SIZE'])))
-
-        if len(d['DIR']) == 1 and d['DIR'][0] == ".":
-            d['directory'] = "/"
+class NotOkException(StoreApiException):
+    def __init__(self, status, *args, **kwargs):
+        self.status = status
+        super(NotOkException, self).__init__(*args, **kwargs)
+
+
+class Store(object):
+
+    def __init__(self, user, default_timeout=0.5):
+        self.request_args = {'verify': settings.STORE_VERIFY_SSL}
+        if settings.STORE_SSL_AUTH:
+            self.request_args['cert'] = (settings.STORE_CLIENT_CERT,
+                                         settings.STORE_CLIENT_KEY)
+        if settings.STORE_BASIC_AUTH:
+            self.request_args['auth'] = (settings.STORE_CLIENT_USER,
+                                         settings.STORE_CLIENT_PASSWORD)
+        self.username = "u-%d" % user.pk
+        self.default_timeout = default_timeout
+        self.store_url = settings.STORE_URL
+
+    def _request(self, url, method=get, timeout=None,
+                 raise_status_code=True, **kwargs):
+        url = urljoin(self.store_url, url)
+        if timeout is None:
+            timeout = self.default_timeout
+        payload = json.dumps(kwargs)
+        try:
+            headers = {'content-type': 'application/json'}
+            response = method(url, data=payload, headers=headers,
+                              timeout=timeout, **self.request_args)
+        except Exception:
+            logger.exception("Error in store %s loading %s",
+                             unicode(method), url)
+            raise
         else:
-            d['directory'] = "/" + d['DIR'] + "/"
-
-        d['path'] = d['directory']
-        d['path'] += d['NAME']
-        if d['TYPE'] == "D":
-            d['path'] += "/"
+            if raise_status_code and response.status_code != codes.ok:
+                if response.status_code == 404:
+                    raise Http404()
+                else:
+                    raise NotOkException(response.status_code)
+            return response
+
+    def _request_cmd(self, cmd, **kwargs):
+        return self._request(self.username, post, CMD=cmd)
+
+    def list(self, path, process=True):
+        r = self._request_cmd("LIST", PATH=path)
+        result = r.json()
+        if process:
+            return self._process_list(result)
+        else:
+            return result
 
-    return sorted(content, key=lambda k: k['TYPE'])
+    def toplist(self, process=True):
+        r = self._request_cmd("TOPLIST")
+        result = r.json()
+        if process:
+            return self._process_list(result)
+        else:
+            return result
+
+    def request_download(self, path):
+            r = self._request_cmd("DOWNLOAD", PATH=path)
+            return r.json()['LINK']
+
+    def request_upload(self, path):
+            r = self._request_cmd("UPLOAD", PATH=path)
+            return r.json()['LINK']
+
+    def remove(self, path):
+        self._request_cmd("REMOVE", PATH=path)
+
+    def new_folder(self, path):
+        self._request_cmd("NEW_FOLDER", PATH=path)
+
+    def rename(self, old_path, new_name):
+        self._request_cmd("RENAME", PATH=old_path, NEW_NAME=new_name)
+
+    def get_quota(self):  # no CMD? :o
+        r = self._request(self.username)
+        return r.json()
+
+    def set_quota(self, quota):
+        self._request(self.username + "/quota/", post, QUOTA=quota)
+
+    def user_exist(self):
+        try:
+            self._request(self.username)
+            return True
+        except NotOkException:
+            return False
+
+    def create_user(self, password, keys, quota):
+        self._request("/new/" + self.username, SMBPASSWD=password, KEYS=keys,
+                      QUOTA=quota)
+
+    @staticmethod
+    def _process_list(content):
+        for d in content:
+            d['human_readable_date'] = datetime.utcfromtimestamp(float(
+                d['MTIME']))
+            delta = (datetime.utcnow() -
+                     d['human_readable_date']).total_seconds()
+            d['is_new'] = 0 < delta < 5
+            d['human_readable_size'] = (
+                "directory" if d['TYPE'] == "D" else
+                filesizeformat(float(d['SIZE'])))
+
+            if len(d['DIR']) == 1 and d['DIR'][0] == ".":
+                d['directory'] = "/"
+            else:
+                d['directory'] = "/" + d['DIR'] + "/"
+
+            d['path'] = d['directory']
+            d['path'] += d['NAME']
+            if d['TYPE'] == "D":
+                d['path'] += "/"
+
+        return sorted(content, key=lambda k: k['TYPE'])
diff --git a/circle/dashboard/views.py b/circle/dashboard/views.py
index 2032bba..f5ebc6c 100644
--- a/circle/dashboard/views.py
+++ b/circle/dashboard/views.py
@@ -21,6 +21,7 @@ from __future__ import unicode_literals, absolute_import
 from collections import OrderedDict
 from itertools import chain
 from os import getenv
+from os.path import join, normpath, dirname
 import os
 import json
 import logging
@@ -83,7 +84,7 @@ from storage.models import Disk
 from firewall.models import Vlan, Host, Rule
 from .models import Favourite, Profile, GroupProfile, FutureMember
 
-from dashboard import store_api
+from .store_api import Store
 
 logger = logging.getLogger(__name__)
 saml_available = hasattr(settings, "SAML_CONFIG")
@@ -225,14 +226,15 @@ class IndexView(LoginRequiredMixin, TemplateView):
                 'operator', user).all()[:5]
 
         # toplist
-        user_home = "u-%d" % user.pk
-        cache_key = "toplist-%s" % user_home
+        cache_key = "toplist-%d" % self.request.user.pk
         cache = get_cache("default")
         toplist = cache.get(cache_key)
         if not toplist:
             try:
-                toplist = store_api.process_list(store_api.toplist(user_home))
-            except Http404:
+                toplist = Store(self.request.user).toplist()
+            except Exception:
+                logger.exception("Unable to get tolist for %s",
+                                 unicode(self.request.user))
                 toplist = []
             cache.set(cache_key, toplist, 300)
 
@@ -3092,14 +3094,12 @@ class StoreList(LoginRequiredMixin, TemplateView):
         directory = self.request.GET.get("directory", "/")
         directory = "/" if not len(directory) else directory
 
-        user_home = "u-%d" % self.request.user.pk
-        content = store_api.listfolder(user_home, directory)
-        context['root'] = store_api.process_list(content)
+        context['root'] = Store(self.request.user).list(directory)
         context['up_url'] = self.create_up_directory(directory)
         context['current'] = directory
         context['next_url'] = "%s%s?directory=%s" % (
-            settings.DJANGO_URL[:-1], reverse("dashboard.views.store-list"),
-            directory)
+            settings.DJANGO_URL.rstrip("/"),
+            reverse("dashboard.views.store-list"), directory)
         return context
 
     def get(self, *args, **kwargs):
@@ -3113,28 +3113,31 @@ class StoreList(LoginRequiredMixin, TemplateView):
             return super(StoreList, self).get(*args, **kwargs)
 
     def create_up_directory(self, directory):
-        cut = -2 if directory.endswith("/") else -1
-        return "/".join(directory.split("/")[:cut]) + "/"
+        return normpath(join('/', directory, '..'))
 
 
 @require_GET
 @login_required
 def store_download(request):
-    user_home = "u-%d" % request.user.pk
     path = request.GET.get("path")
-    url = store_api.requestdownload(user_home, path)
+    url = Store(request.user).request_download(path)
     return redirect(url)
 
 
 @require_GET
 @login_required
 def store_upload(request):
-    user_home = "u-%d" % request.user.pk
     directory = request.GET.get("directory", "/")
-    action = store_api.requestupload(user_home, directory)
+    try:
+        action = Store(request.user).request_upload(directory)
+    except Exception:
+        logger.exception("Unable to upload")
+        messages.error(request, _("Unable to upload file."))
+        return redirect("/")
+
     next_url = "%s%s?directory=%s" % (
-        settings.DJANGO_URL[:-1], reverse("dashboard.views.store-list"),
-        directory)
+        settings.DJANGO_URL.rstrip("/"),
+        reverse("dashboard.views.store-list"), directory)
 
     return render(request, "dashboard/store/upload.html",
                   {'directory': directory, 'action': action,
@@ -3144,9 +3147,13 @@ def store_upload(request):
 @require_GET
 @login_required
 def store_get_upload_url(request):
-    user_home = "u-%d" % request.user.pk
     current_dir = request.GET.get("current_dir")
-    url = store_api.requestupload(user_home, current_dir)
+    try:
+        url = Store(request.user).request_upload(current_dir)
+    except Exception:
+        logger.exception("Unable to upload")
+        messages.error(request, _("Unable to upload file."))
+        return redirect("/")
     return HttpResponse(
         json.dumps({'url': url}), content_type="application/json")
 
@@ -3171,30 +3178,31 @@ class StoreRemove(LoginRequiredMixin, TemplateView):
         return context
 
     def post(self, *args, **kwargs):
-        user_home = "u-%d" % self.request.user.pk
         path = self.request.POST.get("path")
-        store_api.requestremove(user_home, path)
+        try:
+            Store(self.request.user).remove(path)
+        except Exception:
+            logger.exception("Unable to remove %s", path)
+            messages.error(self.request, _("Unable to remove %s.") % path)
 
-        if path.endswith("/"):
-            return redirect("%s?directory=%s" % (
-                reverse("dashboard.views.store-list"),
-                os.path.dirname(os.path.dirname(path)),
-            ))
-        else:
-            return redirect("%s?directory=%s" % (
-                reverse("dashboard.views.store-list"),
-                os.path.dirname(path),
-            ))
+        return redirect("%s?directory=%s" % (
+            reverse("dashboard.views.store-list"),
+            dirname(dirname(path)),
+        ))
 
 
 @require_POST
 @login_required
 def store_new_directory(request):
-    user_home = "u-%d" % request.user.pk
     path = request.POST.get("path")
     name = request.POST.get("name")
 
-    store_api.requestnewfolder(user_home, path + name)
+    try:
+        Store(request.user).new_folder(join(path, name))
+    except Exception:
+        logger.exception("Unable to create folder %s in %s for %s",
+                         name, path, unicode(request.user))
+        messages.error(request, _("Unable to create folder."))
     return redirect("%s?directory=%s" % (
         reverse("dashboard.views.store-list"), path))
 
@@ -3202,12 +3210,12 @@ def store_new_directory(request):
 @require_POST
 @login_required
 def store_refresh_toplist(request):
-    user_home = "u-%d" % request.user.pk
-    cache_key = "toplist-%s" % user_home
+    cache_key = "toplist-%d" % request.user.pk
     cache = get_cache("default")
     try:
-        toplist = store_api.process_list(store_api.toplist(user_home))
-    except Http404:
+        toplist = Store(request.user).toplist()
+    except Exception:
+        logger.exception("Can't get toplist of %s", unicode(request.user))
         toplist = []
     cache.set(cache_key, toplist, 300)