Schedule Fabric Admin API Reads from GitHub Actions Without Secrets: A Step By Step Guide

Have you ever had a scheduled job stop working because someone’s client secret expired? Or wanted to keep the Power BI activity log longer than the 30 days Microsoft gives you, but had no capacity to run anything in?

In this guide we set up a GitHub Action that reads from the Fabric admin API on a schedule, with no secret to store or rotate. We list workspaces to prove it works, and then point it at the more interesting endpoints: activity events, tenant settings, the Scanner API.

The trick is OIDC (OpenID Connect), which Entra now supports. GitHub proves which repository is calling and Entra hands out a short-lived token. You don’t need to care much about the standard, only that there is nothing to expire.

The steps in this guide are:

  1. Create an identity in Entra and give it API read-only permission in Fabric.
  2. Set up trust between GitHub and Entra, so no secret is needed.
  3. Create a simple workflow that lists the workspaces.
  4. Point the same workflow at something more interesting!

Prerequisites

  • Microsoft Fabric or Power BI tenant (Fabric capacity not needed!).
  • access to the Fabric admin portal.
  • Entra administrator on hand for the one-time Entra Group and App Registration setup.
  • GitHub repository with Actions enabled.

Step 1 – Create a read-only identity

A scheduled job cannot sign in as a user so it needs its own non-human identity. Two Entra objects are involved: an app registration (with its service principal) that will be connected to the workflow. The app registration needs to be part of a security group that will be granted API access in Fabric.

In the Entra admin center:

Entra ID β†’ App registrations β†’ New registration

Create a single-tenant application named Fabric External Reader, then copy two values from the overview page:

  • the Application (client) ID
  • the Directory (tenant) ID

Do not create a client secret. Avoiding it is the point πŸ™‚

You do not need to give the App Registration any permissions either.

Next create the security group:

Entra ID β†’ Groups β†’ All groups β†’ New group

Give the group a name like Fabric Read-only Admin API.

Open the group, go to Members, and add the Fabric External Reader enterprise application.

image.png

Next go to the Fabric admin portal:

Admin portal β†’ Tenant settings β†’ Admin API settings

Enable Service principals can access read-only admin APIs, restrict it to Specific security groups, and add Fabric Read-only Admin API.

This is where the permission is granted, and it is a tenant setting plus a group membership rather than a more extensive admin-consented application permission.

Give it a few minutes before testing.

image.png

Step 2 – Establish trust between GitHub and Entra

Create a repository and note its organization and repository name that is found here:

Settings β†’ Actions β†’ OIDC configuration

GitHub shows the subject prefix in the form:

repo:<ORGANIZATION>@<ORGANIZATION-ID>/<REPOSITORY>@<REPOSITORY-ID>

Back in the Fabric External Reader app registration:

Certificates & secrets β†’ Federated credentials β†’ Add credential

Choose the GitHub Actions scenario and fill in:

  • Organization
  • Organization ID
  • Repository name
  • Entity type: Branch
  • GitHub branch name: main

Name it something you will recognise later, like github-fabric-external-reader-main.

When the workflow is run GitHub signs a token asserting which repository and branch is running, Entra then checks that assertion against this credential, and if it matches, access to the API is granted!

image.png

The workflow still needs to know which tenant and which application it is supposed to call. Both are identifiers (like an address), not passwords. They can be saved as repository variables.

Settings β†’ Secrets and variables β†’ Actions β†’ Variables β†’ New repository variable

Create FABRIC_READER_APPLICATION_ID and FABRIC_READER_DIRECTORY_ID from the two values you copied in Step 1.

image.png

Step 3 – Add the workflow

In the repository:

Actions β†’ New workflow β†’ Set up a workflow yourself

Rename the file to fabric-settings.yml, paste the YAML below, and commit.

image.png

name: Read Fabric tenant

on:
  workflow_dispatch:

  schedule:
    - cron: "0 5 * * *"

permissions:
  contents: read
  id-token: write

env:
  FABRIC_API_URL: https://api.fabric.microsoft.com/v1/admin/workspaces

jobs:
  read:
    runs-on: ubuntu-latest

    steps:
      - name: Sign in to Entra with GitHub OIDC
        uses: azure/login@v3
        with:
          client-id: ${{ vars.FABRIC_READER_APPLICATION_ID }}
          tenant-id: ${{ vars.FABRIC_READER_DIRECTORY_ID }}
          allow-no-subscriptions: true

      - name: Call the Fabric admin API
        run: |
          set -euo pipefail

          ## Exchange the sign-in for a token scoped to the Fabric API ##
          TOKEN=$(az account get-access-token \
            --resource https://api.fabric.microsoft.com \
            --query accessToken \
            -o tsv | tr -d '\n')

          if [ -z "$TOKEN" ]; then
            echo "Could not acquire an access token." >&2
            exit 1
          fi

          mkdir -p output

          ## Call the endpoint and keep the raw response ##
          HTTP=$(curl -sS \
            -w "%{http_code}" \
            -o output/response.json \
            -H "Authorization: Bearer $TOKEN" \
            "$FABRIC_API_URL")

          echo "HTTP $HTTP"

          if [ "$HTTP" != "200" ]; then
            cat output/response.json >&2
            exit 1
          fi

          ## Plausibility check: 200 with nothing in it is still a failure ##
          COUNT=$(jq '.workspaces | length' output/response.json)
          echo "Workspaces returned: $COUNT"

          if [ "$COUNT" -eq 0 ]; then
            echo "The call succeeded but returned nothing. Treating as a failure." >&2
            exit 1
          fi

          jq -r '.workspaces[] | "\(.name)\t\(.type)"' output/response.json \
            | sort \
            | head -20

      - name: Upload the response
        if: ${{ always() }}
        uses: actions/upload-artifact@v4
        with:
          name: fabric-response
          path: output/
          if-no-files-found: warn
          retention-days: 7

Run it manually from the Actions tab. The log should print HTTP 200, a workspace count, and the first twenty workspace names.

image.png

Two lines in there are important to note:

id-token: write does not grant write access to anything (despite how it reads). It lets the job ask GitHub for an identity token so Entra can authenticate it.

uses: azure/login@v3 has no client-secret parameter! If you have wired this up the traditional way before you would also have a client secret to manage and rotate.

Step 4 – Point it at something else

Most of our work so far has been focused on authentication and not on what the API can actually deliver. The call itself is one URL in an env: block, so reading something else means changing that line.

Some of the read-only admin surface you might find useful:

/v1/admin/workspaces          workspaces across the tenant (what we use in the example)
/v1/admin/tenantsettings      every tenant setting and its current state
/v1/admin/capacities          capacities and their state
/v1/admin/items               items across all workspaces

The Power BI admin APIs are also reachable with the same identity, using https://analysis.windows.net/powerbi/api as the token resource.

This API gets you activity events, refresh information, and the Scanner API, which returns a full tenant inventory with lineage.

Back to the two questions I opened with. These are the things that make a scheduled GitHub job interesting to me:

The activity log is only kept for 30 days. After that it is gone, and Microsoft’s own guidance is to export events somewhere external and report from there. A scheduled job that saves the activity log indefinitely would be awesome to have for long term usage statistics!

Many Power BI tenants do not have any Fabric Capacity. A Power BI Pro tenant without a capacity will miss out on most Fabric monitoring tools. The admin API does not need a Fabric capacity. It answers regardless of capacity state (mine was off while I ran the workflow), you don’t even need a capacity.

Conclusion

We have created a GitHub job that reads from the Fabric API without the need to manage secrets. We extracted the workspace list to prove that it works. From here the same workflow can archive activity events past their 30-day window, snapshot tenant settings, or inventory a Pro tenant that has no capacity to run anything in.

Entra trusts whatever OIDC issuer you point a federated credential at, as long as that issuer supports this. GitHub is easy to use since the portal has a filled-in wizard for it.

But the same shape (federated credential, no secret, short-lived token) is available from services like Azure DevOps, Kubernetes or Azure Functions.

References

Leave a Reply

Discover more from Data & Analytics Consultant | Jonas Hertz

Subscribe now to keep reading and get access to the full archive.

Continue reading