CCS Manager - API Guide

API Guide

Overview

This application provides a REST API with an OpenAPI (Swagger) specification and an interactive Swagger UI. Use Swagger UI for exploration and testing, and the OpenAPI JSON for client generation or API gateways.

Swagger UI

Open /api-docs to view the interactive documentation. Click "Authorize", choose the required scopes (api.read, api.write), and sign in.

  • Sign in with your CANCOM Entra ID (SSO) account.
  • PKCE is used in Swagger UI; the client_id must be set (it's preconfigured by the server). No client secret is required.
  • Leave the client secret field empty in Swagger UI. The client_id must remain populated.
  • Alternatively, paste a valid Entra ID JWT into the bearer scheme input (Authorize "bearer"). Static ad-hoc tokens are not accepted for the main API.
  • After authorizing, calls include the access token until you click "Logout" in the UI or refresh the browser.

Delegated access (user sign-in)

Use this for interactive/browser/device flows where a user signs in and grants delegated scopes. Replace {TENANT_ID} with your Entra tenant ID and {API_APP_ID_URI} with the CCS Manager API Application ID URI (e.g., api://<API_APP_ID>).

Authorization Code + PKCE (browser/app):

  1. Register a public/SPA client; add a redirect URI (e.g., https://your-app/callback).
  2. Add delegated scopes of the API (e.g., {API_APP_ID_URI}/api.read, api.write) and grant consent.
  3. Start the flow against /authorize with response_type=code, code_challenge, code_challenge_method=S256, scope={API_APP_ID_URI}/api.read offline_access openid profile.
  4. Exchange the code at the token endpoint: grant_type=authorization_code, code_verifier, client_id, redirect_uri.

Device Code (CLI-friendly):

# 1) Request device code
POST https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/devicecode
client_id={CLIENT_ID}&scope={API_APP_ID_URI}/api.read offline_access

# 2) Confirm code in browser, then poll token
POST https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token
grant_type=device_code&client_id={CLIENT_ID}&device_code={DEVICE_CODE}

API call example (curl):

curl -H "Authorization: Bearer <access_token>" https://<host>/api/v1/serviceareas

PowerShell delegated sample: ./sample-delegated-serviceareas.ps1 -ApiBaseUrl "https://api.manager.converged.services" -ResourceAppIdUri "{API_APP_ID_URI}" (uses az account get-access-token for the signed-in user).

Notes:

  • Use delegated scopes (api.read/api.write) for user flows; do not use /.default here.
  • For app-to-app/daemon, use client credentials with scope={API_APP_ID_URI}/.default (see machine-to-machine below).

Machine-to-machine access (client credentials)

For backend services, daemons, or automations without user interaction, the CCS Manager API is accessed via an Entra ID app registration using the OAuth 2.0 Client Credentials flow (grant_type=client_credentials). Calls run as application permissions: permissions are assigned to the calling application (caller app) and are clearly separated from interactive user permissions.

The calling application operates only in its own technical identity (“app identity”) and not in the context of a signed-in user—no interactive sign-in, no delegated user rights.

Typical scenarios

  • Ansible Automation, GitLab Runner, Azure Automation, etc.
  • Background processes that maintain or synchronise data in CCS Manager
  • Integrations that periodically read data from the CCS Manager API

High-level flow

  • A dedicated Entra ID app registration (caller app) authenticates with a client secret or certificate.
  • Application permissions (app roles) on the CCS Manager API are assigned to this caller app.
  • A tenant admin grants admin consent for the requested app roles.
  • The caller app requests a token via client credentials with grant_type=client_credentials and scope={API_APP_ID_URI}/.default.
  • Each API call sends Authorization: Bearer <ACCESS_TOKEN>. The API validates issuer, audience, and app roles (e.g., CcsManager.Api.Read).

PowerShell sample (secret or cert): ./sample-client-credentials.ps1 -TenantId "<TENANT_ID>" -ClientId "<CLIENT_ID>" -ClientSecret "<SECRET>" or ./sample-client-credentials.ps1 -TenantId "<TENANT_ID>" -ClientId "<CLIENT_ID>" -CertificatePath .\\caller.pfx -CertificatePassword "<PFX_PASSWORD>".

Concept – short overview

1) API app “CCS Manager” (resource app)

  • Name: CCS Manager
  • App ID: {API_APP_ID}
  • Application ID URI: {API_APP_ID_URI}
  • Defines app roles such as CcsManager.Api.Read, CcsManager.Api.WriteAssignments, CcsManager.Api.AuditRead, …
  • This resource app is already fully configured—no changes needed.

2) Caller app (your integration)

  • Own Entra ID app registration (e.g., “CCS Git-Server (Ansible Automation Plattform)”).
  • Runs headless and calls the CCS Manager API via client credentials.

Steps at a glance

  1. Register the caller app: Request an app registration via service desk (queue CCK-IT-MS-INFRA), include desired app name and purpose. The Entra team creates it and assigns owners; owners then configure roles/secrets/certs.
  2. Assign application permissions (app roles): In the caller app -> API permissions -> Add a permission -> APIs my organization uses -> CCS Manager -> under Application permissions select roles (e.g., CcsManager.Api.Read, ...WriteAssignments, ...AuditRead) -> Add permissions.
  3. Grant admin consent: Create a ticket to CCK-IT-MS-INFRA naming the caller app (name + client ID) and required roles. An Entra admin runs “Grant admin consent for …”. Without this, token requests will fail (e.g., AADSTS65001 or AADSTS501051).
  4. Add credentials (secret or certificate): In Certificates & secrets, prefer certificates; if using secrets, keep them short-lived and rotated.
  5. Obtain a token via client credentials: Call the v2.0 token endpoint with grant_type=client_credentials and scope={API_APP_ID_URI}/.default, authenticating with the caller app’s client_id plus secret or cert (JWT client assertion). The roles claim will contain the granted app roles.
  6. Call the API with a bearer token: Send Authorization: Bearer <ACCESS_TOKEN> on every request; the API checks audience, issuer, and roles.

PowerShell sample (secret or cert): ./sample-client-credentials.ps1 -TenantId "{TENANT_ID}" -ClientId "" -ClientSecret "" or ./sample-client-credentials.ps1 -TenantId "{TENANT_ID}" -ClientId "" -CertificatePath .\\caller.pfx -CertificatePassword "".

Token request – client secret

POST https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={CLIENT_ID}
client_secret={CLIENT_SECRET}
grant_type=client_credentials
scope={API_APP_ID_URI}/.default
  • CLIENT_ID = caller app ID
  • TENANT_ID = tenant ID (e.g., 21fe9e59-ae52-479c-ae85-68f3aa06763d)
  • scope must end with /.default to include granted app roles.

Token request – certificate (JWT client assertion)

POST https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={CLIENT_ID}
grant_type=client_credentials
scope={API_APP_ID_URI}/.default
client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
client_assertion={JWT_SIGNED_WITH_UPLOADED_CERT_PRIVATE_KEY}

client_assertion is a JWT signed with the private key of the certificate uploaded to the caller app:

  • aud = https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token
  • iss and sub = CLIENT_ID
  • Short expiry (e.g., 5 minutes)

Example (decoded) JWT assertion header/payload:

{
  "alg": "RS256",
  "typ": "JWT",
  "x5t": "N2Y5MWMzYjQ2YzJhNGMwZmI5MWMwYWY2NTEyZGU0YjI"
}

{
  "aud": "https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token",
  "iss": "00000000-0000-0000-0000-000000000000",
  "sub": "00000000-0000-0000-0000-000000000000",
  "jti": "2d3c7d71-2c5f-4a16-9c84-bf6f3ce6d9dd",
  "nbf": 1733222400,
  "exp": 1733222700
}

The actual JWT is the base64url-encoded, signed header.payload.signature string sent as client_assertion.

API call

GET https://api.manager.converged.services/api/v1/serviceareas
Authorization: Bearer {ACCESS_TOKEN}
Accept: application/json

The API checks audience ({API_APP_ID_URI}), token validity, issuer (tenant), and roles (e.g., CcsManager.Api.Read). Failures typically return 401 (no/invalid token) or 403 (insufficient roles).

Troubleshooting

AADSTS501051 – Application is not assigned to a role for the application

  • Cause: No CcsManager.Api.* role assigned or no admin consent.
  • Fix: Add Application permissions on CCS Manager, then request admin consent (ticket to CCK-IT-MS-INFRA).

AADSTS65001 – consent_required / invalid_grant

  • Cause: Admin consent not yet granted.
  • Fix: Add Application permissions and trigger admin consent via ticket.

AADSTS650057 – Invalid resource

  • Cause: Wrong scope or CCS Manager not added as a resource permission.
  • Fix: Ensure scope={API_APP_ID_URI}/.default and the CCS Manager resource with required app roles is present under API permissions.

Authentication

API requests require a valid Entra ID access token with the audience set to this API. For delegated calls use scopes (api.read/api.write); for client-credentials calls use app roles via scope=.../.default and the resulting roles claim.

  • 401 Unauthorized: missing/invalid/expired token (WWW-Authenticate describes the reason).
  • 403 Forbidden: insufficient scope/role (header may include the required scope/role).

Health & Readiness

Health (liveness): GET /healthz - process alive (no dependency checks).

Readiness: GET /ready - API ready; checks dependencies (e.g., database) and returns 200 when ready, 503 otherwise.

# Liveness
curl -sS http://localhost:8080/healthz | jq .

# Readiness
curl -i http://localhost:8080/ready
  • Use /healthz for liveness (restart decision).
  • Use /ready for readiness (traffic gating/alerts).