{
  "info": {
    "name": "EDK-Enterprise-Deployment",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "Full customer journey for an EDK enterprise container deployment: first-run setup, operator sign-in, tenant creation, service configuration, keys and did:web, credential designs (EuPid SD-JWT and Mdl mdoc), status lists, issuance, DCQL and verification, and the authorization code flow. Folder and request names are stable identifiers: the QA-gate snapshots and the documentation site derive filenames from them. Run folders in order; later folders consume variables captured by earlier ones."
  },
  "item": [
    {
      "name": "00 Health",
      "description": "Readiness probes for the six enterprise services. Used by the QA gate as a smoke baseline before the journey starts.",
      "item": [
        {
          "name": "01 KMS health",
          "request": {
            "method": "GET",
            "url": "{{kmsUrl}}/health"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('kms healthy', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 DID health",
          "request": {
            "method": "GET",
            "url": "{{didUrl}}/health"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('did healthy', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 AS health",
          "request": {
            "method": "GET",
            "url": "{{asUrl}}/health"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('as healthy', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Issuer health",
          "request": {
            "method": "GET",
            "url": "{{issuerUrl}}/health"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer healthy', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Verifier health",
          "request": {
            "method": "GET",
            "url": "{{verifierUrl}}/health"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verifier healthy', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "01 Images and Chart",
      "description": "No requests in this step. Pull the six enterprise images (enterprise-platform, enterprise-tenant-kms, enterprise-did, enterprise-tenant-as, enterprise-issuer, enterprise-verifier) and the edk-enterprise Helm chart from the Sphereon registry. Registry and chart repository coordinates are pending final distribution setup; until then images are provided through Docker Hub pull secrets as described in the customer quickstart.",
      "item": []
    },
    {
      "name": "02 Platform Onboarding",
      "description": "Anonymous first-run setup. Step 1 creates/reconciles the platform tenant and operator credential, then generates the signed instance certificate / license request for out-of-band delivery. Step 2 verifies and installs the issued license token, which closes the public setup gate. Platform-admin calls move to the signed-in operator flow after this folder.",
      "item": [
        {
          "name": "01 Get setup status",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/setup/v1/status",
            "description": "Reads the anonymous setup gate and readiness flags before any operator token exists."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('setup status available', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('setup gate is open', () => pm.expect(j.gateOpen).to.eql(true));",
                  "pm.collectionVariables.set('licenseRequestAlias', j.licenseRequestDefaultAlias || 'platform-license-request');"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Get license request readiness",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/setup/v1/license-request",
            "description": "Checks the local KMS-backed request generator without exposing key or secret material."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('license request readiness available', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('manual delivery is selected', () => pm.expect(j.licenseDeliveryMethod).to.eql('MANUAL'));",
                  "pm.collectionVariables.set('licenseRequestAlias', j.defaultAlias || pm.collectionVariables.get('licenseRequestAlias') || 'platform-license-request');"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Bootstrap platform operator",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/setup/v1/bootstrap",
            "description": "Creates the platform tenant, hosted platform authorization server, platform application login surface, and initial operator credential. The setup gate intentionally remains open until license install succeeds.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"operatorEmail\": \"{{operatorEmail}}\",\n  \"operatorDisplayName\": \"Platform Operator\",\n  \"operatorPassword\": \"{{operatorPassword}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('platform operator bootstrapped', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set('platformTenantId', j.tenantId);",
                  "pm.test('operator email is echoed', () => pm.expect(j.operatorEmail).to.eql(pm.variables.get('operatorEmail')));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Generate instance certificate license request",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/setup/v1/license-request/generate",
            "description": "Generates the signed local instance certificate / license request that is sent to the license issuer out of band.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"alias\": \"{{licenseRequestAlias}}\",\n  \"providerId\": \"{{kmsProviderId}}\",\n  \"algorithm\": \"ES256\",\n  \"use\": \"sig\",\n  \"deployment\": {\n    \"installationBaseDomain\": \"saas.localtest.me\",\n    \"deploymentId\": \"deployment-1\",\n    \"tenantName\": \"Acme Corporation\",\n    \"tenantSlug\": \"acme\",\n    \"ownerEmail\": \"{{operatorEmail}}\",\n    \"ownerDisplayName\": \"Platform Operator\",\n    \"technicalContact\": {\n      \"email\": \"{{operatorEmail}}\",\n      \"displayName\": \"Platform Operator\",\n      \"role\": \"TECHNICAL\"\n    },\n    \"administratorContact\": {\n      \"email\": \"{{operatorEmail}}\",\n      \"displayName\": \"Platform Operator\",\n      \"role\": \"ADMINISTRATOR\"\n    },\n    \"organizationName\": \"Acme Corporation\",\n    \"organizationUnit\": \"Platform Operations\",\n    \"locality\": \"Amsterdam\",\n    \"country\": \"NL\",\n    \"licenseDeliveryMethod\": \"MANUAL\"\n  },\n  \"serialNumber\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('license request generated', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.collectionVariables.set('licenseRequestId', j.request.requestId);",
                  "pm.test('request carries both contacts', () => { pm.expect(j.request.technicalContact.email).to.eql(pm.variables.get('operatorEmail')); pm.expect(j.request.administratorContact.email).to.eql(pm.variables.get('operatorEmail')); });"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Verify issued license token",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/setup/v1/license/verify",
            "description": "Verifies the issued license token before install, using the same setup surface and trust configuration that install uses.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"licenseToken\": \"{{licenseToken}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('license token verifies', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Install issued license token",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/setup/v1/license/install",
            "description": "Installs the verified license token and closes the anonymous setup gate.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"licenseToken\": \"{{licenseToken}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('license installed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Setup gate is closed",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/setup/v1/status",
            "description": "After license install, the setup adapter returns 404 so anonymous callers cannot inspect deployment state."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('setup status is hidden after install', () => pm.response.to.have.status(404));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "02b Operator Sign-in",
      "description": "Signs the platform operator in through the hosted authorization server's authorization-code flow with PKCE and captures the bearer token the platform-admin API requires. Redirects are followed explicitly, request by request, so the login form's CSRF tokens and cookies are exercised exactly as a browser would. The PKCE verifier is a fixed QA fixture so snapshot captures stay deterministic.",
      "item": [
        {
          "name": "01 Start authorization request",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/authorize?response_type=code&client_id=platform-operator-cli&redirect_uri={{operatorRedirectUri}}&scope=openid&state=qa-operator-state-0001&code_challenge={{operatorCodeChallenge}}&code_challenge_method=S256",
            "description": "Starts the authorization-code flow for the operator CLI client. The AS answers with a 302 to its hosted login page carrying the pending session id. The S256 code challenge is computed in the pre-request script from the fixed operatorCodeVerifier."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const verifier = pm.variables.replaceIn('{{operatorCodeVerifier}}');",
                  "const challenge = CryptoJS.SHA256(verifier).toString(CryptoJS.enc.Base64)",
                  "  .replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "pm.collectionVariables.set('operatorCodeChallenge', challenge);"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('authorize redirects to the hosted login page', () => pm.response.to.have.status(302));",
                  "const location = pm.response.headers.get('Location');",
                  "pm.expect(location, 'Location header').to.be.a('string').and.to.include('/login?');",
                  "pm.collectionVariables.set('operatorLoginUrl', location);",
                  "const sessionId = /[?&]session_id=([^&]+)/.exec(location);",
                  "pm.expect(sessionId, 'session_id in login URL').to.not.eql(null);",
                  "pm.collectionVariables.set('operatorAuthSessionId', decodeURIComponent(sessionId[1]));",
                  "const returnUrl = /[?&]return_url=([^&]+)/.exec(location);",
                  "pm.expect(returnUrl, 'return_url in login URL').to.not.eql(null);",
                  "pm.collectionVariables.set('operatorReturnUrl', decodeURIComponent(returnUrl[1]));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Open login page",
          "request": {
            "method": "GET",
            "url": "{{operatorLoginUrl}}",
            "description": "Loads the hosted login page. The page sets the oidc_login_csrf cookie and embeds the matching tab_id and session_code hidden inputs that the credential submit must echo back."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('login page rendered', () => pm.response.to.have.status(200));",
                  "const html = pm.response.text();",
                  "const tabId = /name=\"tab_id\" value=\"([^\"]+)\"/.exec(html);",
                  "const sessionCode = /name=\"session_code\" value=\"([^\"]+)\"/.exec(html);",
                  "pm.expect(tabId, 'tab_id hidden input').to.not.eql(null);",
                  "pm.expect(sessionCode, 'session_code hidden input').to.not.eql(null);",
                  "pm.collectionVariables.set('operatorTabId', tabId[1]);",
                  "pm.collectionVariables.set('operatorSessionCode', sessionCode[1]);"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Submit operator credentials",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/login",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "username",
                  "value": "{{operatorEmail}}"
                },
                {
                  "key": "password",
                  "value": "{{operatorPassword}}"
                },
                {
                  "key": "session_id",
                  "value": "{{operatorAuthSessionId}}"
                },
                {
                  "key": "tab_id",
                  "value": "{{operatorTabId}}"
                },
                {
                  "key": "session_code",
                  "value": "{{operatorSessionCode}}"
                },
                {
                  "key": "return_url",
                  "value": "{{operatorReturnUrl}}"
                }
              ]
            },
            "description": "Posts the operator credentials to the login form together with the CSRF tuple from the rendered page. A successful login answers 302 to the authorize callback and sets the oidc_login_sid session cookie."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('credentials accepted', () => pm.response.to.have.status(302));",
                  "const location = pm.response.headers.get('Location');",
                  "pm.expect(location, 'Location header').to.be.a('string');",
                  "pm.expect(location).to.include('/authorize/callback');",
                  "pm.expect(location).to.not.include('error=invalid_credentials');",
                  "pm.collectionVariables.set('operatorCallbackUrl', location);"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Resume authorization callback",
          "protocolProfileBehavior": {
            "followRedirects": false
          },
          "request": {
            "method": "GET",
            "url": "{{operatorCallbackUrl}}",
            "description": "Resumes the pending authorization with the fresh login session cookie. The AS issues the authorization code and answers 302 to the registered redirect URI with code and state."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('authorization code issued', () => pm.response.to.have.status(302));",
                  "const location = pm.response.headers.get('Location');",
                  "pm.expect(location, 'Location header').to.be.a('string').and.to.include('code=');",
                  "pm.expect(location).to.include('state=qa-operator-state-0001');",
                  "const code = /[?&#]code=([^&]+)/.exec(location);",
                  "pm.expect(code, 'authorization code in redirect').to.not.eql(null);",
                  "pm.collectionVariables.set('operatorAuthCode', decodeURIComponent(code[1]));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Exchange code for operator token",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/token",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "authorization_code"
                },
                {
                  "key": "code",
                  "value": "{{operatorAuthCode}}"
                },
                {
                  "key": "redirect_uri",
                  "value": "{{operatorRedirectUri}}"
                },
                {
                  "key": "client_id",
                  "value": "platform-operator-cli"
                },
                {
                  "key": "code_verifier",
                  "value": "{{operatorCodeVerifier}}"
                }
              ]
            },
            "description": "Exchanges the authorization code for tokens, proving possession of the PKCE verifier. The access token carries the operator's roles and is stored as operatorToken for the platform-admin requests in folder 03."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('token issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.expect(j.access_token, 'access_token').to.be.a('string');",
                  "const seg = j.access_token.split('.')[1].replace(/-/g, '+').replace(/_/g, '/');",
                  "const padded = seg + '='.repeat((4 - (seg.length % 4)) % 4);",
                  "const payload = JSON.parse(CryptoJS.enc.Base64.parse(padded).toString(CryptoJS.enc.Utf8));",
                  "const roles = payload.roles || (payload.realm_access && payload.realm_access.roles) || [];",
                  "pm.test('operator token carries platform-admin role', () => pm.expect(roles).to.include('platform-admin'));",
                  "pm.collectionVariables.set('operatorToken', j.access_token);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "03 First Production Tenant",
      "description": "Registers the first production tenant and binds its public protocol endpoints (issuer and verifier hosts).",
      "item": [
        {
          "name": "01 Register tenant",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tenantType\": \"organization\",\n  \"name\": \"Acme Corporation\",\n  \"description\": \"Acme issuing and verification tenant\",\n  \"slug\": \"acme\",\n  \"owner\": {\n    \"type\": \"local\",\n    \"email\": \"admin@acme.example\",\n    \"displayName\": \"Acme Administrator\"\n  },\n  \"ownerDelivery\": {\n    \"mode\": \"none\"\n  }\n}"
            },
            "description": "Registers a root tenant with a local owner account. Owner credential delivery is disabled here; production deployments use email delivery."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant registered', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const tenant = j.tenant || j;",
                  "if (tenant && tenant.id) pm.collectionVariables.set('tenantId', tenant.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Get tenant",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants/{{tenantId}}",
            "description": "Reads the tenant record back by its identifier.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Bind issuer public endpoint",
          "request": {
            "method": "PUT",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants/{{tenantId}}/public-endpoints/OID4VCI_ISSUER",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"host\": \"{{issuerPublicHost}}\",\n  \"pathPrefix\": \"/oid4vci\",\n  \"enabled\": true,\n  \"primaryEndpoint\": true\n}"
            },
            "description": "Binds the public host and path prefix under which this tenant's OID4VCI issuer is reachable. Wallets resolve issuer metadata through this endpoint."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer endpoint bound', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Bind verifier public endpoint",
          "request": {
            "method": "PUT",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants/{{tenantId}}/public-endpoints/OID4VP_VERIFIER",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"host\": \"{{verifierPublicHost}}\",\n  \"pathPrefix\": \"/oid4vp\",\n  \"enabled\": true,\n  \"primaryEndpoint\": true\n}"
            },
            "description": "Binds the public host and path prefix for this tenant's OID4VP verifier."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verifier endpoint bound', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 List public endpoints",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants/{{tenantId}}/public-endpoints",
            "description": "Lists all public endpoint bindings for the tenant.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('endpoints listed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "04 Service Configuration",
      "description": "Configures how users sign in to the tenant: the federation IdP registry covers external OpenID Connect providers. The hosted authorization server itself is provisioned during platform tenant bootstrap; there is no separate per-tenant AS endpoint configuration API.",
      "item": [
        {
          "name": "01 Register federation IdP",
          "request": {
            "method": "POST",
            "url": "{{platformUrl}}/api/platform/admin/v1/federation/idps",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"displayName\": \"Acme Corporate IdP\",\n  \"issuer\": \"https://idp.acme.example\",\n  \"clientId\": \"acme-portal\",\n  \"clientSecret\": \"{{idpClientSecret}}\",\n  \"scopes\": [\"openid\", \"profile\", \"email\"],\n  \"claimsMapping\": {\n    \"subject\": \"sub\",\n    \"email\": \"email\",\n    \"displayName\": \"name\"\n  },\n  \"enabled\": false\n}"
            },
            "description": "Registers an external OpenID Connect identity provider for the tenant. The client secret is stored in the selected secret backend; responses only carry an opaque reference. Enable the IdP after a successful connectivity test (POST /idps/{idpId}/test, then /enable)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('idp registered', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.idpId) pm.collectionVariables.set('idpId', j.idpId);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 List federation IdPs",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/federation/idps",
            "description": "Lists the tenant's configured identity providers.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('idps listed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "04b Tenant Service Token",
      "item": [
        {
          "name": "01 Get tenant service token",
          "request": {
            "method": "POST",
            "url": "{{asUrl}}/token",
            "description": "Uses the tenant AS confidential issuer-service client with client_credentials and stores tenantToken for runtime service calls.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "client_credentials",
                  "type": "text"
                },
                {
                  "key": "scope",
                  "value": "openid",
                  "type": "text"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const id = pm.variables.get('tenantServiceClientId');",
                  "const secret = pm.variables.get('tenantServiceClientSecret');",
                  "pm.request.headers.upsert({ key: 'Authorization', value: 'Basic ' + btoa(id + ':' + secret) });"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant token issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "pm.test('access token returned', () => pm.expect(j.access_token).to.be.a('string').and.not.empty);",
                  "pm.collectionVariables.set('tenantToken', j.access_token);"
                ]
              }
            }
          ]
        }
      ],
      "description": "Obtains a tenant authorization-server token for tenant runtime services. Platform-admin keeps the platform operator token; KMS, DID, issuer, and verifier calls use this tenant token."
    },
    {
      "name": "05 Keys and did-web",
      "description": "Generates ES256 signing keys in the KMS, creates a did:web identifier with authentication and assertionMethod keys, and resolves the hosted did.json document.",
      "item": [
        {
          "name": "01 Generate assertion key",
          "request": {
            "method": "POST",
            "url": "{{kmsUrl}}/api/kms/v1/keys",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"providerId\": \"{{kmsProviderId}}\",\n  \"alias\": \"acme-assertion\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\"sign\"]\n}"
            },
            "description": "Generates a P-256 (ES256) key used for credential signing. The key never leaves the KMS; the response carries the public JWK and the key identifier."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('assertion key generated', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const kp = j.keyPair || j;",
                  "if (kp && kp.kid) pm.collectionVariables.set('assertionKid', kp.kid);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Generate authentication key",
          "request": {
            "method": "POST",
            "url": "{{kmsUrl}}/api/kms/v1/keys",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"providerId\": \"{{kmsProviderId}}\",\n  \"alias\": \"acme-authentication\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\"sign\"]\n}"
            },
            "description": "Generates a second ES256 key used for the DID's authentication relationship."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('authentication key generated', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const kp = j.keyPair || j;",
                  "if (kp && kp.kid) pm.collectionVariables.set('authKid', kp.kid);"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Create did-web identifier",
          "request": {
            "method": "POST",
            "url": "{{didUrl}}/api/did/v1/identifiers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"method\": \"web\",\n  \"didAlias\": \"acme\",\n  \"keyInfo\": {\n    \"providerId\": \"{{kmsProviderId}}\",\n    \"alias\": \"acme-authentication\"\n  },\n  \"options\": {\n    \"domain\": \"{{didWebDomain}}\"\n  }\n}"
            },
            "description": "Creates a did:web identifier anchored to the deployment's public domain. The authentication key becomes the initial verification method. did:web identifiers are not key-derived: the domain is the identifier, so it must match the host that serves the DID document."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('did created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.did) {",
                  "  pm.collectionVariables.set('did', j.did);",
                  "  pm.collectionVariables.set('didEncoded', encodeURIComponent(j.did));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Add assertion verification method",
          "request": {
            "method": "POST",
            "url": "{{didUrl}}/api/did/v1/identifiers/{{didEncoded}}/verification-methods",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"JsonWebKey2020\",\n  \"controller\": \"{{did}}\",\n  \"keyInfo\": {\n    \"providerId\": \"{{kmsProviderId}}\",\n    \"alias\": \"acme-assertion\"\n  },\n  \"purposes\": [\"assertionMethod\"]\n}"
            },
            "description": "Adds the assertion key as a verification method with the assertionMethod relationship. Credentials issued by this tenant are signed with this key."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verification method added', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Resolve DID",
          "request": {
            "method": "GET",
            "url": "{{didUrl}}/api/did/v1/identifiers/{{didEncoded}}",
            "description": "Resolves the DID through the management API and returns the full record including the DID document.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('did resolved', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Fetch hosted did.json",
          "request": {
            "method": "GET",
            "url": "{{didUrl}}/.well-known/did.json",
            "description": "Fetches the DID document from the public did:web hosting surface. External resolvers use exactly this URL; it must be reachable on the domain named in the DID."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('did document hosted', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "06 Issuer Tenant Settings",
      "description": "Creates the issuer design for the tenant: the issuing party identity bound to the tenant's DID. Credential designs created in the next step reference this issuer.",
      "item": [
        {
          "name": "01 Create issuer design",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/credential-design/v1/designs/issuers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bindings\": [\n    {\n      \"issuerDid\": \"{{did}}\"\n    }\n  ],\n  \"alias\": \"acme-authority\",\n  \"hostingMode\": \"LOCAL\",\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"displayName\": \"Acme Authority\",\n      \"description\": \"Acme Corporation credential issuing authority\"\n    }\n  ]\n}"
            },
            "description": "Binds the issuing identity to the tenant's did:web identifier. Wallets display this name and description when presenting a credential offer."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer design created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('issuerDesignId', j.id);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "07 Credential Designs",
      "description": "Creates the two credential designs through the credential-design API: EuPid as dc+sd-jwt and Mdl as mso_mdoc (doctype org.iso.18013.5.1.mDL). Claims, formats, and selective-disclosure policy follow the IDK reference credentials.",
      "item": [
        {
          "name": "01 Create EuPid SD-JWT design",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/credential-design/v1/designs/credentials",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bindings\": [\n    {\n      \"vct\": \"{{issuerUrl}}/oid4vci/vct/EuPid\",\n      \"credentialConfigurationId\": \"EuPid\"\n    }\n  ],\n  \"alias\": \"eu-pid\",\n  \"hostingMode\": \"LOCAL\",\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"name\": \"EU Personal ID\",\n      \"description\": \"European personal identity credential\"\n    }\n  ],\n  \"claims\": [\n    { \"path\": [{ \"type\": \"property\", \"name\": \"family_name\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Family name\" }], \"mandatory\": true, \"order\": 1, \"sdPolicy\": \"ALWAYS\" },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"given_name\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Given name\" }], \"mandatory\": true, \"order\": 2, \"sdPolicy\": \"ALWAYS\" },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"birth_date\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Date of birth\" }], \"mandatory\": true, \"order\": 3 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"age_over_18\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Age over 18\" }], \"mandatory\": false, \"order\": 4 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"nationality\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Nationality\" }], \"mandatory\": true, \"order\": 5 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"issuing_authority\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Issuing authority\" }], \"mandatory\": true, \"order\": 6 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"issuing_country\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Issuing country\" }], \"mandatory\": true, \"order\": 7, \"sdPolicy\": \"ALWAYS\" },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"document_number\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Document number\" }], \"mandatory\": false, \"order\": 8 }\n  ]\n}"
            },
            "description": "EuPid credential design bound by vct and credential configuration id. family_name, given_name, and issuing_country are always selectively disclosable, matching the IDK reference configuration."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid design created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('eupidDesignId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Create Mdl mdoc design",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/credential-design/v1/designs/credentials",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"bindings\": [\n    {\n      \"docType\": \"org.iso.18013.5.1.mDL\",\n      \"credentialConfigurationId\": \"Mdl\"\n    }\n  ],\n  \"alias\": \"mdl\",\n  \"hostingMode\": \"LOCAL\",\n  \"displays\": [\n    {\n      \"locale\": \"en\",\n      \"name\": \"Mobile Driving Licence\",\n      \"description\": \"ISO 18013-5 mobile driving licence\"\n    }\n  ],\n  \"claims\": [\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"family_name\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Family name\" }], \"mandatory\": true, \"order\": 1 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"given_name\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Given name\" }], \"mandatory\": true, \"order\": 2 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"birth_date\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Date of birth\" }], \"mandatory\": true, \"order\": 3 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"issue_date\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Issue date\" }], \"mandatory\": true, \"order\": 4 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"expiry_date\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Expiry date\" }], \"mandatory\": true, \"order\": 5 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"issuing_country\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Issuing country\" }], \"mandatory\": true, \"order\": 6 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"issuing_authority\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Issuing authority\" }], \"mandatory\": true, \"order\": 7 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"document_number\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Document number\" }], \"mandatory\": true, \"order\": 8 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"portrait\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Portrait\" }], \"mandatory\": true, \"order\": 9 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"driving_privileges\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"Driving privileges\" }], \"mandatory\": true, \"order\": 10 },\n    { \"path\": [{ \"type\": \"property\", \"name\": \"org.iso.18013.5.1\" }, { \"type\": \"property\", \"name\": \"un_distinguishing_sign\" }], \"labels\": [{ \"locale\": \"en\", \"label\": \"UN distinguishing sign\" }], \"mandatory\": true, \"order\": 11 }\n  ]\n}"
            },
            "description": "Mdl credential design bound by ISO 18013-5 doctype. Claims are namespace-qualified under org.iso.18013.5.1, matching the IDK reference configuration."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl design created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('mdlDesignId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "03 List credential designs",
          "request": {
            "method": "GET",
            "url": "{{issuerUrl}}/api/credential-design/v1/designs/credentials",
            "description": "Lists the tenant's credential designs.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('designs listed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "08 Status Lists",
      "description": "Creates an IETF Token Status List for EuPid revocation, fetches the publicly hosted signed token, and demonstrates a status update. Issued SD-JWT credentials carry a status claim pointing at the hosted list URI with a randomly allocated index.",
      "item": [
        {
          "name": "01 Create token status list",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/statuslist/v1/statuslists",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"correlationId\": \"eupid-revocation\",\n  \"spec\": \"token_status_list\",\n  \"proofFormat\": \"jwt\",\n  \"issuer\": \"{{issuerUrl}}\",\n  \"statusListUri\": \"{{issuerUrl}}/public/statuslists/eupid-revocation\",\n  \"purposes\": [\"revocation\"],\n  \"length\": 131072,\n  \"bitsPerStatus\": 1,\n  \"signingKeyAlias\": \"acme-assertion\"\n}"
            },
            "description": "Creates the IETF Token Status List the EuPid configuration references, signed with the tenant's assertion key. The deployment configuration declares the list under the statuslists namespace; this call materializes it. The correlation id is the stable business identifier; the signed token is re-issued whenever entries change, and indexes are allocated randomly at issuance time, never sequentially. Issuance refuses to mint when a configured list cannot be resolved."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status list created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.id) pm.collectionVariables.set('statusListId', j.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Fetch hosted status list token",
          "request": {
            "method": "GET",
            "url": "{{issuerUrl}}/public/statuslists/eupid-revocation",
            "description": "Fetches the signed status list token from the public hosting surface by business correlation id. Verifiers dereference exactly this URL when checking credential status."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status list hosted', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Revoke a status entry",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/statuslist/v1/statuslists/{{statusListId}}/status",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"statusListIndex\": 42,\n  \"value\": 1\n}"
            },
            "description": "Sets the status bit at an index to 1 (revoked). A status read exposes only the bit value; whether an index is allocated is intentionally not observable."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('entry revoked', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Reactivate the status entry",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/statuslist/v1/statuslists/{{statusListId}}/status",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"statusListIndex\": 42,\n  \"value\": 0\n}"
            },
            "description": "Sets the same index back to 0 (valid)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('entry reactivated', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "09a Issue Credentials (simple)",
      "description": "Issues the EuPid SD-JWT and the Mdl mdoc with all subject data supplied at offer creation, then walks the wallet protocol steps (offer resolution, token, credential request) headlessly. The wallet holder key lives in the KMS so the proof of possession can be signed without leaving the deployment.",
      "item": [
        {
          "name": "01 Generate wallet holder key",
          "request": {
            "method": "POST",
            "url": "{{kmsUrl}}/api/kms/v1/keys",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"providerId\": \"{{kmsProviderId}}\",\n  \"alias\": \"wallet-holder\",\n  \"use\": \"sig\",\n  \"alg\": \"ECDSA_SHA256\",\n  \"keyOperations\": [\"sign\"]\n}"
            },
            "description": "Simulated wallet key. In production this key lives in the holder's wallet; here the KMS stands in so the proof JWT can be signed during the test run."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('wallet key generated', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "const kp = j.keyPair || j;",
                  "if (kp && kp.kid) pm.collectionVariables.set('walletKid', kp.kid);",
                  "const jwk = kp && kp.jose ? kp.jose.publicJwk : (kp ? kp.publicJwk : null);",
                  "if (jwk) pm.collectionVariables.set('walletJwk', JSON.stringify(jwk));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Create EuPid offer",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"EuPid\"],\n  \"grants\": {\n    \"urn:ietf:params:oauth:grant-type:pre-authorized_code\": {}\n  },\n  \"credential_subject_data\": {\n    \"family_name\": \"Mustermann\",\n    \"given_name\": \"Erika\",\n    \"birth_date\": \"1964-08-12\",\n    \"age_over_18\": true,\n    \"nationality\": \"DE\",\n    \"issuing_authority\": \"DE\",\n    \"issuing_country\": \"DE\",\n    \"document_number\": \"1234567890\"\n  },\n  \"correlation_id\": \"e2e-eupid-001\"\n}"
            },
            "description": "Creates a pre-authorized credential offer with the subject data supplied inline. The response carries the offer URI a wallet would scan."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('offer created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.offer_uri) {",
                  "  pm.collectionVariables.set('offerUri', j.offer_uri);",
                  "  const m = j.offer_uri.match(/credential_offer_uri=([^&]+)/);",
                  "  if (m) pm.collectionVariables.set('credentialOfferUri', decodeURIComponent(m[1]));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Resolve credential offer",
          "request": {
            "method": "GET",
            "url": "{{credentialOfferUri}}",
            "description": "Resolves the offer exactly as a wallet does and extracts the pre-authorized code."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('offer resolved', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const grant = j.grants && j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'];",
                  "if (grant && grant['pre-authorized_code']) pm.collectionVariables.set('preAuthCode', grant['pre-authorized_code']);",
                  "// OID4VCI 1.0: for a path-bearing issuer identifier the metadata URL inserts",
                  "// the well-known segment between host and path.",
                  "if (j.credential_issuer) {",
                  "  pm.collectionVariables.set('credentialIssuer', j.credential_issuer);",
                  "  const m = j.credential_issuer.match(/^(https?:\\/\\/[^/]+)(\\/.*)?$/);",
                  "  if (m) pm.collectionVariables.set('issuerMetadataUrl', m[1] + '/.well-known/openid-credential-issuer' + (m[2] || ''));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Fetch issuer metadata",
          "request": {
            "method": "GET",
            "url": "{{issuerMetadataUrl}}",
            "description": "Issuer metadata: credential configurations, endpoints, and the authorization servers the issuer trusts. The URL derives from the offer's credential_issuer per OID4VCI 1.0 (the well-known segment goes between host and issuer path)."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuer metadata served', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "if (j.credential_endpoint) pm.collectionVariables.set('credentialEndpoint', j.credential_endpoint);",
                  "const tokenEndpoint = j.token_endpoint || (pm.variables.get('asUrl') + '/token');",
                  "pm.collectionVariables.set('tokenEndpoint', tokenEndpoint);",
                  "if (j.nonce_endpoint) pm.collectionVariables.set('nonceEndpoint', j.nonce_endpoint);"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Exchange pre-authorized code for token",
          "request": {
            "method": "POST",
            "url": "{{tokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code"
                },
                {
                  "key": "pre-authorized_code",
                  "value": "{{preAuthCode}}"
                }
              ]
            },
            "description": "Token request with the pre-authorized code grant. The response carries the access token for the credential endpoint and a nonce for the proof of possession."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('token issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "if (j.access_token) pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('cNonce', j.c_nonce || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "06 Request EuPid credential",
          "request": {
            "method": "POST",
            "url": "{{credentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{walletAccessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_id\": \"EuPid\",\n  \"proofs\": {\n    \"jwt\": [\"{{proofJwt}}\"]\n  }\n}"
            },
            "description": "Credential request with an ES256 proof of possession (typ openid4vci-proof+jwt) built in the pre-request script and signed through the KMS. The response carries the SD-JWT credential including the status claim that references the hosted status list."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const b64u = (obj) => CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(obj))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "const jwk = JSON.parse(pm.collectionVariables.get('walletJwk') || '{}');",
                  "function buildAndSign(nonce) {",
                  "  const header = { alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: jwk };",
                  "  const payload = { aud: pm.collectionVariables.get('credentialIssuer') || pm.variables.get('issuerUrl'), iat: Math.floor(Date.now() / 1000), nonce: nonce };",
                  "  const signingInput = b64u(header) + '.' + b64u(payload);",
                  "  const inputB64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(signingInput));",
                  "  pm.sendRequest({",
                  "    url: pm.variables.get('kmsUrl') + '/api/kms/v1/signatures/raw/create',",
                  "    method: 'POST',",
                  "    header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (pm.collectionVariables.get('tenantToken') || pm.variables.get('tenantToken')) },",
                  "    body: { mode: 'raw', raw: JSON.stringify({ keyInfo: { providerId: pm.variables.get('kmsProviderId'), alias: 'wallet-holder' }, input: inputB64 }) }",
                  "  }, (err, res) => {",
                  "    if (err) { console.error('KMS signing failed', err); return; }",
                  "    const j = res.json();",
                  "    const sigB64u = String(j.signature || '').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "    pm.collectionVariables.set('proofJwt', signingInput + '.' + sigB64u);",
                  "  });",
                  "}",
                  "// Nonce: the token response may omit c_nonce; the issuer's nonce endpoint",
                  "// (from metadata) is the authoritative source.",
                  "const tokenNonce = pm.collectionVariables.get('cNonce');",
                  "const nonceEndpoint = pm.collectionVariables.get('nonceEndpoint');",
                  "if (tokenNonce) {",
                  "  buildAndSign(tokenNonce);",
                  "} else if (nonceEndpoint) {",
                  "  pm.sendRequest({ url: nonceEndpoint, method: 'POST' }, (err, res) => {",
                  "    if (err) { console.error('nonce fetch failed', err); return; }",
                  "    buildAndSign(res.json().c_nonce);",
                  "  });",
                  "} else {",
                  "  buildAndSign(undefined);",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid credential issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const cred = (j.credentials && j.credentials[0] && j.credentials[0].credential) || j.credential;",
                  "if (cred) pm.collectionVariables.set('eupidCredential', cred);"
                ]
              }
            }
          ]
        },
        {
          "name": "07 Check EuPid offer status",
          "request": {
            "method": "GET",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/credential/offers/e2e-eupid-001",
            "description": "Backend session status after issuance: the lifecycle ends in credential_issued.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('session tracked', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "08 Create Mdl offer",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"Mdl\"],\n  \"grants\": {\n    \"urn:ietf:params:oauth:grant-type:pre-authorized_code\": {}\n  },\n  \"credential_subject_data\": {\n    \"org.iso.18013.5.1.family_name\": \"Mustermann\",\n    \"org.iso.18013.5.1.given_name\": \"Erika\",\n    \"org.iso.18013.5.1.birth_date\": \"1964-08-12\",\n    \"org.iso.18013.5.1.issue_date\": \"2026-01-15\",\n    \"org.iso.18013.5.1.expiry_date\": \"2031-01-15\",\n    \"org.iso.18013.5.1.issuing_country\": \"DE\",\n    \"org.iso.18013.5.1.issuing_authority\": \"DE\",\n    \"org.iso.18013.5.1.document_number\": \"D123456789\",\n    \"org.iso.18013.5.1.portrait\": \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==\",\n    \"org.iso.18013.5.1.driving_privileges\": [\n      {\n        \"vehicle_category_code\": \"B\",\n        \"issue_date\": \"2010-03-01\",\n        \"expiry_date\": \"2031-01-15\"\n      }\n    ],\n    \"org.iso.18013.5.1.un_distinguishing_sign\": \"D\"\n  },\n  \"correlation_id\": \"e2e-mdl-001\"\n}"
            },
            "description": "Pre-authorized offer for the mdoc credential with namespace-qualified subject data."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl offer created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.offer_uri) {",
                  "  const m = j.offer_uri.match(/credential_offer_uri=([^&]+)/);",
                  "  if (m) pm.collectionVariables.set('mdlCredentialOfferUri', decodeURIComponent(m[1]));",
                  "}"
                ]
              }
            }
          ]
        },
        {
          "name": "09 Resolve Mdl offer",
          "request": {
            "method": "GET",
            "url": "{{mdlCredentialOfferUri}}",
            "description": "Resolves the Mdl offer and extracts the pre-authorized code."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl offer resolved', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const grant = j.grants && j.grants['urn:ietf:params:oauth:grant-type:pre-authorized_code'];",
                  "if (grant && grant['pre-authorized_code']) pm.collectionVariables.set('mdlPreAuthCode', grant['pre-authorized_code']);"
                ]
              }
            }
          ]
        },
        {
          "name": "10 Exchange Mdl code for token",
          "request": {
            "method": "POST",
            "url": "{{tokenEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/x-www-form-urlencoded"
              }
            ],
            "body": {
              "mode": "urlencoded",
              "urlencoded": [
                {
                  "key": "grant_type",
                  "value": "urn:ietf:params:oauth:grant-type:pre-authorized_code"
                },
                {
                  "key": "pre-authorized_code",
                  "value": "{{mdlPreAuthCode}}"
                }
              ]
            },
            "description": "Token request for the Mdl issuance session."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl token issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "if (j.access_token) pm.collectionVariables.set('walletAccessToken', j.access_token);",
                  "pm.collectionVariables.set('cNonce', j.c_nonce || '');"
                ]
              }
            }
          ]
        },
        {
          "name": "11 Request Mdl credential",
          "request": {
            "method": "POST",
            "url": "{{credentialEndpoint}}",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{walletAccessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_id\": \"Mdl\",\n  \"proofs\": {\n    \"jwt\": [\"{{proofJwt}}\"]\n  }\n}"
            },
            "description": "Credential request for the mdoc. The response carries the base64url-encoded ISO 18013-5 mdoc bound to the wallet key (cose_key binding)."
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const b64u = (obj) => CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(obj))).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "const jwk = JSON.parse(pm.collectionVariables.get('walletJwk') || '{}');",
                  "function buildAndSign(nonce) {",
                  "  const header = { alg: 'ES256', typ: 'openid4vci-proof+jwt', jwk: jwk };",
                  "  const payload = { aud: pm.collectionVariables.get('credentialIssuer') || pm.variables.get('issuerUrl'), iat: Math.floor(Date.now() / 1000), nonce: nonce };",
                  "  const signingInput = b64u(header) + '.' + b64u(payload);",
                  "  const inputB64 = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(signingInput));",
                  "  pm.sendRequest({",
                  "    url: pm.variables.get('kmsUrl') + '/api/kms/v1/signatures/raw/create',",
                  "    method: 'POST',",
                  "    header: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + (pm.collectionVariables.get('tenantToken') || pm.variables.get('tenantToken')) },",
                  "    body: { mode: 'raw', raw: JSON.stringify({ keyInfo: { providerId: pm.variables.get('kmsProviderId'), alias: 'wallet-holder' }, input: inputB64 }) }",
                  "  }, (err, res) => {",
                  "    if (err) { console.error('KMS signing failed', err); return; }",
                  "    const j = res.json();",
                  "    const sigB64u = String(j.signature || '').replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');",
                  "    pm.collectionVariables.set('proofJwt', signingInput + '.' + sigB64u);",
                  "  });",
                  "}",
                  "// Nonce: the token response may omit c_nonce; the issuer's nonce endpoint",
                  "// (from metadata) is the authoritative source.",
                  "const tokenNonce = pm.collectionVariables.get('cNonce');",
                  "const nonceEndpoint = pm.collectionVariables.get('nonceEndpoint');",
                  "if (tokenNonce) {",
                  "  buildAndSign(tokenNonce);",
                  "} else if (nonceEndpoint) {",
                  "  pm.sendRequest({ url: nonceEndpoint, method: 'POST' }, (err, res) => {",
                  "    if (err) { console.error('nonce fetch failed', err); return; }",
                  "    buildAndSign(res.json().c_nonce);",
                  "  });",
                  "} else {",
                  "  buildAndSign(undefined);",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl credential issued', () => pm.response.to.have.status(200));",
                  "const j = pm.response.json();",
                  "const cred = (j.credentials && j.credentials[0] && j.credentials[0].credential) || j.credential;",
                  "if (cred) pm.collectionVariables.set('mdlCredential', cred);"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "09b Issue Credentials (pipeline)",
      "description": "The pipeline API alternative: instead of supplying credential_subject_data at offer creation, attribute sources contribute data incrementally into an issuance session, completeness is evaluated against the credential's bindings, and an approval gate releases issuance. Use this for multi-source or human-approved flows.",
      "item": [
        {
          "name": "01 Initialize pipeline session",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/sessions",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"pipeline_configuration\": {\n    \"pipelineId\": \"eupid-registration\",\n    \"sourceBindings\": [\n      {\n        \"sourceId\": \"rest-api\",\n        \"phases\": [\n          {\n            \"value\": \"oid4vci_credential_request\"\n          }\n        ]\n      }\n    ],\n    \"claimsBindings\": [\n      {\n        \"id\": \"EuPid\",\n        \"semanticAttributeSetRef\": {\n          \"bundleId\": \"credential-config:EuPid\"\n        },\n        \"deferralPolicy\": {\n          \"approvalRequired\": true\n        }\n      }\n    ]\n  },\n  \"correlation_id\": \"e2e-pipeline-001\",\n  \"initial_lookup_keys\": [\n    {\n      \"name\": \"document_number\",\n      \"value\": \"1234567890\",\n      \"type\": {\n        \"value\": \"document_number\"\n      },\n      \"producedBy\": \"registration-office\",\n      \"phase\": {\n        \"value\": \"session_init\"\n      },\n      \"timestamp\": \"2026-06-01T00:00:00Z\"\n    }\n  ],\n  \"ttl_seconds\": 600\n}"
            },
            "description": "Opens an attribute pipeline session keyed by correlation id, seeded with the lookup keys that attribute sources use to find the subject."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('pipeline session opened', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Contribute attributes",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/sessions/e2e-pipeline-001/attributes",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"attributes\": [\n    {\n      \"path\": \"family_name\",\n      \"value\": {\n        \"type\": \"data\",\n        \"value\": \"Mustermann\"\n      },\n      \"sourceId\": \"registration-office\",\n      \"phase\": {\n        \"value\": \"oid4vci_credential_request\"\n      },\n      \"timestamp\": \"2026-06-01T00:00:00Z\",\n      \"verified\": true\n    },\n    {\n      \"path\": \"given_name\",\n      \"value\": {\n        \"type\": \"data\",\n        \"value\": \"Erika\"\n      },\n      \"sourceId\": \"registration-office\",\n      \"phase\": {\n        \"value\": \"oid4vci_credential_request\"\n      },\n      \"timestamp\": \"2026-06-01T00:00:00Z\",\n      \"verified\": true\n    },\n    {\n      \"path\": \"birth_date\",\n      \"value\": {\n        \"type\": \"data\",\n        \"value\": \"1964-08-12\"\n      },\n      \"sourceId\": \"registration-office\",\n      \"phase\": {\n        \"value\": \"oid4vci_credential_request\"\n      },\n      \"timestamp\": \"2026-06-01T00:00:00Z\",\n      \"verified\": true\n    }\n  ]\n}"
            },
            "description": "An attribute source contributes verified claims into the session. Multiple sources can contribute; priorities resolve conflicts."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('attributes contributed', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Read accumulated attributes",
          "request": {
            "method": "GET",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/sessions/e2e-pipeline-001/attributes",
            "description": "Reads the attributes accumulated so far across all sources.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('attributes returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 Evaluate completeness",
          "request": {
            "method": "GET",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/sessions/e2e-pipeline-001/completeness",
            "description": "Evaluates whether the accumulated attributes satisfy each credential binding's mandatory claims, and whether deferral or approval is recommended.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('completeness evaluated', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "05 Approve issuance",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/sessions/e2e-pipeline-001/approve",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"decision\": \"APPROVE\",\n  \"reason\": \"Registration office data verified\"\n}"
            },
            "description": "Releases the approval gate. After approval, issuance proceeds with the pipeline-collected attributes instead of inline subject data."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('issuance approved', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "10 DCQL Queries",
      "description": "Creates the DCQL queries the verifier uses: one for the EuPid SD-JWT, one for the Mdl mdoc, and one requesting both. Each query selects only the claims it needs, which is what drives selective disclosure.",
      "item": [
        {
          "name": "01 Create EuPid query",
          "request": {
            "method": "POST",
            "url": "{{verifierUrl}}/api/dcql/v1/queries",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"eupid-sdjwt\",\n  \"name\": \"EuPid identity check\",\n  \"description\": \"Requests name and age attestation from the EU Personal ID\",\n  \"enabled\": true,\n  \"dcqlQuery\": {\n    \"credentials\": [\n      {\n        \"id\": \"eupid\",\n        \"format\": \"dc+sd-jwt\",\n        \"meta\": {\n          \"vct_values\": [\"{{issuerUrl}}/oid4vci/vct/EuPid\"]\n        },\n        \"claims\": [\n          { \"path\": [\"family_name\"] },\n          { \"path\": [\"given_name\"] },\n          { \"path\": [\"age_over_18\"] }\n        ]\n      }\n    ]\n  }\n}"
            },
            "description": "Selects only family_name, given_name, and age_over_18 from the EuPid. The other claims stay undisclosed."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('eupid query stored', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Create Mdl query",
          "request": {
            "method": "POST",
            "url": "{{verifierUrl}}/api/dcql/v1/queries",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"mdl-mdoc\",\n  \"name\": \"Driving licence check\",\n  \"description\": \"Requests driving privileges from the mobile driving licence\",\n  \"enabled\": true,\n  \"dcqlQuery\": {\n    \"credentials\": [\n      {\n        \"id\": \"mdl\",\n        \"format\": \"mso_mdoc\",\n        \"meta\": {\n          \"doctype_value\": \"org.iso.18013.5.1.mDL\"\n        },\n        \"claims\": [\n          { \"path\": [\"org.iso.18013.5.1\", \"family_name\"] },\n          { \"path\": [\"org.iso.18013.5.1\", \"driving_privileges\"] }\n        ]\n      }\n    ]\n  }\n}"
            },
            "description": "Selects only family_name and driving_privileges from the ISO 18013-5 namespace."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('mdl query stored', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Create combined query",
          "request": {
            "method": "POST",
            "url": "{{verifierUrl}}/api/dcql/v1/queries",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queryId\": \"eupid-and-mdl\",\n  \"name\": \"Identity and driving licence\",\n  \"description\": \"Requests the EuPid and the mobile driving licence in one presentation\",\n  \"enabled\": true,\n  \"dcqlQuery\": {\n    \"credentials\": [\n      {\n        \"id\": \"eupid\",\n        \"format\": \"dc+sd-jwt\",\n        \"meta\": {\n          \"vct_values\": [\"{{issuerUrl}}/oid4vci/vct/EuPid\"]\n        },\n        \"claims\": [\n          { \"path\": [\"family_name\"] },\n          { \"path\": [\"given_name\"] }\n        ]\n      },\n      {\n        \"id\": \"mdl\",\n        \"format\": \"mso_mdoc\",\n        \"meta\": {\n          \"doctype_value\": \"org.iso.18013.5.1.mDL\"\n        },\n        \"claims\": [\n          { \"path\": [\"org.iso.18013.5.1\", \"driving_privileges\"] }\n        ]\n      }\n    ]\n  }\n}"
            },
            "description": "Requests both credentials in a single presentation, each with its own claim selection."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('combined query stored', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "04 List queries",
          "request": {
            "method": "GET",
            "url": "{{verifierUrl}}/api/dcql/v1/queries",
            "description": "Lists the tenant's stored DCQL query configurations.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('queries listed', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "11 Verification",
      "description": "Runs a verification session against a stored DCQL query. The wallet-side presentation (key-bound SD-JWT and mdoc DeviceResponse) requires holder cryptography beyond this collection; the session lifecycle is exercised end to end.",
      "item": [
        {
          "name": "01 Create verification request",
          "request": {
            "method": "POST",
            "url": "{{verifierUrl}}/oid4vp/backend/auth/requests",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"query_id\": \"eupid-and-mdl\",\n  \"client_id\": \"redirect_uri:{{verifierUrl}}/callback\",\n  \"correlation_id\": \"e2e-verify-001\"\n}"
            },
            "description": "Opens a verification session referencing the stored query. The response carries the authorization request URI a wallet would open."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('verification session created', () => pm.expect([200, 201]).to.include(pm.response.code));",
                  "const j = pm.response.json();",
                  "if (j.authorization_request_uri) pm.collectionVariables.set('authorizationRequestUri', j.authorization_request_uri);",
                  "pm.collectionVariables.set('verifyCorrelationId', j.correlation_id || 'e2e-verify-001');"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Poll verification status",
          "request": {
            "method": "GET",
            "url": "{{verifierUrl}}/oid4vp/backend/auth/requests/{{verifyCorrelationId}}",
            "description": "Polls the session. With no wallet attached, the session stays in authorization_request_created; after a wallet presents, it ends in authorization_response_verified with the disclosed claims.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status returned', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Cancel verification session",
          "request": {
            "method": "DELETE",
            "url": "{{verifierUrl}}/oid4vp/backend/auth/requests/{{verifyCorrelationId}}",
            "description": "Cleans up the verification session.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('session removed', () => pm.expect([200, 204]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "12 AS Accounts and Identities",
      "description": "No requests yet. Creating login accounts and identities for the enterprise authorization server requires a dedicated identity-store API. Until that surface exists, accounts for the hosted AS are limited to the operator account created during platform tenant bootstrap and federated sign-in through the IdPs registered in step 04. Tracked as gap (b) in the deployment gap inventory.",
      "item": []
    },
    {
      "name": "13 Authorization Code Flow",
      "description": "Issues a credential through the authorization code grant: the wallet is sent to the authorization server to authenticate before receiving the credential. Completing the interactive login requires an AS account (step 12); the offer creation and AS metadata steps below are the non-interactive part of the flow.",
      "item": [
        {
          "name": "01 Create offer with authorization code grant",
          "request": {
            "method": "POST",
            "url": "{{issuerUrl}}/api/oid4vci/v1/backend/credential/offers",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credential_configuration_ids\": [\"EuPid\"],\n  \"grants\": {\n    \"authorization_code\": {\n      \"issuer_state\": \"e2e-authcode-001\"\n    }\n  },\n  \"correlation_id\": \"e2e-authcode-001\"\n}"
            },
            "description": "Creates an offer whose grant directs the wallet through the OAuth2 authorization code flow with PKCE instead of a pre-authorized code. No subject data is supplied; claims are resolved after the user authenticates."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('auth-code offer created', () => pm.expect([200, 201]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Fetch authorization server metadata",
          "request": {
            "method": "GET",
            "url": "{{asUrl}}/.well-known/oauth-authorization-server",
            "description": "Authorization server metadata the wallet uses to drive the flow: authorization endpoint, token endpoint, supported grants, and PKCE methods."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('as metadata served', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "14 OFFLINE Config Mount",
      "item": [
        {
          "name": "01 Setup endpoint remains closed after mounted bootstrap",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/setup/v1/status",
            "description": "The mounted manifest plus setup install path leaves the anonymous setup adapter closed; operators continue through sign-in and platform-admin instead."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('setup endpoint is closed', () => pm.response.to.have.status(404));"
                ]
              }
            }
          ]
        },
        {
          "name": "02 Platform-admin rejects tenant token",
          "request": {
            "method": "GET",
            "url": "{{platformUrl}}/api/platform/admin/v1/tenants",
            "description": "Negative auth boundary: a tenant AS token must not authorize platform-admin.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{tenantToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('tenant token is rejected by platform-admin', () => pm.expect([401, 403]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        },
        {
          "name": "03 Tenant KMS rejects platform token",
          "request": {
            "method": "GET",
            "url": "{{kmsUrl}}/api/kms/v1/providers",
            "description": "Negative auth boundary: a platform operator token must not authorize tenant KMS.",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer {{operatorToken}}"
              }
            ]
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('platform token is rejected by tenant KMS', () => pm.expect([401, 403]).to.include(pm.response.code));"
                ]
              }
            }
          ]
        }
      ],
      "description": "Offline/config-mount assertion for the QA stack: the mounted manifest provides the license token, recipient key, trust bundle, deployment id, and operator seed. A booted stack reaches operator sign-in and first-tenant registration without contacting the platform distribution service for those startup materials."
    }
  ]
}
