diff --git a/internal/db/dbmigrate/scripts/v0.10.0.pre.sql b/internal/db/dbmigrate/scripts/v0.10.0.pre.sql
index bfbcc2f67bfdd581f13c77273cfeaaacc7d90bb6..2a4077e51592e130bb513a5b894c985a8a6cde1d 100644
--- a/internal/db/dbmigrate/scripts/v0.10.0.pre.sql
+++ b/internal/db/dbmigrate/scripts/v0.10.0.pre.sql
@@ -278,3 +278,9 @@ END;
 
 
 DELIMITER ;
+
+# Table Data
+INSERT IGNORE INTO Events (event)
+    VALUES ('tokeninfo_notifications');
+INSERT IGNORE INTO Events (event)
+    VALUES ('tokeninfo_notifications_other_token');
diff --git a/internal/db/notificationsrepo/calendarrepo/calendar.go b/internal/db/notificationsrepo/calendarrepo/calendar.go
index 3d04a2930ffef045a5035fa21d3e65142fbd1229..b4f2cb47fea2eea0a30c454dc5b788131df7e80c 100644
--- a/internal/db/notificationsrepo/calendarrepo/calendar.go
+++ b/internal/db/notificationsrepo/calendarrepo/calendar.go
@@ -2,6 +2,7 @@ package calendarrepo
 
 import (
 	"github.com/jmoiron/sqlx"
+	"github.com/oidc-mytoken/api/v0"
 	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 
@@ -68,7 +69,7 @@ func GetMTsInCalendar(rlog log.Ext1FieldLogger, tx *sqlx.Tx, calendarID string)
 }
 
 // Get returns a calendar entry for a user and name
-func Get(rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID mtid.MTID, name string) (info CalendarInfo, err error) {
+func Get(rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID any, name string) (info CalendarInfo, err error) {
 	err = db.RunWithinTransaction(
 		rlog, tx, func(tx *sqlx.Tx) error {
 			return errors.WithStack(tx.Get(&info, `CALL Calendar_Get(?,?)`, mtID, name))
@@ -88,12 +89,40 @@ func GetByID(rlog log.Ext1FieldLogger, tx *sqlx.Tx, id string) (info CalendarInf
 }
 
 // List returns a list of all calendar entries for a user
-func List(rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID mtid.MTID) (infos []CalendarInfo, err error) {
+func List(rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID mtid.MTID) (cals []api.NotificationCalendar, err error) {
+	var infos []CalendarInfo
 	err = db.RunWithinTransaction(
 		rlog, tx, func(tx *sqlx.Tx) error {
 			return errors.WithStack(tx.Select(&infos, `CALL Calendar_List(?)`, mtID))
 		},
 	)
+	for _, i := range infos {
+		cals = append(
+			cals, api.NotificationCalendar{
+				Name:    i.Name,
+				ICSPath: i.ICSPath,
+			},
+		)
+	}
+	return
+}
+
+// ListCalendarsForMT returns a list of calendars where the passed token is used in
+func ListCalendarsForMT(rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID any) (cals []api.NotificationCalendar, err error) {
+	var infos []CalendarInfo
+	err = db.RunWithinTransaction(
+		rlog, tx, func(tx *sqlx.Tx) error {
+			return errors.WithStack(tx.Select(&infos, `CALL Calendar_ListForMT(?)`, mtID))
+		},
+	)
+	for _, i := range infos {
+		cals = append(
+			cals, api.NotificationCalendar{
+				Name:    i.Name,
+				ICSPath: i.ICSPath,
+			},
+		)
+	}
 	return
 }
 
diff --git a/internal/db/notificationsrepo/notifications.go b/internal/db/notificationsrepo/notifications.go
index 203775d53b3de4ca144a1e2418a80512006d2f3c..39ec79c0b8b667bcbcb0fdeaa84dad5ae9af2d7b 100644
--- a/internal/db/notificationsrepo/notifications.go
+++ b/internal/db/notificationsrepo/notifications.go
@@ -7,6 +7,7 @@ import (
 	log "github.com/sirupsen/logrus"
 
 	"github.com/oidc-mytoken/server/internal/db"
+	"github.com/oidc-mytoken/server/internal/db/notificationsrepo/calendarrepo"
 	"github.com/oidc-mytoken/server/internal/endpoints/notification/pkg"
 	"github.com/oidc-mytoken/server/internal/mytoken/pkg/mtid"
 )
@@ -62,7 +63,7 @@ func GetNotificationsForMTAndClass(
 
 // GetNotificationsForMT checks for and returns the found notifications for a certain mytoken
 func GetNotificationsForMT(
-	rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID mtid.MTID,
+	rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID any,
 ) (notifications []notificationInfoBaseWithClass, err error) {
 	err = db.RunWithinTransaction(
 		rlog, tx, func(tx *sqlx.Tx) error {
@@ -73,20 +74,39 @@ func GetNotificationsForMT(
 	return
 }
 
-// GetNotificationsForUser returns all found notifications for a user
-func GetNotificationsForUser(
-	rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID mtid.MTID,
-) (notifications []api.NotificationInfo, err error) {
+func GetNotificationsAndCalendarsForMT(
+	rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID any,
+) (notifications []api.NotificationInfo, calendars []api.NotificationCalendar, err error) {
 	err = db.RunWithinTransaction(
 		rlog, tx, func(tx *sqlx.Tx) error {
-			var withClass []notificationInfoBaseWithClass
-			_, err = db.ParseError(tx.Select(&withClass, `CALL Notifications_GetForUser(?)`, mtID))
+			calendars, err = calendarrepo.ListCalendarsForMT(rlog, tx, mtID)
 			if err != nil {
-				return errors.WithStack(err)
+				return err
 			}
-			notificationMap := make(map[uint64]api.NotificationInfo)
-			var ids []uint64
-			for _, n := range withClass {
+			ns, err := GetNotificationsForMT(rlog, tx, mtID)
+			if err != nil {
+				return err
+			}
+			notifications, err = notificationInfoBaseWithClassToNotificationInfo(rlog, tx, ns)
+			return err
+		},
+	)
+	return
+}
+
+func notificationInfoBaseWithClassToNotificationInfo(
+	rlog log.Ext1FieldLogger, tx *sqlx.Tx,
+	in []notificationInfoBaseWithClass,
+) (
+	out []api.
+		NotificationInfo,
+	err error,
+) {
+	notificationMap := make(map[uint64]api.NotificationInfo)
+	var ids []uint64
+	err = db.RunWithinTransaction(
+		rlog, tx, func(tx *sqlx.Tx) error {
+			for _, n := range in {
 				nie, ok := notificationMap[n.NotificationID]
 				if ok {
 					nie.Classes = append(nie.Classes, api.NewNotificationClass(n.Class))
@@ -108,12 +128,33 @@ func GetNotificationsForUser(
 				}
 				notificationMap[nie.NotificationID] = nie
 			}
-			for _, id := range ids {
-				notifications = append(notifications, notificationMap[id])
-			}
 			return nil
 		},
 	)
+	if err != nil {
+		return
+	}
+	for _, id := range ids {
+		out = append(out, notificationMap[id])
+	}
+	return
+}
+
+// GetNotificationsForUser returns all found notifications for a user
+func GetNotificationsForUser(
+	rlog log.Ext1FieldLogger, tx *sqlx.Tx, mtID mtid.MTID,
+) (notifications []api.NotificationInfo, err error) {
+	err = db.RunWithinTransaction(
+		rlog, tx, func(tx *sqlx.Tx) error {
+			var withClass []notificationInfoBaseWithClass
+			_, err = db.ParseError(tx.Select(&withClass, `CALL Notifications_GetForUser(?)`, mtID))
+			if err != nil {
+				return errors.WithStack(err)
+			}
+			notifications, err = notificationInfoBaseWithClassToNotificationInfo(rlog, tx, withClass)
+			return err
+		},
+	)
 	return
 }
 
diff --git a/internal/endpoints/notification/calendar/calendar.go b/internal/endpoints/notification/calendar/calendar.go
index 321916801295c86755172423fce7b0349ea472e9..805089c8333fd160c1904a2f63fcef427b08b17e 100644
--- a/internal/endpoints/notification/calendar/calendar.go
+++ b/internal/endpoints/notification/calendar/calendar.go
@@ -123,6 +123,7 @@ func HandleAdd(ctx *fiber.Ctx) error {
 	)
 	icsPath := utils.CombineURLPath(routes.CalendarDownloadEndpoint, id)
 	cal.SetUrl(icsPath)
+	calendarInfo.ICSPath = icsPath
 	dbInfo := calendarrepo.CalendarInfo{
 		ID:      id,
 		Name:    calendarInfo.Name,
@@ -131,7 +132,7 @@ func HandleAdd(ctx *fiber.Ctx) error {
 	}
 	res := model.Response{
 		Status:   http.StatusCreated,
-		Response: pkg.CreateCalendarResponse{CalendarInfo: dbInfo},
+		Response: pkg.CreateCalendarResponse{NotificationCalendar: calendarInfo},
 	}
 	if err := db.Transact(
 		rlog, func(tx *sqlx.Tx) error {
diff --git a/internal/endpoints/notification/calendar/pkg/calendarRequest.go b/internal/endpoints/notification/calendar/pkg/calendarRequest.go
index 41ac1867b77009c6d408e4aa631d94957b91c6f0..142196f48e4426cefa6ef0a4458278604973e96b 100644
--- a/internal/endpoints/notification/calendar/pkg/calendarRequest.go
+++ b/internal/endpoints/notification/calendar/pkg/calendarRequest.go
@@ -3,7 +3,6 @@ package pkg
 import (
 	"github.com/oidc-mytoken/api/v0"
 
-	"github.com/oidc-mytoken/server/internal/db/notificationsrepo/calendarrepo"
 	"github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg"
 	"github.com/oidc-mytoken/server/internal/mytoken/pkg/mtid"
 )
@@ -16,12 +15,12 @@ type AddMytokenToCalendarRequest struct {
 
 // CreateCalendarResponse is the response returned when a new calendar is created
 type CreateCalendarResponse struct {
-	calendarrepo.CalendarInfo
+	api.NotificationCalendar
 	TokenUpdate *pkg.MytokenResponse `json:"token_update,omitempty"`
 }
 
 // CalendarListResponse is the response returned to list all calendars of a user
 type CalendarListResponse struct {
-	Calendars   []calendarrepo.CalendarInfo `json:"calendars"`
-	TokenUpdate *pkg.MytokenResponse        `json:"token_update,omitempty"`
+	Calendars   []api.NotificationCalendar `json:"calendars"`
+	TokenUpdate *pkg.MytokenResponse       `json:"token_update,omitempty"`
 }
diff --git a/internal/endpoints/tokeninfo/history.go b/internal/endpoints/tokeninfo/history.go
index 2cfbc65bb6a31708fe3c78fe505c93baf61a7a8f..d55fd90f244d8e80a903dcabbdefefa10ec09235 100644
--- a/internal/endpoints/tokeninfo/history.go
+++ b/internal/endpoints/tokeninfo/history.go
@@ -125,8 +125,8 @@ func HandleTokenInfoHistory(
 		}
 		return handleTokenInfoHistory(rlog, tx, req, mt, clientMetadata)
 	}
-	if !mt.Capabilities.Has(api.CapabilityHistoryAnyToken) {
-		for _, momid := range req.MOMIDs {
+	for _, momid := range req.MOMIDs {
+		if !mt.Capabilities.Has(api.CapabilityHistoryAnyToken) {
 			if momid == api.MOMIDValueThis || momid == api.MOMIDValueChildren {
 				continue
 			}
@@ -150,21 +150,21 @@ func HandleTokenInfoHistory(
 					},
 				}
 			}
+		}
 
-			same, err := helper.CheckMytokensAreForSameUser(rlog, tx, momid, mt.ID)
-			if err != nil {
-				return *model.ErrorToInternalServerErrorResponse(err)
-			}
-			if !same {
-				return model.Response{
-					Status: fiber.StatusForbidden,
-					Response: api.Error{
-						Error: api.ErrorStrInvalidGrant,
-						ErrorDescription: fmt.Sprintf(
-							"The provided token cannot be used to obtain history for mom_id '%s'", momid,
-						),
-					},
-				}
+		same, err := helper.CheckMytokensAreForSameUser(rlog, tx, momid, mt.ID)
+		if err != nil {
+			return *model.ErrorToInternalServerErrorResponse(err)
+		}
+		if !same {
+			return model.Response{
+				Status: fiber.StatusForbidden,
+				Response: api.Error{
+					Error: api.ErrorStrInvalidGrant,
+					ErrorDescription: fmt.Sprintf(
+						"The provided token cannot be used to obtain history for mom_id '%s'", momid,
+					),
+				},
 			}
 		}
 	}
diff --git a/internal/endpoints/tokeninfo/notifications.go b/internal/endpoints/tokeninfo/notifications.go
new file mode 100644
index 0000000000000000000000000000000000000000..a0af6f7eef71c9dc23e186fdadb0ffce0eefa036
--- /dev/null
+++ b/internal/endpoints/tokeninfo/notifications.go
@@ -0,0 +1,169 @@
+package tokeninfo
+
+import (
+	"fmt"
+
+	"github.com/gofiber/fiber/v2"
+	"github.com/jmoiron/sqlx"
+	"github.com/oidc-mytoken/api/v0"
+	log "github.com/sirupsen/logrus"
+
+	"github.com/oidc-mytoken/server/internal/db"
+	helper "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper"
+	"github.com/oidc-mytoken/server/internal/db/notificationsrepo"
+	"github.com/oidc-mytoken/server/internal/endpoints/tokeninfo/pkg"
+	"github.com/oidc-mytoken/server/internal/model"
+	eventService "github.com/oidc-mytoken/server/internal/mytoken/event"
+	pkg2 "github.com/oidc-mytoken/server/internal/mytoken/event/pkg"
+	mytoken "github.com/oidc-mytoken/server/internal/mytoken/pkg"
+	"github.com/oidc-mytoken/server/internal/mytoken/restrictions"
+	"github.com/oidc-mytoken/server/internal/mytoken/rotation"
+	"github.com/oidc-mytoken/server/internal/utils/auth"
+	"github.com/oidc-mytoken/server/internal/utils/cookies"
+	"github.com/oidc-mytoken/server/internal/utils/errorfmt"
+)
+
+func doTokenInfoNotifications(
+	rlog log.Ext1FieldLogger, tx *sqlx.Tx, req *pkg.TokenInfoRequest, mt *mytoken.Mytoken,
+	clientMetadata *api.ClientMetaData,
+	usedRestriction *restrictions.Restriction,
+) (res pkg.TokeninfoNotificationsResponse, err error) {
+	err = db.RunWithinTransaction(
+		rlog, tx, func(tx *sqlx.Tx) error {
+			if len(req.MOMIDs) > 1 {
+				res.MomIDMapping = make(map[string]api.NotificationsCombinedResponse)
+				for _, id := range req.MOMIDs {
+					if id == api.MOMIDValueThis {
+						id = mt.ID.String()
+					}
+					var data api.NotificationsCombinedResponse
+					data.Notifications, data.Calendars, err = notificationsrepo.GetNotificationsAndCalendarsForMT(
+						rlog, tx, id,
+					)
+					if err != nil {
+						return err
+					}
+					res.MomIDMapping[id] = data
+				}
+			} else {
+				var id any
+				id = mt.ID
+				if len(req.MOMIDs) > 0 {
+					id = req.MOMIDs[0]
+				}
+				res.Notifications, res.Calendars, err = notificationsrepo.GetNotificationsAndCalendarsForMT(
+					rlog, tx, id,
+				)
+				if err != nil {
+					return err
+				}
+			}
+			if usedRestriction == nil {
+				return nil
+			}
+			if err = usedRestriction.UsedOther(rlog, tx, mt.ID); err != nil {
+				return err
+			}
+			res.TokenUpdate, err = rotation.RotateMytokenAfterOtherForResponse(
+				rlog, tx, req.Mytoken.JWT, mt, *clientMetadata, req.Mytoken.OriginalTokenType,
+			)
+			if err != nil {
+				return err
+			}
+			ev := api.EventTokenInfoNotifications
+			if len(req.MOMIDs) > 0 {
+				ev = api.EventTokenInfoNotificationsOtherToken
+			}
+			return eventService.LogEvent(
+				rlog, tx, pkg2.MTEvent{
+					Event:          ev,
+					MTID:           mt.ID,
+					ClientMetaData: *clientMetadata,
+				},
+			)
+		},
+	)
+	return
+}
+
+func handleTokenInfoNotifications(
+	rlog log.Ext1FieldLogger, tx *sqlx.Tx, req *pkg.TokenInfoRequest, mt *mytoken.Mytoken,
+	clientMetadata *api.ClientMetaData,
+) model.Response {
+	usedRestriction, errRes := auth.RequireUsableRestrictionOther(rlog, nil, mt, clientMetadata)
+	if errRes != nil {
+		return *errRes
+	}
+	res, err := doTokenInfoNotifications(rlog, tx, req, mt, clientMetadata, usedRestriction)
+	if err != nil {
+		rlog.Errorf("%s", errorfmt.Full(err))
+		return *model.ErrorToInternalServerErrorResponse(err)
+	}
+	rsp := model.Response{
+		Status:   fiber.StatusOK,
+		Response: res,
+	}
+	if res.TokenUpdate != nil {
+		rsp.Cookies = []*fiber.Cookie{cookies.MytokenCookie(res.TokenUpdate.Mytoken)}
+	}
+	return rsp
+}
+
+// HandleTokenInfoNotifications handles a tokeninfo notifications request
+func HandleTokenInfoNotifications(
+	rlog log.Ext1FieldLogger, tx *sqlx.Tx, req *pkg.TokenInfoRequest, mt *mytoken.Mytoken,
+	clientMetadata *api.ClientMetaData,
+) model.Response {
+	// If we call this function it means the token is valid.
+
+	rlog.Debug("Handle tokeninfo notifications request")
+	if len(req.MOMIDs) == 0 {
+		if errRes := auth.RequireCapability(
+			rlog, tx, api.CapabilityTokeninfoNotify, mt, clientMetadata,
+		); errRes != nil {
+			return *errRes
+		}
+		return handleTokenInfoNotifications(rlog, tx, req, mt, clientMetadata)
+	}
+	for _, momid := range req.MOMIDs {
+		if !mt.Capabilities.Has(api.CapabilityNotifyAnyTokenRead) {
+			if momid == api.MOMIDValueThis {
+				continue
+			}
+			isParent, err := helper.MOMIDHasParent(rlog, tx, momid, mt.ID)
+			if err != nil {
+				return *model.ErrorToInternalServerErrorResponse(err)
+			}
+			if !isParent {
+				return model.Response{
+					Status: fiber.StatusForbidden,
+					Response: api.Error{
+						Error: api.ErrorStrInsufficientCapabilities,
+						ErrorDescription: fmt.Sprintf(
+							"The provided token is neither a parent of the the token with "+
+								" mom_id '%s' nor does it have the '%s' capability", momid,
+							api.CapabilityNotifyAnyTokenRead.Name,
+						),
+					},
+				}
+			}
+		}
+
+		same, err := helper.CheckMytokensAreForSameUser(rlog, tx, momid, mt.ID)
+		if err != nil {
+			return *model.ErrorToInternalServerErrorResponse(err)
+		}
+		if !same {
+			return model.Response{
+				Status: fiber.StatusForbidden,
+				Response: api.Error{
+					Error: api.ErrorStrInvalidGrant,
+					ErrorDescription: fmt.Sprintf(
+						"The provided token cannot be used to obtain notifications for mom_id '%s'", momid,
+					),
+				},
+			}
+		}
+	}
+	return handleTokenInfoNotifications(rlog, tx, req, mt, clientMetadata)
+}
diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoNotificationsResponse.go b/internal/endpoints/tokeninfo/pkg/tokeninfoNotificationsResponse.go
new file mode 100644
index 0000000000000000000000000000000000000000..b42ff9645d7e29919f389b774d1994ad96b4538a
--- /dev/null
+++ b/internal/endpoints/tokeninfo/pkg/tokeninfoNotificationsResponse.go
@@ -0,0 +1,14 @@
+package pkg
+
+import (
+	"github.com/oidc-mytoken/api/v0"
+
+	my "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg"
+)
+
+// TokeninfoNotificationsResponse is a type for responses to tokeninfo notification requests
+type TokeninfoNotificationsResponse struct {
+	// on update check api.TokeninfoNotificationsResponse
+	api.TokeninfoNotificationsResponse
+	TokenUpdate *my.MytokenResponse `json:"token_update,omitempty"`
+}
diff --git a/internal/endpoints/tokeninfo/tokeninfo.go b/internal/endpoints/tokeninfo/tokeninfo.go
index 8f9fc637441cf946a69cfc973dba883c543faca0..430802dbf7cb2c92655c86ee84473786a5bb46c4 100644
--- a/internal/endpoints/tokeninfo/tokeninfo.go
+++ b/internal/endpoints/tokeninfo/tokeninfo.go
@@ -30,6 +30,8 @@ func HandleTokenInfo(ctx *fiber.Ctx) error {
 	switch req.Action {
 	case model.TokeninfoActionIntrospect:
 		return HandleTokenInfoIntrospect(rlog, nil, mt, req.Mytoken.OriginalTokenType, clientMetadata).Send(ctx)
+	case model.TokeninfoActionNotifications:
+		return HandleTokenInfoNotifications(rlog, nil, &req, mt, clientMetadata).Send(ctx)
 	case model.TokeninfoActionEventHistory:
 		return HandleTokenInfoHistory(rlog, nil, &req, mt, clientMetadata).Send(ctx)
 	case model.TokeninfoActionSubtokenTree:
diff --git a/internal/model/tokeninfoAction.go b/internal/model/tokeninfoAction.go
index 922a03ef058e2af683c88d4ea007cf3c8eb69a83..bce2344a1a70e00a2de215bfae80efecbfebda71 100644
--- a/internal/model/tokeninfoAction.go
+++ b/internal/model/tokeninfoAction.go
@@ -20,6 +20,7 @@ const ( // assert that these are in the same order as api.AllTokeninfoActions
 	TokeninfoActionEventHistory
 	TokeninfoActionSubtokenTree
 	TokeninfoActionListMytokens
+	TokeninfoActionNotifications
 	maxTokeninfoAction
 )