Skip to content

SAIF CLI

The SAIF CLI is a command-line tool for automating SAIF platform development tasks.


Description

The SAIF CLI is a console app published to a NuGet Feed and installed using the dotnet CLI. It automates processes that developers might do manually, including:

  • Creating new projects from templates (dotnet new)
  • Setting up Azure DevOps pipelines
  • Creating Azure DevOps repositories
  • Configuring project infrastructure
  • Generating and inspecting JWT tokens
  • Discovering Entra ID application registrations

Why we use it

We use the SAIF CLI to provide a single interface for operations across multiple cloud services. All Forge projects are created using the CLI to ensure standard setup and naming conventions.


Commands

saif auth generate

Generate an OAuth token for a SAIF application. See JWT Test Tokens for full usage.

saif auth generate --name <application-name>

saif auth validate

Decode and inspect a JWT token. Displays a claims breakdown and resolves the audience to an Entra ID app registration.

# Run without arguments and paste the token interactively (recommended)
saif auth validate

# Or pass the token directly as an argument
saif auth validate eyJ0eXAiOiJKV1Qi...

Note

Long tokens pasted directly on the command line may cause errors due to terminal word-wrapping. See JWT Test Tokens — Troubleshooting for workarounds.

Output includes:

  • Claims table — audience, issuer, subject, tenant ID, name, scopes, roles, timestamps
  • Expiry status — highlighted in green (valid) or red (expired)
  • Audience resolution — looks up the Entra ID app registration matching the aud claim

Search or list Entra ID application registrations.

# Search by audience URI or GUID (tries identifier URI, app ID, and api://{guid} patterns)
saif app search --audience api://my-app-test
saif app search --audience 00000000-0000-0000-0000-000000000000

# Search by application (client) ID directly
saif app search --appid 00000000-0000-0000-0000-000000000000

# List all registrations
saif app search --all

# List with a name filter
saif app search --all --name my-app

# List only apps that have an audience URI configured
saif app search --all --filter audience
Option Description
--audience Search by audience value / identifier URI (e.g., api://my-app-test). If a bare GUID is provided, the CLI will attempt to resolve it as both an app ID and api://{guid}.
--appid Search by application (client) ID
--all List all application registrations
--name Partial name filter (use with --all)
--filter Post-filter results; audience shows only apps with an identifier URI (use with --all)

Note

--audience, --appid, and --all are mutually exclusive — specify exactly one. If your JWT aud claim is a GUID, you can use either --appid or --audience; --appid is recommended for clarity.

Search for services by name. A service consolidates everything known about a service under a common key (the base name with environment suffixes stripped): its per-environment Entra ID application registrations (e.g. payments-platformdev, payments-test, payments-prod) and its correlated source repository.

The query is a case-insensitive substring match against the application registration display names — searching pol finds every service whose registrations contain pol, and policyportal finds pol-api-exp-policyportal, pol-func-policyportal, and pol-web-policyportal. Only registrations that carry a known environment suffix (see below) qualify as service facets, so free-text, vendor, or COTS application registrations (e.g. Navex PolicyTech, Guidewire PolicyCenter App) are never surfaced as services. Matching registrations are then consolidated into services.

Minimum query length

The query must be at least 2 characters. Single-character queries are rejected immediately because they would match an impractically large fraction of the tenant and are unlikely to be useful.

Large tenants — results may be incomplete

Service search scans up to 2 000 application registrations per query. In tenants with many registrations a broad query (e.g. api) may hit this limit before all registrations are scanned; when that happens the output shows a notice that results may be incomplete. Narrow your query to get a complete result set.

# Find a service across all environments
saif service search payments

# Match on any segment of the name (here, the app-name segment)
saif service search policyportal

# Expand per-environment application registration details (app ID, audience URI)
saif service search payments --verbose

# Machine-readable output
saif service search payments --format json
Option Description
<query> Name or pattern to search for; matches any substring of the service name (e.g. payments, policyportal)
--verbose Show per-environment application registration details (app ID, audience URI)
--format Output format: table (default) or json

The same service-consolidation logic powers saif search and the search_services MCP tool, so results stay consistent across the CLI and agent surfaces.

Recognized environments

A service is defined by the platform naming convention and deployed per-environment, so only application registrations ending in a known environment suffix — platformdev, test, qa, uat, or prod (e.g. payments-prod) — count as service facets. Registrations without one of these suffixes are not platform services and are excluded from results.

Repository correlation

Service names follow {domain}-{app type}{-app sub type}-{app name}. Each service is matched to a repository by an exact-name lookup, probing in order: the full service name, then {domain}-{app name}, then the bare app name. The first exact match wins. A component that has no repository of its own — for example a web or func sibling whose code lives in another component's repository — simply shows no repository ( in table output, null in JSON).

saif service describe

Show a full topology view of a single service: its repository (URL and branch), each environment's Entra ID application registration, the permissions that registration requests against target APIs, and a pipeline count with a follow-up hint.

The <name> is resolved by an exact service-key match (the base name with environment suffixes stripped), so both the base name and any environment-suffixed form resolve to the same service. A name that matches no service exits with a clear "not found" error.

# Describe a service by its base name
saif service describe payments

# An environment-suffixed name resolves to the same service
saif service describe payments-prod

# Limit the view to a single environment
saif service describe payments --environment prod

# Machine-readable output
saif service describe payments --format json

# Full topology: includes service key, pipeline IDs/URLs, App ID and permission GUIDs
saif service describe payments --verbose
Option Description
<name> The service name to describe; resolved by exact service key (environment suffix optional)
--environment, --env Limit the view to a single environment (e.g. prod, platformdev). Errors with the available environments if the service has no such environment.
--format Output format: table (default) or json
--verbose Show additional low-level identifiers: service key, pipeline IDs and URLs, and the App ID (resourceAppId) and permission GUID (id) for each permission entry. In JSON output, adds a pipelines array and surfaces resourceAppId/id per permission.

Permissions

For each environment's application registration, describe reads the registration's requested permissions (Graph requiredResourceAccess) and resolves them to human-readable values against each target API's service principal: delegated permissions (OAuth2 scopes) and application permissions (app roles). Target lookups are cached so an API shared across many apps (e.g. Microsoft Graph) is resolved once. Permission resolution is best-effort — if it fails for an environment (e.g. a transient Graph error, timeout, or malformed response), that environment's permissions render as unavailable (null in JSON) while the rest of the topology still renders. A target API that cannot be resolved falls back to its App ID, and a permission whose id cannot be resolved falls back to its raw id — in both table and JSON output, even without --verbose.

saif app vs saif service

saif app search operates on individual Entra ID application registrations — the raw, flat facets keyed by audience, app ID, or name. saif service works one level up: it consolidates those registrations into logical services (grouping payments-platformdev, payments-test, payments-prod under payments), correlates the source repository, and — for describe — resolves each environment's requested permissions. Use saif app to look up a specific registration; use saif service for the cross-environment, repository-aware view of a service.

Building features that read live Azure state

To extend service describe (or any command) with live deployed/runtime state — image tags, logs, health, cost — first read the Service ↔ Azure Resource Model. It documents the app-registration ↔ App Service naming invariant, the one-subscription-per-environment topology, how to discover resources via Azure Resource Graph by exact name, and the deployed-version tag contract.


Guide Description
Service ↔ Azure Resource Model Map a service to its live Azure resources
Installing the SAIF CLI Setup, configuration, and troubleshooting
JWT Test Tokens Generate and use tokens for API testing