diff --git a/src/gisaf/_version.py b/src/gisaf/_version.py
index 0e3a257..c41c4d2 100644
--- a/src/gisaf/_version.py
+++ b/src/gisaf/_version.py
@@ -1 +1 @@
-__version__: str = '2023.4.dev34+g5dacc90.d20240212'
\ No newline at end of file
+__version__: str = '2023.4.dev37+gb00bf1f.d20240226'
\ No newline at end of file
diff --git a/src/gisaf/api/admin.py b/src/gisaf/api/admin.py
index eb27a04..57caeb4 100644
--- a/src/gisaf/api/admin.py
+++ b/src/gisaf/api/admin.py
@@ -1,6 +1,6 @@
 import logging
 
-from fastapi import Depends, FastAPI, HTTPException, status, responses
+from fastapi import Depends, APIRouter, HTTPException, status, responses
 
 from gisaf.models.admin import AdminBasket, BasketNameOnly
 from gisaf.models.authentication import User
@@ -9,8 +9,10 @@ from gisaf.admin import manager
 
 logger = logging.getLogger(__name__)
 
-api = FastAPI(
-    default_response_class=responses.ORJSONResponse,
+api = APIRouter(
+    tags=["admin"],
+    # dependencies=[Depends(get_token_header)],
+    responses={404: {"description": "Not found"}},
 )
 
 @api.get('/basket')
diff --git a/src/gisaf/api/dashboard.py b/src/gisaf/api/dashboard.py
index 9f10de6..da828bc 100644
--- a/src/gisaf/api/dashboard.py
+++ b/src/gisaf/api/dashboard.py
@@ -2,7 +2,7 @@ import logging
 from pathlib import Path
 from json import dumps
 
-from fastapi import Depends, FastAPI, HTTPException, status, responses
+from fastapi import Depends, APIRouter, HTTPException, status, responses
 from sqlalchemy.orm import selectinload
 from sqlmodel import select
 import pandas as pd
@@ -34,8 +34,10 @@ Gisaf is free, open source software for geomatics and GIS:
 <a href="http://redmine.auroville.org.in/projects/gisaf">Gisaf</a>.
 '''
 
-api = FastAPI(
-    default_response_class=responses.ORJSONResponse,
+api = APIRouter(
+    tags=["dashboard"],
+    # dependencies=[Depends(get_token_header)],
+    responses={404: {"description": "Not found"}},
 )
 
 @api.get('/groups')
diff --git a/src/gisaf/api/geoapi.py b/src/gisaf/api/geoapi.py
index cb75887..c5dc5a5 100644
--- a/src/gisaf/api/geoapi.py
+++ b/src/gisaf/api/geoapi.py
@@ -7,7 +7,7 @@ import logging
 from typing import Annotated
 from asyncio import CancelledError
 
-from fastapi import (Depends, FastAPI, HTTPException, Response, Header,
+from fastapi import (Depends, APIRouter, HTTPException, Response, Header,
                      WebSocket, WebSocketDisconnect,
                      status, responses)
 
@@ -20,8 +20,10 @@ from gisaf.security import get_current_active_user
 
 logger = logging.getLogger(__name__)
 
-api = FastAPI(
-    default_response_class=responses.ORJSONResponse,
+api = APIRouter(
+    tags=["geoapi"],
+    # dependencies=[Depends(get_token_header)],
+    responses={404: {"description": "Not found"}},
 )
 
 class ConnectionManager:
@@ -103,11 +105,10 @@ async def get_geojson(store_name,
     if model.cache_enabled:
         ttag = await redis_store.get_ttag(store_name)
         if ttag and If_None_Match == ttag:
-            return status.HTTP_304_NOT_MODIFIED
+            raise HTTPException(status.HTTP_304_NOT_MODIFIED)
     if hasattr(model, 'get_geojson'):
         geojson = await model.get_geojson(simplify_tolerance=simplify,
-                                          preserve_topology=preserveTopology,
-                                          registry=registry)
+                                          preserve_topology=preserveTopology)
         ## Store to redis for caching
         if use_cache:
             await redis_store.store_json(model, geojson)
diff --git a/src/gisaf/api/v2.py b/src/gisaf/api/main.py
similarity index 94%
rename from src/gisaf/api/v2.py
rename to src/gisaf/api/main.py
index ed2e6ee..2fa75bf 100644
--- a/src/gisaf/api/v2.py
+++ b/src/gisaf/api/main.py
@@ -2,7 +2,7 @@ import logging
 from datetime import timedelta
 from typing import Annotated
 
-from fastapi import Depends, FastAPI, HTTPException, status, responses
+from fastapi import Depends, APIRouter, HTTPException, status, responses
 from sqlalchemy.orm import selectinload
 from fastapi.security import OAuth2PasswordRequestForm
 from sqlmodel import select
@@ -12,7 +12,6 @@ from gisaf.models.authentication import (
   Role, RoleRead,
 )
 from gisaf.models.category import Category, CategoryRead
-from gisaf.models.geo_models_base import LineWorkSurveyModel
 from gisaf.models.to_migrate import DataProvider
 from gisaf.models.survey import Equipment, SurveyMeta, Surveyor
 from gisaf.config import Survey, conf
@@ -31,19 +30,16 @@ from gisaf.models.to_migrate import (
   FeatureInfo, InfoItem, Attachment, InfoCategory
 )
 from gisaf.live_utils import get_live_feature_info
-from gisaf.api.dashboard import api as dashboard_api
-from gisaf.api.map import api as map_api
 
 logger = logging.getLogger(__name__)
 
 
-api = FastAPI(
-    default_response_class=responses.ORJSONResponse,
+api = APIRouter(
+    tags=["api"],
+    # dependencies=[Depends(get_token_header)],
+    responses={404: {"description": "Not found"}},
 )
 #api.add_middleware(SessionMiddleware, secret_key=conf.crypto.secret)
-api.mount('/dashboard', dashboard_api)
-api.mount('/map', map_api)
-
 
 @api.get('/bootstrap')
 async def bootstrap(
diff --git a/src/gisaf/api/map.py b/src/gisaf/api/map.py
index c3b1bff..a75ac1b 100644
--- a/src/gisaf/api/map.py
+++ b/src/gisaf/api/map.py
@@ -2,21 +2,26 @@ from collections import OrderedDict, defaultdict
 import logging
 from json import dumps
 
-from fastapi import FastAPI, Request, HTTPException, status, responses
+from fastapi import (APIRouter, Request, HTTPException,
+                     Response, status)
 from sqlalchemy.orm import selectinload
 from sqlalchemy.exc import NoResultFound
 from sqlmodel import select
 
-from gisaf.models.map_bases import BaseMap, BaseMapLayer, BaseStyle, MapInitData
+from gisaf.models.map_bases import (BaseMap, BaseMapLayer, BaseMapWithStores,
+                                    BaseStyle, MapInitData, MaplibreStyle)
+from gisaf.models.misc import Qml
 from gisaf.registry import registry
-from gisaf.database import db_session
-from gisaf.database import fastapi_db_session
+from gisaf.database import db_session, fastapi_db_session
 from gisaf.tiles import registry as tiles_registry
+from gisaf.redis_tools import store as redis_store
 
 logger = logging.getLogger(__name__)
 
-api = FastAPI(
-    default_response_class=responses.ORJSONResponse,
+api = APIRouter(
+    tags=["map"],
+    # dependencies=[Depends(get_token_header)],
+    # responses={404: {"description": "Not found"}},
 )
 
 async def get_base_styles():
@@ -31,23 +36,21 @@ async def get_base_styles():
     # base_styles.extend(tiles_registry.mbtiles.values())
     return [BaseStyle(name=bs) for bs in base_styles] # type: ignore
 
-async def get_base_maps() -> list[BaseMap]:
+async def get_base_maps() -> list[BaseMapWithStores]:
     async with db_session() as session:
-        query1 = select(BaseMap).options(selectinload(BaseMap.layers)) # type: ignore
+        query1 = select(BaseMap) #.options(selectinload(BaseMap.layers)) # type: ignore
         data1 = await session.exec(query1)
         base_maps = data1.all()
-        return base_maps # type: ignore
         base_map_dict = {bm.id: bm.name for bm in base_maps}
-        query2 = select(BaseMapLayer).options(selectinload(BaseMapLayer.base_map))
+        query2 = select(BaseMapLayer).options(selectinload(BaseMapLayer.base_map)) # type: ignore
         data2 = await session.exec(query2)
         base_map_layers = data2.all()
-        bms = defaultdict(list)
+        bms: dict[str, list] = defaultdict(list)
         for bml in base_map_layers:
-            breakpoint()
             if bml.store:
-                bms[base_map_dict[bml.base_map_id]].append(name=bml.store)
+                bms[base_map_dict[bml.base_map_id]].append(bml.store)
         return [
-            BaseMap(name=bm, stores=bmls)
+            BaseMapWithStores(name=bm, stores=bmls)
             for bm, bmls in OrderedDict(sorted(bms.items())).items()
         ]
 
@@ -84,3 +87,29 @@ async def get_base_style(request: Request, name: str,
     else:
         style = base_style.style # type: ignore
     return BaseStyle(name=name, style=style) # type: ignore
+
+@api.get('/layer_style/{store}')
+async def get_layer_style(request: Request, store: str,
+                          response: Response,
+                          ) -> MaplibreStyle | None:
+    store_record = registry.stores.loc[store]
+    if store_record.is_live:
+        ## No ttag for live layers' style (could be added?)
+        ## Get layer_defs from live redis and give symbol
+        return await redis_store.get_maplibre_style(store)
+    ## Set the etag based on the last modification of the model's style.
+    #if store in info.schema.app['registry'].geom_auto:
+    if store_record.custom:
+        ## The style is in Qml
+        ttag_channel = 'gisaf_map.qml'
+    else:
+        ## The style is in Category
+        ttag_channel = 'gisaf_survey.category'
+    ## Check if the request was etagged:
+    ttag = await redis_store.get_ttag(ttag_channel)
+    if ttag and request.headers.get('If-None-Match') == ttag:
+        raise HTTPException(status_code=status.HTTP_304_NOT_MODIFIED)
+        # request.not_modified = True
+        # return MaplibreStyle()
+    response.headers['ETag'] = ttag
+    return await store_record.model.get_maplibre_style()
diff --git a/src/gisaf/application.py b/src/gisaf/application.py
index e3377a3..36681a1 100644
--- a/src/gisaf/application.py
+++ b/src/gisaf/application.py
@@ -3,15 +3,17 @@ from contextlib import asynccontextmanager
 
 from fastapi import FastAPI, responses
 
-from gisaf.api.v2 import api
-from gisaf.api.geoapi import api as geoapi
 from gisaf.config import conf
 from gisaf.registry import registry
 from gisaf.redis_tools import setup_redis, setup_redis_cache, shutdown_redis
 from gisaf.tiles import registry as map_tile_registry
 from gisaf.live import setup_live
 from gisaf.admin import manager as admin_manager
+from gisaf.api.main import api
+from gisaf.api.geoapi import api as geoapi
 from gisaf.api.admin import api as admin_api
+from gisaf.api.dashboard import api as dashboard_api
+from gisaf.api.map import api as map_api
 
 logging.basicConfig(level=conf.gisaf.debugLevel)
 logger = logging.getLogger(__name__)
@@ -37,6 +39,8 @@ app = FastAPI(
     default_response_class=responses.ORJSONResponse,
 )
 
-app.mount('/v2', api)
-app.mount('/gj', geoapi)
-app.mount('/admin', admin_api)
\ No newline at end of file
+app.include_router(api, prefix="/api")
+app.include_router(geoapi, prefix="/api/gj")
+app.include_router(admin_api, prefix="/api/admin")
+app.include_router(dashboard_api, prefix="/api/dashboard")
+app.include_router(map_api, prefix='/api/map')
\ No newline at end of file
diff --git a/src/gisaf/custom_store_base.py b/src/gisaf/custom_store_base.py
index ff358d2..d764be8 100644
--- a/src/gisaf/custom_store_base.py
+++ b/src/gisaf/custom_store_base.py
@@ -1,3 +1,4 @@
+from re import A
 import geopandas as gpd
 from shapely import from_wkb
 from json import dumps
@@ -5,7 +6,8 @@ from json import dumps
 from sqlmodel import SQLModel
 
 from gisaf.config import conf
-from gisaf.models.to_migrate import MapboxPaint, MapboxLayout, FeatureInfo
+from gisaf.models.map_bases import MaplibreStyle
+from gisaf.models.to_migrate import FeatureInfo
 
 
 class BaseStore(SQLModel):
@@ -13,8 +15,8 @@ class BaseStore(SQLModel):
     name: str = '<Unnamed store>'
     description: str = '<Description>'
     icon: str | None = None
-    mapbox_paint: MapboxPaint | None = None
-    mapbox_layout: MapboxLayout | None = None
+    mapbox_paint: dict[str, dict] | None = None
+    mapbox_layout: dict[str, dict] | None = None
     attribution: str | None = None
     symbol: str = '\ue32b'
     base_gis_type: str = 'Point'
@@ -117,15 +119,12 @@ class BaseStore(SQLModel):
         raise NotImplementedError('Subclasses of BaseStore must implement get_item_params()')
 
     @classmethod
-    async def get_mapbox_style(cls):
+    async def get_maplibre_style(cls) -> MaplibreStyle:
         """
         Get the mapbox style (paint, layout, attribution...)
         """
-        style = {}
-        if cls.mapbox_paint is not None:
-            style['paint'] = dumps(cls.mapbox_paint)
-        if cls.mapbox_layout is not None:
-            style['layout'] = dumps(cls.mapbox_layout)
-        if cls.attribution is not None:
-            style['attribution'] = cls.attribution
-        return style
+        return MaplibreStyle(
+            paint=cls.mapbox_paint,
+            layout=cls.mapbox_layout,
+            attribution=cls.attribution
+        )
\ No newline at end of file
diff --git a/src/gisaf/models/authentication.py b/src/gisaf/models/authentication.py
index a8f9f70..32a4324 100644
--- a/src/gisaf/models/authentication.py
+++ b/src/gisaf/models/authentication.py
@@ -27,7 +27,7 @@ class UserBase(SQLModel):
 
 class User(UserBase, table=True):
     __table_args__ = gisaf_admin.table_args
-    id: int | None = Field(default=None, primary_key=True)
+    id: str | None = Field(default=None, primary_key=True)
     roles: list["Role"] = Relationship(back_populates="users",
                                        link_model=UserRoleLink)
     password: str | None = None
diff --git a/src/gisaf/models/category.py b/src/gisaf/models/category.py
index 0100867..23ab621 100644
--- a/src/gisaf/models/category.py
+++ b/src/gisaf/models/category.py
@@ -61,8 +61,8 @@ class CategoryBase(BaseModel):
     style: str | None = Field(sa_type=TEXT)
     symbol: str | None = Field(sa_type=String(1)) # type: ignore
     mapbox_type_custom: str | None = Field(sa_type=String(12)) # type: ignore
-    mapbox_paint: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore
-    mapbox_layout: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore
+    mapbox_paint: dict[str, dict | list | float | int | str] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore
+    mapbox_layout: dict[str, dict | list | float | int | str] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore
     viewable_role: str | None
     extra: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore
 
diff --git a/src/gisaf/models/geo_models_base.py b/src/gisaf/models/geo_models_base.py
index 96ecada..0ea96db 100644
--- a/src/gisaf/models/geo_models_base.py
+++ b/src/gisaf/models/geo_models_base.py
@@ -14,20 +14,20 @@ import shapely  # type: ignore
 import pyproj
 
 from pydantic import BaseModel
-from sqlmodel import select, Field, Relationship
+from sqlmodel import select, Field
 from sqlmodel.ext.asyncio.session import AsyncSession
-from sqlalchemy import BigInteger, MetaData, String, func, and_, text
+from sqlalchemy import BigInteger, String, func, and_, text
 from sqlalchemy.sql import sqltypes
 from sqlalchemy.orm import declared_attr
 from psycopg2.extensions import adapt
 from geoalchemy2.shape import from_shape
 from geoalchemy2.types import Geometry, WKBElement
 from shapely import wkb, from_wkb
-from shapely.geometry import mapping
+from shapely.geometry import mapping # type: ignore
 from shapely.ops import transform  # type: ignore
 
-from shapefile import (
-    Writer as ShapeFileWriter,  # type: ignore
+from shapefile import ( # type: ignore
+    Writer as ShapeFileWriter,
     POINT, POINTZ,
     POLYLINE, POLYLINEZ,
     POLYGON, POLYGONZ,
@@ -35,8 +35,9 @@ from shapefile import (
 
 from gisaf.database import db_session
 from gisaf.config import conf
+from gisaf.models.map_bases import MaplibreStyle
 from gisaf.models.models_base import Model
-from gisaf.models.metadata import gisaf_survey, gisaf_admin, survey
+from gisaf.models.metadata import gisaf_survey, gisaf_admin, survey, raw_survey
 from gisaf.models.misc import Qml
 from gisaf.models.category import Category
 from gisaf.models.to_migrate import InfoItem
@@ -157,6 +158,7 @@ class SurveyModel(BaseSurveyModel):
     # status: str = Field(sa_type=String(1))
 
     get_gdf_with_related: ClassVar[bool] = False
+    category_name: ClassVar[str]
 
     filtered_columns_on_map: ClassVar[list[str]] = [
         'equip_id',
@@ -219,9 +221,8 @@ class SurveyModel(BaseSurveyModel):
 
     @classmethod
     async def get_geojson(cls,
-        registry=None, simplify_tolerance=0, preserve_topology=False):
-        if registry is None:
-            from ..registry import registry
+        simplify_tolerance=0, preserve_topology=False):
+        from gisaf.registry import registry
 
         ## Fastest, but the id is in properties and needs the front end (eg Gisaf for mapbox)
         ## to move it at the feature level
@@ -592,36 +593,35 @@ class GeoModelNoStatus(Model):
         return zip_file
 
     @classmethod
-    async def get_mapbox_style(cls):
+    async def get_maplibre_style(cls) -> MaplibreStyle | None:
         """
         Get the mapbox style (paint, layout, attribution...)
         """
         ## If the model is from survey, it should have a category, which has a style
         ## Get from database
-        style = {}
-        if hasattr(cls, 'category'):
-            category = await Category.get_item_by_pk(pk=cls.category.name)
-            if category:
-                if category['mapbox_paint'] is not None:
-                    style['paint'] = category['mapbox_paint']
-                if category['mapbox_layout'] is not None:
-                    style['layout'] = category['mapbox_layout']
-
-        else:
-            category = None
-
-        qml = await Qml.get_item_by_pk(pk=cls.__name__)
-        if qml:
-            if qml['mapbox_paint']:
-                style['paint'] = qml['mapbox_paint']
-            if qml['mapbox_layout']:
-                style['layout'] = qml['mapbox_layout']
-
-        if cls.attribution is not None:
-            style['attribution'] = cls.attribution
+        style: MaplibreStyle | None
+        async with db_session() as session:
+            if hasattr(cls, 'category'):
+                category = await session.get(Category, cls.get_store_name())
+                if category:
+                    style = MaplibreStyle(
+                        layout=category.mapbox_layout,
+                        paint=category.mapbox_paint,
+                        )
+                else:
+                    style = None
+            else:
+                qml = await session.get(Qml, cls.__name__)
+                if qml:
+                    style = MaplibreStyle(
+                        layout=qml.mapbox_layout,
+                        paint=qml.mapbox_paint,
+                        attribution=qml.attr,
+                    )
+                else:
+                    style = None
         return style
 
-
     @classmethod
     async def get_features_attrs(cls, simplify_tolerance):
         """
@@ -1094,10 +1094,13 @@ class RawSurveyBaseModel(BaseSurveyModel, GeoPointModelNoStatus):
     """
     Abstract base class for category based raw survey point models
     """
-    # metadata: ClassVar[MetaData] = raw_survey
-    geom: Annotated[str, WKBElement] = Field(sa_type=Geometry('POINTZ', dimension=3,
-                                       srid=conf.geo.raw_survey.srid))
-    status: str = Field(sa_type=String(1))
+    __table_args__ = raw_survey.table_args
+    geom: Annotated[str, WKBElement] = Field(
+        sa_type=Geometry('POINTZ',
+        dimension=3,
+        srid=conf.geo.raw_survey.srid),
+    ) # type: ignore
+    status: str = Field(sa_type=String(1)) # type: ignore
 
     ## store_name is set in category_models_maker.make_category_models
     store_name: ClassVar[str | None] = None
diff --git a/src/gisaf/models/map_bases.py b/src/gisaf/models/map_bases.py
index 8472344..4db128d 100644
--- a/src/gisaf/models/map_bases.py
+++ b/src/gisaf/models/map_bases.py
@@ -28,30 +28,6 @@ class BaseStyle(Model, table=True):
         return f'<models.BaseStyle {self.name:s}>'
 
 
-class BaseMap(Model, table=True):
-    __table_args__ = gisaf_map.table_args
-    __tablename__: str = 'base_map' # type: ignore
-
-    class Admin:
-        menu = 'Other'
-
-    id: int | None = Field(primary_key=True, default=None)
-    name: str
-    layers: list['BaseMapLayer'] = Relationship(back_populates='base_map')
-
-    def __repr__(self) -> str:
-        return f'<models.BaseMap {self.name:s}>'
-
-    def __str__(self) -> str:
-        return self.name
-
-    @classmethod
-    def selectinload(cls) -> list[list['BaseMapLayer']]:
-        return [
-            cls.layers
-        ]
-
-
 class BaseMapLayer(Model, table=True):
     __table_args__ = gisaf_map.table_args
     __tablename__: str = 'base_map_layer' # type: ignore
@@ -62,7 +38,7 @@ class BaseMapLayer(Model, table=True):
     id: int | None = Field(primary_key=True, default=None)
     base_map_id: int = Field(foreign_key=gisaf_map.table('base_map.id'),
                              index=True)
-    base_map: BaseMap = Relationship(back_populates='layers')
+    base_map: 'BaseMap' = Relationship() #back_populates='layers')
     store: str = Field(sa_type=String(100)) # type: ignore
 
     @classmethod
@@ -78,8 +54,46 @@ class BaseMapLayer(Model, table=True):
         return f"{self.store or '':s}"
 
 
+class BaseMap(Model, table=True):
+    __table_args__ = gisaf_map.table_args
+    __tablename__: str = 'base_map' # type: ignore
+
+    class Admin:
+        menu = 'Other'
+
+    id: int | None = Field(primary_key=True, default=None)
+    name: str
+    # layers: list['BaseMapLayer'] = Relationship(
+    #     back_populates='base_map',
+    #     # link_model=BaseMapLayer
+    #     )
+
+    def __repr__(self) -> str:
+        return f'<models.BaseMap {self.name:s}>'
+
+    def __str__(self) -> str:
+        return self.name
+
+    # @classmethod
+    # def selectinload(cls) -> list[list[BaseMapLayer]]:
+    #     return [
+    #         cls.layers
+    #     ]
+
+
+class BaseMapWithStores(BaseModel):
+    name: str
+    stores: list[str]
+
+
 class MapInitData(BaseModel):
     baseStyles: list[BaseStyle] = []
-    baseMaps: list[BaseMap] = []
+    baseMaps: list[BaseMapWithStores] = []
     groups: list[CategoryGroup] = []
-    stores: list[Store] = []
\ No newline at end of file
+    stores: list[Store] = []
+
+
+class MaplibreStyle(BaseModel):
+    paint: dict[str, dict | list | float | int | str] | None = None
+    layout: dict[str, dict | list | float | int | str] | None = None
+    attribution: str | None = None
\ No newline at end of file
diff --git a/src/gisaf/models/misc.py b/src/gisaf/models/misc.py
index 7a8b4c0..51d3a96 100644
--- a/src/gisaf/models/misc.py
+++ b/src/gisaf/models/misc.py
@@ -14,11 +14,11 @@ class NotADataframeError(Exception):
     pass
 
 
-class Qml(Model):
+class Qml(Model, table=True):
     """
     Model for storing qml (QGis style)
     """
-    model_config = ConfigDict(protected_namespaces=())
+    model_config = ConfigDict(protected_namespaces=()) # type: ignore
     __table_args__ = gisaf_map.table_args
 
     class Admin:
@@ -26,11 +26,11 @@ class Qml(Model):
         flask_admin_model_view = 'QmlModelView'
 
     model_name: str | None = Field(default=None, primary_key=True)
-    qml: str
-    attr: str
-    style: str
-    mapbox_paint: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True))
-    mapbox_layout: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True))
+    qml: str | None = None
+    attr: str | None = None
+    style: str | None = None
+    mapbox_paint: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore
+    mapbox_layout: dict[str, Any] | None = Field(sa_type=JSON(none_as_null=True)) # type: ignore
 
     def __repr__(self):
         return '<models.Qml {self.model_name:s}>'.format(self=self)
diff --git a/src/gisaf/models/store.py b/src/gisaf/models/store.py
index 2cfbd10..46b6885 100644
--- a/src/gisaf/models/store.py
+++ b/src/gisaf/models/store.py
@@ -1,10 +1,4 @@
-from typing import Any
 from pydantic import BaseModel
-from gisaf.models.geo_models_base import GeoModel, RawSurveyBaseModel, GeoPointSurveyModel
-
-
-class MapLibreStyle(BaseModel):
-    ...
 
 
 class StoreNameOnly(BaseModel):
@@ -12,6 +6,7 @@ class StoreNameOnly(BaseModel):
 
 
 class Store(StoreNameOnly):
+    category: str | None = None
     auto_import: bool
     # base_gis_type: str
     count: int | None = None
@@ -39,7 +34,6 @@ class Store(StoreNameOnly):
     #raw_model: GeoPointSurveyModel
     #raw_model_store_name: str
     status: str
-    store: str
     style: str | None
     symbol: str | None
     title: str
diff --git a/src/gisaf/models/to_migrate.py b/src/gisaf/models/to_migrate.py
index 0102096..853ccfb 100644
--- a/src/gisaf/models/to_migrate.py
+++ b/src/gisaf/models/to_migrate.py
@@ -73,11 +73,3 @@ class FeatureInfo(BaseModel):
     files: list[Attachment] = []
     images: list[Attachment] = []
     externalRecordUrl: str | None = None
-
-
-class MapboxPaint(BaseModel):
-    ...
-
-
-class MapboxLayout(BaseModel):
-    ...
\ No newline at end of file
diff --git a/src/gisaf/redis_tools.py b/src/gisaf/redis_tools.py
index e99b7ac..f39f5a4 100644
--- a/src/gisaf/redis_tools.py
+++ b/src/gisaf/redis_tools.py
@@ -16,6 +16,7 @@ from redis import asyncio as aioredis
 
 from gisaf.config import conf
 # from gisaf.models.live import LiveModel
+from gisaf.models.map_bases import MaplibreStyle
 from gisaf.utils import (SHAPELY_TYPE_TO_MAPBOX_TYPE, DEFAULT_MAPBOX_LAYOUT,
     DEFAULT_MAPBOX_PAINT, gisTypeSymbolMap)
 from gisaf.registry import registry
@@ -281,18 +282,14 @@ class Store:
             registry.geom_live_defs[model_info['store']] = model_info
         registry.update_live_layers()
 
-    async def get_mapbox_style(self, store_name):
+    async def get_maplibre_style(self, store_name) -> MaplibreStyle:
         """
         Get the http headers (mapbox style) from the store name (layer_def)
         """
-        paint = await self.redis.get(self.get_mapbox_paint_channel(store_name))
-        layout = await self.redis.get(self.get_mapbox_layout_channel(store_name))
-        style = {}
-        if paint is not None:
-            style['paint'] = paint.decode()
-        if layout is not None:
-            style['layout'] = layout.decode()
-        return style
+        return MaplibreStyle(
+            paint=(await self.redis.get(self.get_mapbox_paint_channel(store_name))).decode(),
+            layout=(await self.redis.get(self.get_mapbox_layout_channel(store_name))).decode(),
+        )
 
     async def get_layer_as_json(self, store_name):
         """
diff --git a/src/gisaf/registry.py b/src/gisaf/registry.py
index 000b105..4f7af62 100644
--- a/src/gisaf/registry.py
+++ b/src/gisaf/registry.py
@@ -12,7 +12,7 @@ from pydantic import create_model
 from sqlalchemy import text
 from sqlalchemy.orm import selectinload, joinedload
 from sqlalchemy.exc import NoResultFound
-from sqlmodel import SQLModel, select, inspect, Relationship
+from sqlmodel import SQLModel, col, select, inspect, Relationship
 import pandas as pd
 import numpy as np
 
@@ -336,7 +336,7 @@ class ModelRegistry:
     #             for category in categories
     #             if self.raw_survey_models.get(category.table_name)}
 
-    async def get_model_id_params(self, model: SQLModel, id: int) -> FeatureInfo:
+    async def get_model_id_params(self, model: SQLModel, id: int) -> FeatureInfo | None:
         """
         Return the parameters for this item (table name, id), displayed in info pane
         """
@@ -389,8 +389,9 @@ class ModelRegistry:
         """
         ## Utility functions used with apply method (dataframes)
         def fill_columns_from_custom_models(row) -> tuple[str, str, str, str, str, str, str]:
+            # model:
             return (
-                row.model.__name__,
+                row.model.get_store_name(),
                 row.model.__name__,
                 row.model.description,
                 row.model.__table__.schema,
@@ -429,7 +430,11 @@ class ModelRegistry:
         self.categories = self.categories.merge(df_raw_models, left_on='store', right_index=True)
         self.categories['custom'] = False
         self.categories['is_db'] = True
-        self.categories.sort_index(inplace=True)
+        self.categories.reset_index(inplace=True)
+        self.categories.rename(columns={'name': 'category'}, inplace=True)
+        self.categories.set_index('store', inplace=True)
+        self.categories.sort_values('category')
+        # self.categories.sort_index(inplace=True)
         # self.categories['name_letter'] = self.categories.index.str.slice(0, 1)
         # self.categories['name_number'] = self.categories.index.str.slice(1).astype('int64')
         # self.categories.sort_values(['name_letter', 'name_number'], inplace=True)
@@ -467,6 +472,7 @@ class ModelRegistry:
         self.custom_models['custom'] = True
         self.custom_models['is_db'] = True
         self.custom_models['raw_model_store_name'] = ''
+        self.custom_models['category'] = ''
         self.custom_models['in_menu'] = self.custom_models.apply(
             lambda row: getattr(row.model, 'in_menu', True),
             axis=1
@@ -512,6 +518,7 @@ class ModelRegistry:
         self.custom_stores = self.custom_stores.loc[self.custom_stores.in_menu]
         self.custom_stores['auto_import'] = False
         self.custom_stores['is_line_work'] = False
+        self.custom_stores['category'] = ''
 
         if len(self.custom_stores) > 0:
             self.custom_stores['long_name'],\
@@ -525,13 +532,18 @@ class ModelRegistry:
         ## Combine Misc (custom) and survey (auto) stores
         ## Retain only one status per category (defaultStatus, 'E'/existing by default)
         self.stores = pd.concat([
-            self.categories[self.categories.status==conf.map.defaultStatus[0]].reset_index().set_index('store').sort_values('title'),
+            self.categories[self.categories.status==conf.map.defaultStatus[0]].sort_values('title'),
             self.custom_models,
             self.custom_stores
         ])#.drop(columns=['store_name'])
+        self.stores.drop(columns='name', inplace=True)
+        self.stores.index.name = 'name'
         self.stores['in_menu'] = self.stores['in_menu'].astype(bool)
         self.stores['status'].fillna('E', inplace=True)
 
+        self.categories.reset_index(inplace=True)
+        self.categories.set_index('category', inplace=True)
+
         ## Set in the stores dataframe some useful properties, from the model class
         ## Maybe at some point it makes sense to get away from class-based definitions
         def fill_columns_from_model(row):
@@ -576,18 +588,20 @@ class ModelRegistry:
         ## Add Misc and Live
         self.primary_groups.append(CategoryGroup(
             name='Misc',
+            long_name='Misc',
             major=True,
-            long_name='Misc and old layers (not coming from our survey; '
-                      'they will be organized, '
-                      'eventually as the surveys get more complete)',
+            # description]='Misc and old layers (not coming from our survey; '
+            #           'they will be organized, '
+            #           'eventually as the surveys get more complete)',
             categories=[],
         ))
 
         self.primary_groups.append(CategoryGroup(
             name='Live',
+            long_name='Live',
             major=True,
-            long_name='Layers from data processing, sensors, etc, '
-                      'and are updated automatically',
+            # long_name='Layers from data processing, sensors, etc, '
+            #           'and are updated automatically',
             categories=[],
         ))
 
@@ -671,7 +685,7 @@ class ModelRegistry:
         df_live['auto_import'] = False
         df_live['base_gis_type'] = df_live['gis_type']
         df_live['custom'] = False
-        df_live['group'] = ''
+        df_live['group'] = 'Live'
         df_live['in_menu'] = True
         df_live['is_db'] = False
         df_live['is_line_work'] = False
@@ -681,8 +695,11 @@ class ModelRegistry:
         df_live['minor_group_2'] = ''
         df_live['status'] = 'E'
         df_live['style'] = None
-        df_live['title'] = df_live['name']
+        df_live['category'] = ''
+        df_live.rename(columns={'name': 'title'}, inplace=True)
+        df_live.index.name = 'name'
         registry.stores = pd.concat([registry.stores, df_live])
+        df_live.index.name = 'store'
         for store, model_info in self.geom_live_defs.items():
             ## Add provided live layers in the stores df
             # Create the pydantic model