diff --git a/check_gitlab_tokens b/check_gitlab_tokens
index 58fdd8cad3aec51bff3e72990f3baf0a59029d9a..c3ad8495a6ae11fcde45308a89d873923f17bf11 100755
--- a/check_gitlab_tokens
+++ b/check_gitlab_tokens
@@ -1,22 +1,22 @@
 #!/bin/bash
 # ======================================================================
 #
-# Check Gitlab tokens
+# Check Gitlab tokens of groups and projects.
+# It warns if tokens expire soon.
 #
 # requirements:
 # - inc/rest-api-client.sh
 # - curl
 #
 # ----------------------------------------------------------------------
-# 2024-10-25  v0.0  <axel.hahn@iml.unibe.ch>
+# 2024-10-29  v1.0  <axel.hahn@iml.unibe.ch>
 # ======================================================================
 
-
 cd "$( dirname "$0" )" || exit
-
 . "$( dirname $0 )/inc_pluginfunctions" || exit 1
 
-export self_APPVERSION=0.1
+export self_APPVERSION=1.0
+
 
 GITLAB_API='https://gitlab.example.com/api/v4'
 GITLAB_TOKEN='glpat-12345678'
@@ -24,6 +24,11 @@ GITLAB_TOKEN='glpat-12345678'
 GITLAB_CONFIG=/etc/icinga2/gitlab.cfg
 REST_CLIENT="$( dirname $0 )/../inc/rest-api-client.sh"
 
+projectUrls=
+
+OUT_TOKENS=/tmp/gitlab-tokens__$USER.json
+OUT_USERS=/tmp/gitlab-users__$USER.json
+
 typeset -i iSince=395
 typeset -i iTokenCount
 typeset -i iTokensFound
@@ -35,19 +40,20 @@ typeset -i iCriticalLimit=10
 
 typeset -i iCountWarn=0
 typeset -i iCountCritical=0
+
 output=""
 
 # ----------------------------------------------------------------------
 # functions
 # ----------------------------------------------------------------------
 
-
+# Show help
 function showHelp(){
     local _self; _self=$(basename $0)
 cat <<EOF
 $( ph.showImlHelpHeader )
 
-Check gitlab tokens and warn if a token expires soon.
+Check gitlab tokens and warn if tokens expire soon.
 
 This check fetches the gitlbab tokens created in the last $iSince days
 from the Gitlab API. It skips
@@ -55,8 +61,11 @@ from the Gitlab API. It skips
     - personal access tokens of users
     - revoked tokens
 
+The script can run several seconds depending on count of tokens, projects
+and users. Maybe you want to call it with a longer interval.
+
 SYNTAX:
-$_self [-w WARN_LIMIT] [-c CRITICAL_LIMIT]
+$_self [OPTIONS]
 
 OPTIONS:
 
@@ -74,13 +83,22 @@ PARAMETERS:
 
     None.
 
-EXAMPLE:
-    $(basename $0) -w 28 -c 7
+EXAMPLES:
+
+    $_self -w 28 -c 7
+        Set other warning and critical level
+
+    $_self -g ./gitlab.cfg
+        Set a custom gitlab config file
+
+    $_self -r /opt/bash-api-client/bash-api-client.sh
+        Set a custom gitlab config file
 
 EOF
 }
 
-# Ffetch data from gitlab api with page requests
+# Fetch data from gitlab api with multiple page requests
+#
 # param  string  url
 # param  string  output file
 # param  int     optional: number of items per page; default: 100
@@ -104,7 +122,7 @@ function _getPagesToFile(){
             echo "ERROR: Request failed: $pageUrl"
             http.getResponseHeader
             http.getResponse
-            exit 1
+            ph.abort
         fi
 
         # if response is "[]" then we are done
@@ -117,8 +135,45 @@ function _getPagesToFile(){
     done
 }
 
+# Get web link of a project to see its tokens
+#
+# global string  projectUrls  string to cache project name + link
+#
+# param  string  username eg. projects_14_bot_nnnnnn
+# return string
+function getWeblink(){
+    local myusername="$1"
+
+    sType=$( cut -f 1 -d "_" <<< "$myusername" )s
+    # sType is one of users|projects
+
+    sId2=$( cut -f 2 -d "_" <<< "$myusername" )
+    local data
+    local myurl
+    if ! grep -q "^/$sType/$sId2:" <<< "$projectUrls" ; then
+        http.makeRequest "/$sType/$sId2"
+        if ! http.isOk > /dev/null; then
+            echo "ERROR: Request /$sType/$sId2 failed: $pageUrl"
+            http.getResponseHeader
+            http.getResponse
+            ph.abort
+        fi
+        data="$( http.getResponse )"
+        myname=$( getKey "$data" "name" )
+        myurl=$( getKey "$data" "web_url" )
+        if [ -n "$myurl" ]; then
+            projectUrls+="/$sType/$sId2:$myname <$myurl/-/settings/access_tokens>$NL"
+        fi
+    fi
+    grep "^/$sType/$sId2:" <<< "$projectUrls" | cut -f 2- -d ":"
+}
+
+# Get a single value from json
+# param  string  json data
+# param  string  key
+# return string
 function getKey(){
-    echo "$1" | jq -r ".$2" | grep -v "null"
+    jq -r ".$2" <<< "$1" | grep -v "null"
 }
 
 # ----------------------------------------------------------------------
@@ -156,26 +211,23 @@ startdate="$( date +%Y-%m-%dT00:00:00Z --date "$iSince days ago")"
 sDateWarn="$( date +%Y%m%d --date "${iWarnLimit} days" )"
 sDateCritical="$( date +%Y%m%d --date "${iCriticalLimit} days" )"
 
-url="/personal_access_tokens/?revoked=false&created_after=${startdate}"
-_getPagesToFile "$url" "/tmp/gitlab-tokens.json"
-_getPagesToFile "/users" /tmp/gitlab-users.json
+# get all tokens
+# see https://docs.gitlab.com/ee/api/personal_access_tokens.html
+_getPagesToFile "/personal_access_tokens/?revoked=false&created_after=${startdate}" "$OUT_TOKENS"
 
+# get all users
+# see https://docs.gitlab.com/ee/api/users.html
+_getPagesToFile "/users?exclude_humans=true&exclude_external=true&exclude_internal=true&created_after=${startdate}&search=_bot_" "$OUT_USERS"
 
 # IDs / Einträge zählen:
-iTokenCount=$( cat /tmp/gitlab-tokens.json | jq ".[].id " | wc -l )
-# echo "Found Tokens since $startdate: $iTokenCount"
-
-
-# echo "Warn when expiring before: ${sDateWarn} ... critical before: ${sDateCritical}"
-# loop over tokens
+iTokenCount=$( jq ".[].id " < "$OUT_TOKENS" | wc -l )
 
 iTokensFound=0
-
 for i in $( seq 1 $iTokenCount )
 do
 
     # get nth token
-    entry="$( cat /tmp/gitlab-tokens.json | jq ".[$i]" )"
+    entry="$( jq ".[$i]" < "$OUT_TOKENS")"
 
     # hide non active tokens
     if [ "$( getKey "$entry" "active" )" = "false" ]; then
@@ -190,7 +242,7 @@ do
 
     # hide tokens referencing a username that doesn't contain "_[number]_bot_"
     sUserid=$( getKey "$entry" "user_id" )
-    myusername="$( cat /tmp/gitlab-users.json | jq  ".[] | select(.id == $sUserid)" | jq ".username" | cut -f 1-3 -d "_" | tr -d '"') "
+    myusername="$( jq  ".[] | select(.id == $sUserid)" < "$OUT_USERS" | jq ".username" | cut -f 1-3 -d "_" | tr -d '"') "
     if ! grep -q "_[0-9]*_bot" <<< "$myusername" ; then
         continue
     fi
@@ -215,12 +267,9 @@ do
         fi
     fi
 
-    # url of owning group (is that useful?)
-    sType=$( cut -f 1 -d "_" <<< "$myusername" )s
-    sId2=$( cut -f 2 -d "_" <<< "$myusername" )
-    myurl="$GITLAB_API/$sType/$sId2"
+    myproject="$( getWeblink "$myusername" )"
 
-    output+="$sExpire $sStatus $sName $myusername $myurl${NL}" 
+    output+="$sExpire  $sStatus  $sName - $myproject${NL}" 
 
 done 
 
@@ -236,4 +285,10 @@ ph.status "$iTokensFound Gitlab Tokens (max $iSince days old) .. critical: $iCou
 echo
 
 echo "$output"
-rm -f /tmp/gitlab-tokens.json /tmp/gitlab-users.json
\ No newline at end of file
+
+# cleanup
+rm -f "$OUT_TOKENS" "$OUT_USERS"
+
+ph.exit
+
+# ----------------------------------------------------------------------
diff --git a/docs/20_Checks/check_gitlab_tokens.md b/docs/20_Checks/check_gitlab_tokens.md
index e93bbae26babc57fe51442fa4a23223d98089b63..50ac7121dab949f53847f0a21e4c652d4415b7ce 100644
--- a/docs/20_Checks/check_gitlab_tokens.md
+++ b/docs/20_Checks/check_gitlab_tokens.md
@@ -4,12 +4,36 @@
 
 **check_gitlab_tokens** checks all newer tokens of projects and groups if they expire soon. You can set a warning and a critical level in days.
 
+Gitlab has an api requrest `/personal_access_tokens` but it doesn't have the information about the project or usergroup where it is defined.
+This check executes additional requests to show it and offers the url to the web linkinterface.
+
+The check returns
+
+* unknown - the http request to gitlab api failed
+* critical - min. 1 token is expiring soon 
+* warning - min. 1 token reached the warning level (and no criritical token was found)
+* ok - api request was successful; no critical or warning token was found.
+
 ## Requirements
 
 * curl
 * Bash REST API client<br>A set of class like functions with a http. prefix. <br>Docs: <https://os-docs.iml.unibe.ch/bash-rest-api-client/>
 
-Extract or Git pull the Bash REST API client somewhere in your filesystem. eg. /opt/bash-api-client/. With the parameter `-r <FILE>` you point to the file `rest-api-client.sh`. 
+Extract or Git pull the Bash REST API client somewhere in your filesystem. eg. /opt/bash-api-client/. With the parameter `-r <FILE>` you point to the file `rest-api-client.sh`.
+
+## Configuration
+
+The script needs to connect to the Gitlab API.
+You need to create a token in a admin group to read all tokens of all projects.
+
+Put 2 bash variabbles into `/etc/icinga2/gitlab.cfg`:
+
+```shell
+GITLAB_API='https://gitlab.example.com/api/v4'
+GITLAB_TOKEN='glpat-1234567890'
+```
+
+You can use another filename for this configuration - but then you need the parameter `-g <FILE>`to reference it.
 
 ## Syntax
 
@@ -18,7 +42,7 @@ Extract or Git pull the Bash REST API client somewhere in your filesystem. eg. /
 ______________________________________________________________________
 
 CHECK_GITLAB_TOKENS
-v0.1
+v1.0
 
 (c) Institute for Medical Education - University of Bern
 Licence: GNU GPL 3
@@ -26,7 +50,7 @@ Licence: GNU GPL 3
 https://os-docs.iml.unibe.ch/icinga-checks/Checks/check_gitlab_tokens.html
 ______________________________________________________________________
 
-Check gitlab tokens and warn if a token expires soon.
+Check gitlab tokens and warn if tokens expire soon.
 
 This check fetches the gitlbab tokens created in the last 395 days
 from the Gitlab API. It skips
@@ -34,8 +58,11 @@ from the Gitlab API. It skips
     - personal access tokens of users
     - revoked tokens
 
+The script can run several seconds depending on count of tokens, projects
+and users. Maybe you want to call it with a longer interval.
+
 SYNTAX:
-check_gitlab_tokens [-w WARN_LIMIT] [-c CRITICAL_LIMIT]
+check_gitlab_tokens [OPTIONS]
 
 OPTIONS:
 
@@ -53,30 +80,36 @@ PARAMETERS:
 
     None.
 
-EXAMPLE:
-    check_gitlab_tokens -w 28 -c 7
+EXAMPLES:
 
-```
+    check_gitlab_tokens -w 28 -c 7
+        Set other warning and critical level
 
-### Parameters
+    check_gitlab_tokens -g ./gitlab.cfg
+        Set a custom gitlab config file
 
-Add directories to check.
-Set a directory that is writable for world or prepared to be accessible for the icinga user.
+    check_gitlab_tokens -r /opt/bash-api-client/bash-api-client.sh
+        Set a custom gitlab config file
 
-## Configuration
-
-The script needs to connect to the Gitlab API.
-You need to create a token in a admin group to read all tokens of all projects.
+```
 
-Put 2 bash variabbles into `/etc/icinga2/gitlab.cfg`:
+## Example
 
-```shell
-GITLAB_API='https://gitlab.example.com/api/v4'
-GITLAB_TOKEN='glpat-1234567890'
-```
+The execution of  `check_gitlab_tokens` returns
 
-You can use another filename for this configuration - but then you need the parameter `-g <FILE>`to reference it.
+* a status line with found tokens total, count of warning and critical
+* one line per token with
+  * date of expiration
+  * status; one of OK, warning, critical based on number of days before expiring
+  * name of thwe token
+  * name of the project or group
+  * web link to the token page of the project or group
 
-## Example
+```text
+OK: 16 Gitlab Tokens (max 395 days old) .. critical: 0 (10 days) .. warnings: 0 (30 days)
 
-`check_gitlab_tokens -r /opt/rest-api-client/rest-api-client.sh`
+2025-01-17  OK        changelog - demoproject <https://gitlab.example.com/test/demoproject/-/settings/access_tokens>
+2025-01-23  OK        read_repo - demoproject <https://gitlab.example.com/test/demoproject/-/settings/access_tokens>
+2025-03-14  OK        api_token - admin <https://gitlab.example.com/admin/sysadminstuff/-/settings/access_tokens>
+...
+```
\ No newline at end of file