diff --git a/geomagio/api/secure/app.py b/geomagio/api/secure/app.py
index 8ced54ab2f4802a00b91669b8d5d25e8c62d5d11..79e3f8c6a66a79fe96afdecda79f816c6cc03e11 100644
--- a/geomagio/api/secure/app.py
+++ b/geomagio/api/secure/app.py
@@ -11,7 +11,11 @@ from .metadata import router as metadata_router
 from .SessionMiddleware import SessionMiddleware
 
 
-app = FastAPI(root_path="/ws/secure")
+app = FastAPI(
+    description="Web service for interaction with operational metadata records",
+    root_path="/ws/secure",
+    title="Geomagnetism Metadata Web Service",
+)
 
 # NOTE: database used for sessions is started by ..app.app,
 # which mounts this application at /ws/secure
diff --git a/geomagio/api/secure/login.py b/geomagio/api/secure/login.py
index b4c8f5cd0890d1ac4e3dfb7ac4506a3d827ebc06..9bab1d8d7877449a8dff11bef03f3fe94539641c 100644
--- a/geomagio/api/secure/login.py
+++ b/geomagio/api/secure/login.py
@@ -150,10 +150,11 @@ oauth.register(
 router = APIRouter()
 
 
-@router.get("/authorize")
+@router.get(
+    "/authorize",
+    description="Authorize callback after authenticating using OpenID",
+)
 async def authorize(request: Request):
-    """Authorize callback after authenticating using OpenID"""
-
     # finish login
     token = await oauth.openid.authorize_access_token(request)
 
@@ -171,9 +172,11 @@ async def authorize(request: Request):
     )
 
 
-@router.get("/login")
+@router.get(
+    "/login",
+    description="Redirect to OpenID provider.",
+)
 async def login(request: Request):
-    """Redirect to OpenID provider."""
     redirect_uri = request.url_for("authorize")
     if "127.0.0.1" not in redirect_uri:
         # 127.0.0.1 used for local dev, all others use https
@@ -185,9 +188,11 @@ async def login(request: Request):
     return await oauth.openid.authorize_redirect(request, redirect_uri)
 
 
-@router.get("/logout")
+@router.get(
+    "/logout",
+    description="Clear session and redirect to index page.",
+)
 async def logout(request: Request):
-    """Clear session and redirect to index page."""
     request.session.pop("token", None)
     request.session.pop("user", None)
     return RedirectResponse(
@@ -199,7 +204,9 @@ async def logout(request: Request):
     )
 
 
-@router.get("/user")
+@router.get(
+    "/user",
+    description="Get currently logged in user.",
+)
 async def user(request: Request, user: User = Depends(require_user())) -> User:
-    """Get currently logged in user."""
     return user
diff --git a/geomagio/api/secure/metadata.py b/geomagio/api/secure/metadata.py
index 755948d12afa590d377ee034ebd0b77bf3bddc11..ac8113acf98b353f8ef306ae727349527c209665 100644
--- a/geomagio/api/secure/metadata.py
+++ b/geomagio/api/secure/metadata.py
@@ -59,7 +59,11 @@ def get_metadata_query(
     )
 
 
-@router.post("/metadata", response_model=Metadata)
+@router.post(
+    "/metadata",
+    description="Save metadata in database",
+    response_model=Metadata,
+)
 async def create_metadata(
     request: Request,
     metadata: Metadata,
@@ -71,13 +75,22 @@ async def create_metadata(
     return Response(metadata.json(), status_code=201, media_type="application/json")
 
 
-@router.get("/metadata", response_model=List[Metadata])
+@router.get(
+    "/metadata",
+    description="Search metadata by query parameters",
+    name="Request metadata",
+    response_model=List[Metadata],
+)
 async def get_metadata(query: MetadataQuery = Depends(get_metadata_query)):
     metas = await MetadataDatabaseFactory(database=database).get_metadata(params=query)
     return metas
 
 
-@router.get("/metadata/history", response_model=List[Metadata])
+@router.get(
+    "/metadata/history",
+    description="Search historical metadata by query parameters",
+    response_model=List[Metadata],
+)
 async def get_metadata_history(query: MetadataQuery = Depends(get_metadata_query)):
     metas = await MetadataDatabaseFactory(database=database).get_metadata(
         params=query, history=True
@@ -85,12 +98,20 @@ async def get_metadata_history(query: MetadataQuery = Depends(get_metadata_query
     return metas
 
 
-@router.get("/metadata/{id}", response_model=Metadata)
+@router.get(
+    "/metadata/{id}",
+    description="Search metadata by database id",
+    response_model=Metadata,
+)
 async def get_metadata_by_id(id: int):
     return await MetadataDatabaseFactory(database=database).get_metadata_by_id(id=id)
 
 
-@router.get("/metadata/{metadata_id}/history", response_model=List[Metadata])
+@router.get(
+    "/metadata/{metadata_id}/history",
+    description="Search metadata version history by database id",
+    response_model=List[Metadata],
+)
 async def get_metadata_history_by_metadata_id(
     metadata_id: int,
 ):
@@ -101,7 +122,11 @@ async def get_metadata_history_by_metadata_id(
     )
 
 
-@router.get("/metadata/history/{id}", response_model=Metadata)
+@router.get(
+    "/metadata/history/{id}",
+    description="Search historical metadata by database id",
+    response_model=Metadata,
+)
 async def get_metadata_history_by_id(id: int):
     metadata = await MetadataDatabaseFactory(
         database=database
@@ -111,7 +136,11 @@ async def get_metadata_history_by_id(id: int):
     return metadata
 
 
-@router.put("/metadata/{id}", response_model=Metadata)
+@router.put(
+    "/metadata/{id}",
+    description="Edit metadata from older version",
+    response_model=Metadata,
+)
 async def update_metadata(
     id: int,
     metadata: Metadata = Body(...),
diff --git a/geomagio/api/ws/algorithms.py b/geomagio/api/ws/algorithms.py
index bcc42a3590b1804c87d95ada48679e2ba7584b4c..fe6d3207853a6284e0449c62dfb4db248cd97719 100644
--- a/geomagio/api/ws/algorithms.py
+++ b/geomagio/api/ws/algorithms.py
@@ -1,3 +1,4 @@
+from os import name
 from fastapi import APIRouter, Depends, HTTPException
 from starlette.responses import Response
 
@@ -14,7 +15,11 @@ from .data import format_timeseries, get_data_factory, get_data_query, get_times
 router = APIRouter()
 
 
-@router.get("/algorithms/dbdt/")
+@router.get(
+    "/algorithms/dbdt/",
+    description="First order derivative at requested interval",
+    name="Dbdt Algorithm",
+)
 def get_dbdt(
     query: DataApiQuery = Depends(get_data_query),
     data_factory: TimeseriesFactory = Depends(get_data_factory),
@@ -31,7 +36,12 @@ def get_dbdt(
     )
 
 
-@router.post("/algorithms/residual", response_model=Reading)
+@router.post(
+    "/algorithms/residual",
+    description="Caclulates new absolutes and baselines from reading\n\n"
+    + "Returns posted reading object with updated values",
+    response_model=Reading,
+)
 def calculate_residual(reading: Reading, adjust_reference: bool = True):
     try:
         return calculate(reading=reading, adjust_reference=adjust_reference)
diff --git a/geomagio/api/ws/app.py b/geomagio/api/ws/app.py
index 454c918ee83d8f3c85828d2e941785840208d81e..fdf9cc57177e963d450866c244fda4bb369d903c 100644
--- a/geomagio/api/ws/app.py
+++ b/geomagio/api/ws/app.py
@@ -22,7 +22,13 @@ METADATA_ENDPOINT = bool(os.getenv("METADATA_ENDPOINT", False))
 VERSION = os.getenv("GEOMAG_VERSION", "version")
 
 
-app = FastAPI(docs_url="/docs", root_path="/ws")
+app = FastAPI(
+    description="Web service for data access and observatory/element information\n\n"
+    + "Supports realtime processing via algorithms",
+    docs_url="/docs",
+    root_path="/ws",
+    title="Geomagnetism Data Web Service",
+)
 
 app.include_router(algorithms.router)
 app.include_router(data.router)
diff --git a/geomagio/api/ws/data.py b/geomagio/api/ws/data.py
index 873de6db15d8cb305ee12ae469a5f9ac26e96406..bec7e546e39d1b3452597d7e428d6afac3f3db2b 100644
--- a/geomagio/api/ws/data.py
+++ b/geomagio/api/ws/data.py
@@ -158,7 +158,12 @@ def get_timeseries(data_factory: TimeseriesFactory, query: DataApiQuery) -> Stre
 router = APIRouter()
 
 
-@router.get("/data/")
+@router.get(
+    "/data/",
+    name="Request data",
+    description="Returns timeseries depending on query parameters\n\n"
+    + "Limited to 345600 data points",
+)
 def get_data(
     query: DataApiQuery = Depends(get_data_query),
 ) -> Response:
diff --git a/geomagio/api/ws/elements.py b/geomagio/api/ws/elements.py
index a1379ad77df8ffbd7f8e3cdea0b40ee92ce88011..06da476a84d61ae94861472582113bbcc52bb5b7 100644
--- a/geomagio/api/ws/elements.py
+++ b/geomagio/api/ws/elements.py
@@ -8,7 +8,10 @@ from .Element import ELEMENTS
 router = APIRouter()
 
 
-@router.get("/elements/")
+@router.get(
+    "/elements/",
+    description="Information regarding available geomagnetic elements",
+)
 def get_elements() -> Dict:
     features = []
     for e in ELEMENTS:
diff --git a/geomagio/api/ws/metadata.py b/geomagio/api/ws/metadata.py
index 300dc3340428fcef198621378b226d9ee316d0b2..fb5627a0578702cdd2e763a4fe09e586ae1a51b9 100644
--- a/geomagio/api/ws/metadata.py
+++ b/geomagio/api/ws/metadata.py
@@ -10,7 +10,11 @@ from ..db import MetadataDatabaseFactory
 router = APIRouter()
 
 
-@router.get("/metadata", response_model=List[Metadata])
+@router.get(
+    "/metadata",
+    description="Search metadata records with query parameters(excludes id and metadata id)",
+    response_model=List[Metadata],
+)
 async def get_metadata(
     category: MetadataCategory = None,
     starttime: UTCDateTime = None,
@@ -36,6 +40,6 @@ async def get_metadata(
         status=status,
     )
     metas = await MetadataDatabaseFactory(database=database).get_metadata(
-        **query.datetime_dict(exclude={"id"})
+        **query.datetime_dict(exclude={"id", "metadata_id"})
     )
     return metas
diff --git a/geomagio/api/ws/observatories.py b/geomagio/api/ws/observatories.py
index 3e4fe928cc99498bd33443b23105f118d57b6e46..1a8c797c258952ba8793f12a903ffb70b93b6abf 100644
--- a/geomagio/api/ws/observatories.py
+++ b/geomagio/api/ws/observatories.py
@@ -8,7 +8,10 @@ from .Observatory import OBSERVATORIES, OBSERVATORY_INDEX
 router = APIRouter()
 
 
-@router.get("/observatories/")
+@router.get(
+    "/observatories/",
+    description="Information regarding available geomagnetic observatories",
+)
 def get_observatories() -> Dict:
     return {
         "type": "FeatureCollection",
@@ -16,7 +19,10 @@ def get_observatories() -> Dict:
     }
 
 
-@router.get("/observatories/{id}")
+@router.get(
+    "/observatories/{id}",
+    description="Search observatories by 3-letter observatory code",
+)
 async def get_observatory_by_id(id: str) -> Dict:
     try:
         return OBSERVATORY_INDEX[id].geojson()