API explorer and Postman collections
Overview
Ready-made Postman, Insomnia, and Bruno collections, an OpenAPI 3.0 specification, and a bash + jq CI smoke test covering the full LuxID token flow.
LuxID provides a ready-made Postman collection and an OpenAPI specification covering every endpoint in the token flow. These resources let you:
- Explore the API interactively without writing any code first.
- Run the full Authorization Code + PKCE flow step by step.
- Test token refresh, UserInfo, introspection, and revocation in isolation.
- Use the collection as a reference when building your own HTTP client integration.
- Run smoke tests against UAT in a CI pipeline.
Downloading the collection
| Resource | Location |
|---|---|
| Postman collection (v2.1 JSON) | https://github.com/luxid-lu/postman-collections (TBC) |
| OpenAPI 3.0 specification | Available via LuxID Console > Developer Resources (TBC) |
| Insomnia export | Included in the GitHub repository (TBC) |
| Bruno collection | Included in the GitHub repository (TBC) |
Alternatively, access these resources via LuxID under Developer Resources > API Collections.
Setting up the Postman environment
The collection uses environment variables for all values that differ between installations. After importing the collection, create a new Postman environment with the following variables:
| Variable | UAT value | Production value |
|---|---|---|
luxid_base_url | https://login-uat.luxid.lu | https://login.luxid.lu |
luxid_client_id | Your UAT Client ID | Your production Client ID |
luxid_client_secret | Your UAT Client Secret | Your production Client Secret |
luxid_redirect_uri | https://yourapp-uat.example.com/callback | https://yourapp.example.com/callback |
luxid_scopes | openid profile email | openid profile email |
luxid_code_verifier | (auto-generated by collection) | (auto-generated by collection) |
luxid_code_challenge | (auto-generated by collection) | (auto-generated by collection) |
luxid_auth_code | (set manually during the flow) | (set manually during the flow) |
luxid_access_token | (populated automatically) | (populated automatically) |
luxid_id_token | (populated automatically) | (populated automatically) |
luxid_refresh_token | (populated automatically) | (populated automatically) |
Never commit a Postman environment file containing real credentials to version control. Use Postman's secret variable type or a .gitignore-d local environment file.
Collection contents
Authorization code + PKCE walk-through
The Authorization Code + PKCE flow cannot be fully automated in Postman because it requires a browser redirect. The collection handles this with a two-step approach:
Request 1: Generate PKCE parameters and build the authorization URL
A pre-request script generates a cryptographically random code_verifier and computes the code_challenge:
// Postman pre-request script (included in the collection)
const crypto = require('crypto-js');
// Generate code_verifier: 43-128 random URL-safe characters
const verifier = CryptoJS.lib.WordArray.random(32)
.toString(CryptoJS.enc.Base64url);
// Compute code_challenge: BASE64URL(SHA256(code_verifier))
const challenge = CryptoJS.SHA256(verifier)
.toString(CryptoJS.enc.Base64url);
pm.environment.set('luxid_code_verifier', verifier);
pm.environment.set('luxid_code_challenge', challenge);
// Build and log the authorization URL
const params = new URLSearchParams({
response_type: 'code',
client_id: pm.environment.get('luxid_client_id'),
redirect_uri: pm.environment.get('luxid_redirect_uri'),
scope: pm.environment.get('luxid_scopes'),
code_challenge: challenge,
code_challenge_method: 'S256',
state: CryptoJS.lib.WordArray.random(16).toString(CryptoJS.enc.Hex),
nonce: CryptoJS.lib.WordArray.random(16).toString(CryptoJS.enc.Hex),
});
const authUrl = `${pm.environment.get('luxid_base_url')}/mga/sps/oauth/oauth20/authorize?${params}`;
console.log('Open this URL in your browser:', authUrl);
pm.environment.set('luxid_auth_url', authUrl);
After running this request, copy the logged authorization URL, open it in your browser, complete the LuxID login, and copy the code parameter from the callback URL into the luxid_auth_code environment variable.
Request 2: Exchange the authorization code for tokens
POST /mga/sps/oauth/oauth20/token
Host: login-uat.luxid.lu
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code={{luxid_auth_code}}
&redirect_uri={{luxid_redirect_uri}}
&client_id={{luxid_client_id}}
&client_secret={{luxid_client_secret}}
&code_verifier={{luxid_code_verifier}}
A post-response script automatically extracts and stores access_token, id_token, and refresh_token into environment variables for use in subsequent requests.
Token refresh
Exchanges a Refresh Token for a new Access Token and Refresh Token:
POST /mga/sps/oauth/oauth20/token
Host: login-uat.luxid.lu
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token={{luxid_refresh_token}}
&client_id={{luxid_client_id}}
&client_secret={{luxid_client_secret}}
The collection's post-response script updates luxid_access_token and luxid_refresh_token after a successful refresh.
UserInfo
Retrieves the claims for the authenticated user from the UserInfo endpoint:
GET /mga/sps/oauth/oauth20/userinfo
Host: login-uat.luxid.lu
Authorization: Bearer {{luxid_access_token}}
See UserInfo endpoint for full claim reference.
Token introspection
Validates an Access Token server-side and retrieves its metadata:
POST /mga/sps/oauth/oauth20/introspect
Host: login-uat.luxid.lu
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>
token={{luxid_access_token}}
&token_type_hint=access_token
A valid active token returns:
{
"active": true,
"sub": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"client_id": "your-client-id",
"scope": "openid profile email",
"exp": 1748003600,
"iat": 1748000000
}
An expired or invalid token returns {"active": false}. See Token introspection for full reference.
Token revocation
Revokes an Access Token or Refresh Token:
POST /mga/sps/oauth/oauth20/revoke
Host: login-uat.luxid.lu
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>
token={{luxid_refresh_token}}
&token_type_hint=refresh_token
A successful revocation returns HTTP 200 with an empty body. Subsequent introspection of the revoked token returns {"active": false}.
Events API (webhook Subscription management)
The collection includes placeholder requests for Events API webhook subscription management. These will be updated when the Events API is generally available. See the Events section of the developer documentation for the current status.
Using the OpenAPI specification
The OpenAPI 3.0 specification describes every endpoint in machine-readable format. You can use it to:
- Generate client SDK stubs in your language of choice using tools such as
openapi-generator-cliorswagger-codegen. - Import into API design tools (Stoplight, Swagger UI, Redocly) for interactive documentation.
- Validate your HTTP requests against the schema in CI.
Example: generate a TypeScript client
npx @openapitools/openapi-generator-cli generate \
-i luxid-openapi.yaml \
-g typescript-fetch \
-o ./src/luxid-client
Example: serve interactive documentation locally
npx @redocly/cli preview-docs luxid-openapi.yaml
Insomnia
Import the LuxID collection into Insomnia via File > Import > From file, selecting the Insomnia export from the GitHub repository. Set up an environment with the same variables listed in the Postman environment table above.
Insomnia supports PKCE natively via the OAuth 2.0 auth type in the request editor. Set:
- Grant type: Authorization Code
- Authorization URL:
{{luxid_base_url}}/mga/sps/oauth/oauth20/authorize - Access token URL:
{{luxid_base_url}}/mga/sps/oauth/oauth20/token - Client ID:
{{luxid_client_id}} - Client secret:
{{luxid_client_secret}} - Redirect URL:
{{luxid_redirect_uri}} - Scope:
openid profile email - PKCE: enabled (
S256)
Bruno
Bruno is an open-source, Git-friendly API client. The LuxID Bruno collection is a folder of .bru files that can be committed alongside your source code.
Import via Open Collection and select the luxid-bruno/ folder from the repository. Environment variables are stored in luxid-bruno/environments/uat.bru (gitignored by default for secret safety).
CI smoke test: bash + jq
For automated smoke testing of your UAT integration, the following script performs the Authorization Code + PKCE flow end-to-end using a headless browser approach via a test user's credentials. This is suitable for CI pipelines where you control the redirect URI handling.
Prerequisites:
curl,jq, and a UAT test user with known credentials. The redirect URI must be a URI your CI environment can capture (for example,http://localhost:9876/callbackwith a temporary listener).
#!/usr/bin/env bash
# luxid-smoke-test.sh
# Smoke test: Authorization Code + PKCE against LuxID UAT
# Usage: LUXID_CLIENT_ID=xxx LUXID_CLIENT_SECRET=xxx ./luxid-smoke-test.sh
set -euo pipefail
LUXID_BASE_URL="${LUXID_BASE_URL:-https://login-uat.luxid.lu}"
LUXID_CLIENT_ID="${LUXID_CLIENT_ID:?Required}"
LUXID_CLIENT_SECRET="${LUXID_CLIENT_SECRET:?Required}"
LUXID_REDIRECT_URI="${LUXID_REDIRECT_URI:-http://localhost:9876/callback}"
LUXID_SCOPES="${LUXID_SCOPES:-openid profile email}"
TEST_USER_EMAIL="${TEST_USER_EMAIL:?Required}"
TEST_USER_PASSWORD="${TEST_USER_PASSWORD:?Required}"
echo "==> Fetching discovery document"
DISCOVERY=$(curl -sf "${LUXID_BASE_URL}/.well-known/openid-configuration")
TOKEN_ENDPOINT=$(echo "$DISCOVERY" | jq -r '.token_endpoint')
USERINFO_ENDPOINT=$(echo "$DISCOVERY" | jq -r '.userinfo_endpoint')
INTROSPECTION_ENDPOINT=$(echo "$DISCOVERY" | jq -r '.introspection_endpoint')
echo " Token endpoint: ${TOKEN_ENDPOINT}"
echo "==> Generating PKCE parameters"
CODE_VERIFIER=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')
CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" \
| openssl dgst -sha256 -binary \
| openssl base64 \
| tr '+/' '-_' \
| tr -d '=')
STATE=$(openssl rand -hex 16)
NONCE=$(openssl rand -hex 16)
echo "==> Building authorization URL"
AUTH_URL="${LUXID_BASE_URL}/mga/sps/oauth/oauth20/authorize"
AUTH_URL+="?response_type=code"
AUTH_URL+="&client_id=${LUXID_CLIENT_ID}"
AUTH_URL+="&redirect_uri=${LUXID_REDIRECT_URI}"
AUTH_URL+="&scope=${LUXID_SCOPES// /+}"
AUTH_URL+="&code_challenge=${CODE_CHALLENGE}"
AUTH_URL+="&code_challenge_method=S256"
AUTH_URL+="&state=${STATE}"
AUTH_URL+="&nonce=${NONCE}"
# NOTE: The following step requires a mechanism to complete the login and
# capture the redirect. In a real CI environment this is handled by:
# (a) A headless browser (Playwright, Puppeteer) that fills in credentials
# and captures the callback URL.
# (b) A test-only direct grant endpoint (if enabled for CI use - contact
# support@luxid.lu to discuss CI testing options).
# The placeholder below assumes the authorization code has been captured
# via one of these mechanisms and is available as AUTH_CODE.
# Replace with your actual CI login automation.
echo "==> [CI: complete login and capture code]"
echo " Authorization URL: ${AUTH_URL}"
AUTH_CODE="${AUTH_CODE:?Set AUTH_CODE after completing login in CI}"
echo "==> Exchanging authorization code for tokens"
TOKEN_RESPONSE=$(curl -sf -X POST "${TOKEN_ENDPOINT}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=${AUTH_CODE}" \
-d "redirect_uri=${LUXID_REDIRECT_URI}" \
-d "client_id=${LUXID_CLIENT_ID}" \
-d "client_secret=${LUXID_CLIENT_SECRET}" \
-d "code_verifier=${CODE_VERIFIER}")
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
ID_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.id_token')
REFRESH_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.refresh_token // empty')
echo "==> Token exchange succeeded"
echo " Access token: ${ACCESS_TOKEN:0:20}..."
echo "==> Validating ID Token claims"
PAYLOAD=$(echo "$ID_TOKEN" | cut -d '.' -f2 | base64 -d 2>/dev/null || \
echo "$ID_TOKEN" | cut -d '.' -f2 | python3 -c "
import sys, base64, json
data = sys.stdin.read().strip()
padded = data + '=='[:(4 - len(data) % 4) % 4]
print(json.dumps(json.loads(base64.urlsafe_b64decode(padded))))
")
ISS=$(echo "$PAYLOAD" | jq -r '.iss')
AUD=$(echo "$PAYLOAD" | jq -r '.aud')
SUB=$(echo "$PAYLOAD" | jq -r '.sub')
EMAIL=$(echo "$PAYLOAD" | jq -r '.email // "missing"')
echo " iss: ${ISS}"
echo " aud: ${AUD}"
echo " sub: ${SUB}"
echo " email: ${EMAIL}"
[ "$ISS" = "$LUXID_BASE_URL" ] || { echo "FAIL: unexpected issuer"; exit 1; }
[ "$AUD" = "$LUXID_CLIENT_ID" ] || { echo "FAIL: unexpected audience"; exit 1; }
[ "$SUB" != "null" ] || { echo "FAIL: sub is null"; exit 1; }
echo "==> Calling UserInfo endpoint"
USERINFO=$(curl -sf "${USERINFO_ENDPOINT}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}")
echo " UserInfo sub: $(echo "$USERINFO" | jq -r '.sub')"
[ "$(echo "$USERINFO" | jq -r '.sub')" = "$SUB" ] || \
{ echo "FAIL: UserInfo sub does not match ID Token sub"; exit 1; }
echo "==> Introspecting Access Token"
INTROSPECT=$(curl -sf -X POST "${INTROSPECTION_ENDPOINT}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "${LUXID_CLIENT_ID}:${LUXID_CLIENT_SECRET}" \
-d "token=${ACCESS_TOKEN}" \
-d "token_type_hint=access_token")
ACTIVE=$(echo "$INTROSPECT" | jq -r '.active')
[ "$ACTIVE" = "true" ] || { echo "FAIL: token is not active"; exit 1; }
echo " Token is active: ${ACTIVE}"
if [ -n "$REFRESH_TOKEN" ]; then
echo "==> Testing token refresh"
REFRESH_RESPONSE=$(curl -sf -X POST "${TOKEN_ENDPOINT}" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token" \
-d "refresh_token=${REFRESH_TOKEN}" \
-d "client_id=${LUXID_CLIENT_ID}" \
-d "client_secret=${LUXID_CLIENT_SECRET}")
NEW_ACCESS_TOKEN=$(echo "$REFRESH_RESPONSE" | jq -r '.access_token')
[ "$NEW_ACCESS_TOKEN" != "null" ] || { echo "FAIL: refresh did not return access_token"; exit 1; }
echo " Refresh succeeded, new token: ${NEW_ACCESS_TOKEN:0:20}..."
fi
echo ""
echo "==> All smoke tests passed"
Save this script as luxid-smoke-test.sh in your repository and run it as part of your CI pipeline after deploying to a UAT environment.
Note on the authorization code step: fully automating the browser login step in CI requires either a headless browser integration (Playwright is recommended - it can navigate to the authorization URL, fill in credentials, and capture the redirect) or a CI-specific grant type. Contact LuxID to discuss CI testing options for your integration.
Example: playwright-based CI login
For teams using Playwright for end-to-end testing, the following snippet completes the LuxID login step and captures the authorization code:
// luxid-auth.spec.ts (Playwright)
import { test, expect } from '@playwright/test';
import * as crypto from 'crypto';
test('LuxID Authorization Code + PKCE', async ({ page }) => {
const clientId = process.env.LUXID_CLIENT_ID!;
const redirectUri = process.env.LUXID_REDIRECT_URI!;
const baseUrl = process.env.LUXID_BASE_URL ?? 'https://login-uat.luxid.lu';
// Generate PKCE
const verifier = crypto.randomBytes(32).toString('base64url');
const challenge = crypto.createHash('sha256')
.update(verifier).digest('base64url');
const state = crypto.randomBytes(16).toString('hex');
const authUrl = new URL(`${baseUrl}/mga/sps/oauth/oauth20/authorize`);
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('client_id', clientId);
authUrl.searchParams.set('redirect_uri', redirectUri);
authUrl.searchParams.set('scope', 'openid profile email');
authUrl.searchParams.set('code_challenge', challenge);
authUrl.searchParams.set('code_challenge_method', 'S256');
authUrl.searchParams.set('state', state);
// Navigate and complete login
await page.goto(authUrl.toString());
await page.fill('[name="username"]', process.env.TEST_USER_EMAIL!);
await page.fill('[name="password"]', process.env.TEST_USER_PASSWORD!);
await page.click('[type="submit"]');
// Wait for redirect and capture code
await page.waitForURL(`${redirectUri}**`);
const url = new URL(page.url());
const code = url.searchParams.get('code')!;
expect(code).toBeTruthy();
// Exchange code for tokens (use your HTTP client here)
// ...
});
Related
- Add Login to Your App - integration starting point
- OpenID Connect - full protocol reference
- Sandbox environment - UAT environment details
- Token debugger - inspect tokens from the collection responses
- UserInfo endpoint - UserInfo claim reference
- Token introspection - introspection endpoint reference
- Token validation issues - troubleshooting guide