> ## Documentation Index
> Fetch the complete documentation index at: https://docs.synq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Users management

### users\_management

Find full example [here](https://github.com/getsynq/api/tree/main/examples/http/users_management)

#### users.sh

```bash theme={null}
#!/usr/bin/env bash
#
# Users Management Example (HTTP/JSON) - list workspace members and manage their roles
#
# Commands:
#     ./users.sh list
#     ./users.sh add-role <identity> <role>
#     ./users.sh remove-role <identity> <role>
#     ./users.sh set-roles <identity> <role> [<role> ...]
#
# <identity> is an email address or any identity string from `list`
# ("synq:<user_id>", "email:<email>", "slack:<id>", "msteams:<id>").
# <role> is one of: admin, developer, analyst, business_user.
#
# Prerequisites:
# - curl and jq
# - SYNQ_CLIENT_ID and SYNQ_CLIENT_SECRET environment variables
#   (Read Users scope for `list`, Edit Users scope for the role commands)
# - Optional API_ENDPOINT (defaults to developer.synq.io)

set -euo pipefail

if [[ -f .env ]]; then
  set -a
  # shellcheck disable=SC1091
  source .env
  set +a
fi

API_ENDPOINT="${API_ENDPOINT:-developer.synq.io}"
BASE_URL="https://${API_ENDPOINT}/api/users/v1"

if [[ -z "${SYNQ_CLIENT_ID:-}" || -z "${SYNQ_CLIENT_SECRET:-}" ]]; then
  echo "SYNQ_CLIENT_ID and SYNQ_CLIENT_SECRET must be set" >&2
  exit 1
fi

ROLE_NAMES=(admin developer analyst business_user)

usage() {
  sed -n '3,13p' "$0" | sed 's/^# \{0,1\}//' >&2
  exit 2
}

role_value() {
  case "$1" in
    admin) echo USER_ROLE_ADMIN ;;
    developer) echo USER_ROLE_DEVELOPER ;;
    analyst) echo USER_ROLE_ANALYST ;;
    business_user) echo USER_ROLE_BUSINESS_USER ;;
    *)
      echo "unknown role '$1', expected one of: ${ROLE_NAMES[*]}" >&2
      exit 2
      ;;
  esac
}

# Accepts a bare email address as shorthand for "email:<address>".
to_identity() {
  if [[ "$1" != *:* && "$1" == *@* ]]; then
    echo "email:$1"
  else
    echo "$1"
  fi
}

TOKEN=""

obtain_token() {
  TOKEN=$(curl -sS --fail-with-body "https://${API_ENDPOINT}/oauth2/token" \
    --data-urlencode "client_id=${SYNQ_CLIENT_ID}" \
    --data-urlencode "client_secret=${SYNQ_CLIENT_SECRET}" \
    --data-urlencode "grant_type=client_credentials" | jq -r '.access_token')
}

api() {
  local method="$1" path="$2" body="${3:-}"
  local args=(-sS --fail-with-body -X "$method" "${BASE_URL}${path}" -H "Authorization: Bearer ${TOKEN}")
  if [[ -n "$body" ]]; then
    args+=(-H "Content-Type: application/json" --data "$body")
  fi
  curl "${args[@]}"
}

# Fields left at their default are omitted from the JSON, hence the `//` fallbacks.
FORMAT_USER='
  def role: {USER_ROLE_ADMIN: "admin", USER_ROLE_DEVELOPER: "developer",
             USER_ROLE_ANALYST: "analyst", USER_ROLE_BUSINESS_USER: "business_user"}[.] // .;
  def status: {USER_STATUS_ACTIVE: "active", USER_STATUS_INVITED: "invited"}[. // ""] // "-";
  [ (.email // ""),
    ((((.firstName // "") + " " + (.lastName // "")) | ltrimstr(" ") | rtrimstr(" ")) | if . == "" then "-" else . end),
    ((.roles // []) | map(role) | join(",") | if . == "" then "-" else . end),
    (.status | status),
    (.lastLoginAt // "never")
  ] | @tsv'

print_users() {
  jq -r "$FORMAT_USER" | awk -F'\t' '{ printf "%-40s %-30s %-30s %-8s %s\n", $1, $2, $3, $4, $5 }'
}

list_users() {
  local resp
  resp=$(api GET "")
  printf "%-40s %-30s %-30s %-8s %s\n" EMAIL NAME ROLES STATUS "LAST LOGIN"
  jq -c '(.users // []) | sort_by(.email) | .[]' <<<"$resp" | print_users
  printf "\n%s users\n" "$(jq '(.users // []) | length' <<<"$resp")"
}

add_role() {
  local identity="$1" role="$2" resp
  resp=$(api POST /roles "$(jq -n --arg identity "$identity" --arg role "$(role_value "$role")" '{identity: $identity, role: $role}')")
  echo "Added ${role} to ${identity}"
  jq -c '.user' <<<"$resp" | print_users
}

remove_role() {
  local identity="$1" role="$2" resp
  resp=$(api POST /roles/remove "$(jq -n --arg identity "$identity" --arg role "$(role_value "$role")" '{identity: $identity, role: $role}')")
  echo "Removed ${role} from ${identity}"
  if jq -e '.user' <<<"$resp" >/dev/null; then
    jq -c '.user' <<<"$resp" | print_users
  else
    echo "User is not a member of the workspace"
  fi
}

current_roles() {
  local identity="$1" email=""
  if [[ "$identity" == email:* ]]; then
    email=$(tr '[:upper:]' '[:lower:]' <<<"${identity#email:}")
  fi
  api GET "" | jq -r --arg identity "$identity" --arg email "$email" '
    def role: {USER_ROLE_ADMIN: "admin", USER_ROLE_DEVELOPER: "developer",
               USER_ROLE_ANALYST: "analyst", USER_ROLE_BUSINESS_USER: "business_user"}[.] // empty;
    [ (.users // [])[]
      | select(((.identities // []) | index($identity)) or ($email != "" and ((.email // "") | ascii_downcase) == $email)) ]
    | first // {} | (.roles // [])[] | role'
}

# Adds before removing so the user never loses membership midway.
set_roles() {
  local identity="$1"
  shift
  local wanted current role
  for role in "$@"; do role_value "$role" >/dev/null; done
  wanted=$(printf '%s\n' "$@" | sort -u)
  current=$(current_roles "$identity" | sort -u)

  if [[ "$wanted" == "$current" ]]; then
    echo "${identity} already holds $(paste -sd, - <<<"$wanted" | sed 's/,/, /g')"
    return
  fi
  for role in $(comm -23 <(echo "$wanted") <(echo "$current")); do
    add_role "$identity" "$role"
  done
  for role in $(comm -13 <(echo "$wanted") <(echo "$current")); do
    remove_role "$identity" "$role"
  done
}

main() {
  [[ $# -ge 1 ]] || usage
  local command="$1"
  shift

  case "$command" in
    list)
      [[ $# -eq 0 ]] || usage
      obtain_token
      list_users
      ;;
    add-role | remove-role)
      [[ $# -eq 2 ]] || usage
      role_value "$2" >/dev/null
      obtain_token
      "${command/-/_}" "$(to_identity "$1")" "$2"
      ;;
    set-roles)
      [[ $# -ge 2 ]] || usage
      local identity
      identity=$(to_identity "$1")
      shift
      obtain_token
      set_roles "$identity" "$@"
      ;;
    *)
      usage
      ;;
  esac
}

main "$@"
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.